README.md

Art Institute of Chicago Official Mobile App

A digital guide to the Art Institute of Chicago. Built with Swift 4 for iOS.

The Art Insitute of Chicago Official Mobile App is your personal,
pocket-sized guide to our collection. The mobile experience merges location-aware technology with audio
storytelling, letting the art speak to you. The Art Institute offers nearly a million square feet to
explore—the Official Mobile App will be your guide.

The Art Institute of Chicago's mobile app launched in the iOS App Store on August 10, 2016. It's
still in place today and is being maintained by a team of internal developers.

Please note that while we took steps to generalize this project, it is not meant to be a plug-and-play product for your institution. You may need to substantially modify the iOS app, beyond basic configuration. More importantly, you may need to contact Apple regarding indoor positioning, or research and implement some open-source alternative.

Features

This app is split into four distinct sections. In the future, additional sections might be added as we improve our infrastructure.

Home

Provides a summary of current tours/exhibits/events at the museum.

Audio Guide

Allows users to type in numbers found in the physical space of the museum next to artworks, which pulls up the corresponding audio content and information.

Map

The map has a number of information points (annotations) enabled at various zoom levels. These include:

Departments

Amenities (bathrooms, elevators, etc.)

Galleries

Artworks

The map uses CoreLocation to locate and orient the user when they are on-location in the museum.

Tours

Provides custom tours with unique audio content that work in tandem with the map and guide users on a narrated journey.

Search

Provides the ability to search for artworks, tours and exhibitions currently on display at the museum. Using the search, users are also able to find the location on the map of artworks as well as gift shops, restrooms, dining locations and the member lounge.

Information

This section includes:

Museum Information: basic museum info: hours, holidays, etc.

Language Settings: allowing you to switch the language of the app to English, Spanish or Chinese.

Location Settings: allowing you to modify your preference for tracking your location in the museum.

Getting Started

The Official Mobile App consists of two parts: a content authoring system written in Drupal, and this repository - an iOS app written in Swift. The content authoring CMS can be found here:

We included some SampleData with this repo, so you don't need the CMS in order to run the front-end. As long as the expected data format is followed, feel free to serve the files statically (cf. SampleData) or roll your own CMS.

Installation

Note: We recommend against downloading the ZIP of this project to avoid "Missing Reference" issues.

Once you have downloaded the repo, one way or another, open your terminal and change your directory to the top level of the project:

cd /path/to/aic-mobile-ios

Then, run the install.sh script. This script will fetch and build all of the required libraries for the app, create stub config files, and automatically launch Xcode when finished.

./install.sh

You should now be up-and-running in Xcode! The next steps will be to get started with some test data so you can run the app in the Simulator.

Serving SampleData

We've provided some sample data to test the iOS frontend and to demonstrate the data format expected by the app. In order to use this sample data, you must host the SampleData folder on localhost port 8888. You can use any webserver to do so, with some reservations. If for some reason, you cannot use port 8888, or if you'd like to use a virtual host instead, you can edit Config.plist to point the app elsewhere.

Note: the server you choose must be able to serve correct headers for audio files. We ran into issues with python -m SimpleHTTPServer during testing, where the audio duration was not being reported correctly, and the app would crash. This was most likely due to a missing Content-Length header. For this reason, we recommend taking the time to setup a marginally more advanced localhost than SimpleHTTPServer.

For the purposes of this documentation, we will use devinrhode2/pache. Pache is a script that allows you to quickly start Apache from the commandline and host a folder at a specified port. Given that Mac OS X has Apache built-in, minimal configuration is required. However, pache requires node.js and npm.

For readability, we assume that all config paths are relative to the /aic/aic subdirectory, unless otherwise noted. For instance, when we talk about Info.plist, we mean /aic/aic/Info.plist specifically.

You might note that Config.plist and GoogleService-Info.plist are not included with the repository. Running install.sh as part of the installation process will create these files for you. Any changes you make to these files will be ignored by Git.

We'll briefly go through these files and discuss the changes you may need to make.

Xcode Settings + Info.plist

In Xcode, open the settings for the aic build target (reference). In the General pane, change the Display Name and Bundle Identifier to match your institution. You may need to bump the Version and Build numbers as your development progresses.

Next, scroll down to App Icons and Launch Images and swap the images with whatever as you see fit. These can also be accessed via Assets.xcassets.

Go to the Info pane, expand URL Types (1), and change the Identifier and URL Schemes to match your institution. General best practice suggests that Identifier ought to match your Bundle Identifier, but it just has to be unique. Our URL-based functionality is still in development, but this is a good step to take as things progress.

Info.plist is a standard file expected by most iOS apps. For the most part, it stores the information you've edited via the settings GUI. Many settings live here, but the only one of interest is NSLocationWhenInUseageDesription, which contains text for the dialog that a user is prompted with when we request location services access. Other settings of potential interest include fonts, status bar styles, and supported device orientations.

Config.plist + Common.swift

Common.swift (located in /aic/aic/Shared) is the main config file for the mobile app. This is where data that is shared across UIViews is defined. Additionally, the Testing struct contains several flags that are helpful for debugging or generally understanding how data flows through the app at runtime.

Ideally, Common.swift should not be edited directly. Instead, you should edit Config.plist when possible. Values defined in Config.plist will override the values defined in Common.swift (see AppDataManager.swift).

For the purposes of this open-source effort, we extracted the most critical variables from Common.swift and put them into Config.plist, i.e. variables which are required for setting up the app to run using a source other than the included sample data. The intention behind this was to minimize downstream changes to Common.swift, so that you could more easily incorporate changes from upstream (us) in the future.

However, we realize that you may want to change some of the strings and settings in Common.swift directly. For the purposes of this documentation, we will focus on Config.plist, but we invite you to explore Common.swift and search through the source code (⌘ + Shift + F) to see how these variables are being used.

With this in mind, you will need to update appDataJSON with the full URL path of your hosted JSON file. If you are using an instance of our Drupal-based CMS, your path will likely look something like this:

http://example.com/sites/default/files/appData.json

Other settings maintained in Common.swift include text strings for various app sections and for the tooltip screens that display when the app is launched for the first time, anchor points and bounding boxes for the PDF-based map view overlay, departmental map icon flag names, department titles, etc. For now, these sorts of hard-coded values will have to be modified in the code, but we are open to PRs that would help make it possible to define these things via the CMS.

GoogleService-Info.plist

This file stores values that are needed to associate the app with your Google Analytics account. You'll want to update the following:

TRACKING_ID
BUNDLE_ID
PROJECT_ID
GOOGLE_APP_ID

Note that this file does not exist initially, and it is not tracked by git. Like Config.plist, it will be created when you run install.sh.

Map + Indoor Positioning

The map in this application provides users with accurate location information throughout the Art Institutes galleries. To make indoor user-positioning as accurate as possible, the Art Institute has partnered with Apple via the MapsConnect program. Through this program, we utilize Apple's Indoor Survey App to map the wireless signals throughout our buildings, creating a fingerprint of all of the areas of our venue.

These wireless fingerprints become a part of Apple's venue database and are utilized by the CoreLocation API to place the users "blue dot" on the map. By utilizing CoreLocation in combination with the on-site survey, we are able to take advantage of advanced location metrics such as current floor level to provide a better navigation experience to our app users on-site.

Map Overlay

To present a custom map overlay on top of Apple's default map, we use custom PDFs that contain a rough outline of our galleries as derived from the CAD drawings of our museum. This is the same map that we utilize for our printed guides that are available to our on-site visitors. You can find the PDFs used for each floor level under /SampleData. The URLs to these PDF floor plans are parsed from appData.json. The app downloads these PDFs and processes them for image tiling to optimize loading times at runtime.

Aligning the Map

As a part of the Maps Connect program, we work with Apple to survey our site using higher-detailed floor plans than what we display in the app. Apple processes these plans and converts them into their custom Apple Venue Format (AVF), which is compatible with GeoJSON. As GeoJSON has latitude and longitude coordinates embedded within it, we are able to use these files to derive anchor points for our PDF overlay to ensure that "blue dot" locations displayed via CoreLocation align as closely as possible with our PDF overlay. These achor coordinates are defined in the appData.json file in the map_floors json node. The app matches the anchor_pixel_1 and anchor_pixel_2 variables with the correspondent geolocations defined in anchor_location_1 and anchor_location_2.

Data

The application currently uses three main data sources:

App data from the Mobile CMS

Data Aggregator

Membership API

The first one is a JSON document.

The Data Aggregator is the Art Institute's main API that aggregates all different types of data used accross the museum's digital applications and websites.

For membership the app is querying a custom SOAP API. This data is meant to be managed through the companion CMS, but as long as the expected data format is followed, feel free to serve the files statically (cf. SampleData) or roll your own CMS.

App Data

The main app data is pulled from an external-facing server each time the application loads. This data includes galleries, objects (artworks), tours, audio files, map floors overlays, map annotations, and Data Aggregator API urls. We've included appData.json to demonstrate how "real" data would look like.

Here's a breakdown of the expected data format:

// Predominantly, we took the "Object of Objects" approach here. For context:// http://stackoverflow.com/questions/31469441/array-of-objects-vs-object-of-objects// For "galleries", "objects", and "audio_tours", each entity's "nid"// should match its parent property (or more accurately, vice versa)
{
// These show up as small text on the map at higher zoom levels"galleries": {
"1052": {
"nid":1052,
"title":"Allerton Building",
// Galleries are referenced by gallery_id// Objects and exhibits that refer to invalid galleries will be hidden"gallery_id":"2147483642"// If true, objects from this gallery will be hidden"closed":false,
// Latitude, longitude, separated with comma and optional space"location":"41.879565,-87.623865",
// For the Art Institute of Chicago, floor is either a number or "LL" for lower-level// See AppDataParser.swift#L167"floor":1
}
},
"objects": {
"1036": {
"nid":1036,
"location":"41.87964734443971, -87.62376828224376",
// Should match one of the "galleries" by name"gallery_location":"Allerton Building",
"title":"Artwork Title",
// Shown when the individual object is opened"large_image_full_path":"http://localhost:8888/placeholder.png",
// Shown as circular thumbnail on the map"thumbnail_full_path":"http://localhost:8888/placeholder.png",
// An audio commentary is a pair of selector number (to enter on the Audio Guide key pad) and the correspondent audio file.// The audio id should match one of the audio_files"audio_commentary": [
{
object_selector_number:"101",
audio:"1027"
}
]
}
},
"audio_files": {
"1027": {
"nid":1027,
"title":"Introduction to Tour",
"audio_file_url":"http://localhost:8888/unfa.mp3",
"audio_transcript":"This is an introduction.",
// Translations is an array of nodes, each one containing all the content translated in a language that is not English."translations": [
{
"language":"es",
"title":"Spanish Title",
"audio_file_url":"http://localhost:8888/unfa.mp3",
"audio_transcript":"Spanish transcript"
}
]
}
},
// Note that "tours" is an Array of Objects, not Object of Objects.// This inconsistency is kept for historical compatibility reasons."tours": [
{
"nid":1023,
"title":"Lorem Ipsum",
"image_url":"http://example.com/link-to-banner-image.png",
"description":"Short blurb description",
"intro":"Transcript of tour_audio file",
// Should match one of the "audio_files""tour_audio":1027,
// Location where the tour starts"location":"41.87954599481745, -87.62390507490352",
// Translations is an array of nodes, each one containing all the content translated in a language that is not English."translations": [
{
"language":"es",
"title":"Spanish Title",
"description":"Spanish description",
"intro":"Spanish intro"
}
],
// "tour_stops" is also an Array of Objects!"tour_stops": [
{
// Does not have to be consecutive or integer"sort":0,
// Should match one of the "objects""object":1036,
// Should match one of the "audio_files""audio_id":1027,
// An Audio Bumper plays at the end of a stop to provide directions to the next stop in the tour// The audio bumper for this stop plays at the end of the previous stop// Should match one of the "audio_files""audio_bumper":1027
}
]
}
]
}

For more details, see AppDataParser.swift. It's a good file to reference when you want to clarify which fields are needed. We recommend doing a project-wide search for the affected model attributes in order to find out how your data is being used.

Data Aggregator

The Data Aggregator is the Art Institute's main API that aggregates all different types of data used accross the museum's digital applications and websites. The API is an open source project and its repository can be found here:

From the appData.json file the iOS app retreives the URL of the Data Aggregator (data_api_url) and the API endpoints used throughout the app for retreiving the latest events and exhibitions as well as performing searches for tours, artworks and exhibitions.

Depending on the specific data needs, the app performs GET or POST methods to obtain that data. These methods are using ElasticSearch syntax and POST requests are performed by sending query parameters defined in a dictionary and encoded in JSON format.

Current Exhibitions Request

The iOS app is sending POST requests to the Data Aggregator to get a list of exhibitions currently open at the museum.

Current Events Request

Search Request

All the search requests to the Data Aggregator are performed using the multisearch_endpoint of the API.
Another endpoint that the app uses is the autocomplete_endpoint which is returning autocomplete strings to provide search suggestions.

The multi-search is a request that contains 3 different queries, each one for a different content type: artworks, tours, exhibitions.

The Data Aggregator API returns data for each content type in a single JSON file, with 3 separate arrays of results, one per content type. An example of multi-search response can be found in SampleData/search/msearch.json

Note: The appData.json provided in the SampleData has the exhibitions_endpoint, events_endpoint and multisearch_endpoint pointing directly to the sample json files. These should not point to static files, but to API endpoints on a server, which is supposed to process the request and return data for your institution.

Member Card API

The member card information is validated through a simple SOAP API that exists on the Art Institute of Chicago's server. Given a member's ID number and ZIP code, this API attempts to validate the user and returns their information if successful.

Our membership system is based on Gateway's Galaxy Connect. You will likely have to substantially modify the membership components of this app to suit your institution. This functionality is very specific to the Art Institute.

External Libs

The application relies on a few external libs, all of which are built using Cocoapods.

Build Targets

There are two main build schemes/targets for the application, aic and aicRental.

aic is the main application and should be used for all app store and TestFlight builds.

aicRental is used for internal enterprise builds by the Art Institute for our in-museum rental devices. This target disables member card functionality and resets every morning to ensure that applications are refreshed on a daily basis. These builds are used with JAMF Casper, the museum's MDM provider.

Contributing

We encourage your contributions. Please fork this repository and make your changes in a separate branch. We like to use git-flow to make this process easier, but it is not required.