Swift Other
Switch branches/tags
Nothing to show
Clone or download
Latest commit a54c30b Aug 13, 2018
Permalink
Failed to load latest commit information.
Documentation version2.0 Apr 17, 2018
SampleData version2.0 Apr 17, 2018
aic updating fonts Aug 13, 2018
.gitignore updates for xcode 9, libraries and updated map Oct 26, 2017
.gitmodules Initial Commit Mar 31, 2017
CODE_OF_CONDUCT.md Initial Commit Mar 31, 2017
LICENSE Initial Commit Mar 31, 2017
README.md version2.0 Apr 17, 2018
install.sh version2.0 Apr 17, 2018

README.md

Art Institute of Chicago

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.

Table of Contents

  1. Features
    1. Home
    2. Audio Guide
    3. Map
    4. Tours
    5. Search
    6. Information
  2. Getting Started
    1. Prerequisites
    2. Installation
    3. Serving SampleData
  3. Configuration
    1. Xcode Settings + Info.plist
    2. Config.plist + Common.swift
    3. GoogleService-Info.plist
  4. Map + Indoor Positioning
    1. Map Overlay
    2. Aligning the Map
  5. Data
    1. App Data
    2. Data Aggregator
      1. Current Exhibitions Request
      2. Current Events Request
      3. Search Request
    3. Member Card API
  6. External Libs
  7. Analytics
  8. Build Targets
  9. Credits

Features

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

Home Image

Home

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

Audio Guide Image

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 Image

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 Image

Tours

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

Search Image

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 Image

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:

https://github.com/art-institute-of-chicago/aic-mobile-cms

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.

Prerequisites

  1. Mac OS X (tested with Sierra)
  2. Xcode 9+ (Swift 4 support)
  3. CocoaPods

Throughout this guide, we assume that you know the basics of using Git and Terminal, or another terminal emulator of your choice.

Installation

Clone this repo to your computer:

git clone https://github.com/art-institute-of-chicago/aic-mobile-ios.git

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.

cd /path/to/aic-mobile-ios/SampleData
pache . 8888

http://localhost:8888/appData.json should be pointing at appData.json

Regardless of how you chose to host the sample data, you should now be ready to run the app in the Simulator!

Go to Xcode, choose an iOS simulator (e.g. iPhone SE) for the aic build target, and run the build.

References:

Configuration

This section is meant to get you started with modifying the app for your own museum. There are several configs you may need to edit to suit your environment:

  1. Xcode aic target settings (cf. Info.plist)
  2. Config.plist, which overrides values initialized in /shared/Common.swift
  3. GoogleService-Info.plist

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.

See the Twitter Dev Docs for more info on URL Schemes.

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.

Here are the variables set through Config.plist:

Variable Default Description
printDataErrors true

Show data parsing errors in Xcode's console.

appDataJSON http://localhost:8888/appData.json

Gallery, object, audio, and tour data. See App Data.

memberCardSOAPRequestURL http://localhost:8888/api/1?token=foobar

Your membership system's SOAP API endpoint. See Member Card API.

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.

See the Analytics section for more info.

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:

https://github.com/art-institute-of-chicago/data-aggregator

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.

"data": {
	// Data Aggregator API URL
	"data_api_url": "http://localhost:8888",
	
	// Data Aggregator API endpoints
	"exhibitions_endpoint": "/search/exhibitions.json",
	"artworks_endpoint": "/artworks",
	"galleries_endpoint": "/galleries",
	"images_endpoint": "/images",
	"events_endpoint": "/search/events.json",
	"autocomplete_endpoint": "/autocomplete",
	"tours_endpoint": "/tours",
	"multisearch_endpoint": "/search/msearch.json",
	
	// URL to the museum web page where you can become a member
	"membership_url": "http://localhost:8888",
	
	// URL to the museum website
	"website_url": "http://localhost:8888",
	
	// URL to the museum web page where you can buy tickets
	"tickets_url": "http://localhost:8888",
	
	// URL to download the museum artworks images
	"image_server_url": "http://localhost:8888"
}

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.

Loading current exhibitions

In order to demonstrate how the Data Aggregator API returns data for the latest exhibitions and events, we included a JSON file as an example of API response in SampleData/search/exhibitions.json

Current Events Request

The Data Aggregator is also queried to get a list of events for the next 2 weeks.

Loading current events

Here's an example of API response for current events: SampleData/search/events.json

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.

For more details about all the requests used for the search functionality, see SearchDataManager.swift.

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.

  • PureLayout is used for handling the layout and managing views.
  • SwiftyJSON is used for parsing the JSON data.
  • SWXMLHash is used for parsing the SOAP response from the Member Card API
  • AlamoFire is used for all networking requests of data, assets, SOAP, etc.
  • Kingfisher is used for asynchronously load and cache images retreived from the different APIs.
  • Localize-Swift is used for the localization of content in English, Spanish and Chinese
  • Atributika is used for rendering HTML tags into iOS's attributed strings (rich text)
  • GoogleAnalytics provides data analytics on general app usage.

Analytics

The app uses Google Analytics, installed using CocoaPods.

After running install.sh, you will need to configure GoogleService-Info.plist to enable analytics.

See AICAnalytics.swift for details regarding which events are tracked.

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.

# Clone the repo to your computer
git clone https://github.com/your-github-account/aic-mobile-ios.git

# Enter the folder that was created by the clone
cd aic-mobile-ios

# Run the install script
chmod a+rx install.sh
./install.sh

# Start a feature branch
git flow start feature yourinitials-good-description-issuenumberifapplicable

# ... make some changes, commit your code

# Push your branch to GitHub
git push origin yourinitials-good-description-issuenumberifapplicable

Then on github.com, create a Pull Request to merge your changes into our develop branch. We promise to review it in a timely manner.

We also welcome bug reports and questions under GitHub's Issues.

By participating in this project, you agree to abide by its Code of Conduct. Be nice, and write good code!

Licensing

The code in this project is licensed under the AGNU Affero General Public License Version 3.

Acknowledgments

Supported by Bloomberg Philanthropies

Design and development by Potion

Additional development by Josh Biillons at MBLabs