summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin R. Miller <incanus@codesorcery.net>2015-02-09 18:01:40 -0800
committerJustin R. Miller <incanus@codesorcery.net>2015-02-09 18:01:40 -0800
commitd4d4cd44151423d374e373798f09d12157babc27 (patch)
treeede100edc58475b969b62fba8f3bcb219b29beda
parent0ee6044ccf49bb32a25c57ce25af15bcd58ca77b (diff)
downloadqtlocation-mapboxgl-d4d4cd44151423d374e373798f09d12157babc27.tar.gz
move iOS code from gl-cocoa to this project
-rw-r--r--.gitignore1
-rw-r--r--.gitmodules10
-rw-r--r--.travis.yml16
-rw-r--r--Makefile19
-rw-r--r--README.md12
-rw-r--r--gyp/install.gypi82
-rw-r--r--gyp/platform-ios.gypi23
-rw-r--r--gyp/platform-osx.gypi1
-rw-r--r--include/mbgl/ios/MGLMapView.h203
-rw-r--r--include/mbgl/ios/MGLStyleFunctionValue.h23
-rw-r--r--include/mbgl/ios/MGLTypes.h17
-rw-r--r--include/mbgl/ios/NSArray+MGLAdditions.h7
-rw-r--r--include/mbgl/ios/NSDictionary+MGLAdditions.h7
-rw-r--r--include/mbgl/ios/UIColor+MGLAdditions.h11
-rw-r--r--ios/FAQ.md49
-rw-r--r--ios/MapboxGL.podspec33
-rw-r--r--ios/README.md68
-rw-r--r--ios/app/MBXAppDelegate.h7
-rw-r--r--ios/app/MBXAppDelegate.m15
-rw-r--r--ios/app/MBXViewController.h5
-rw-r--r--ios/app/MBXViewController.mm266
-rw-r--r--ios/app/README.md3
-rw-r--r--ios/app/app-info.plist51
-rw-r--r--ios/app/img/Default-568h@2x.pngbin0 -> 2239 bytes
-rw-r--r--ios/app/img/Default-667h@2x.pngbin0 -> 2797 bytes
-rw-r--r--ios/app/img/Icon-40.pngbin0 -> 951 bytes
-rw-r--r--ios/app/img/Icon-40@2x.pngbin0 -> 1528 bytes
-rw-r--r--ios/app/img/Icon-60.pngbin0 -> 1313 bytes
-rw-r--r--ios/app/img/Icon-60@2x.pngbin0 -> 2091 bytes
-rw-r--r--ios/app/img/Icon-72.pngbin0 -> 1418 bytes
-rw-r--r--ios/app/img/Icon-72@2x.pngbin0 -> 2442 bytes
-rw-r--r--ios/app/img/Icon-76.pngbin0 -> 1447 bytes
-rw-r--r--ios/app/img/Icon-76@2x.pngbin0 -> 2520 bytes
-rw-r--r--ios/app/img/Icon-Small-50.pngbin0 -> 1118 bytes
-rw-r--r--ios/app/img/Icon-Small-50@2x.pngbin0 -> 1839 bytes
-rw-r--r--ios/app/img/Icon-Small.pngbin0 -> 684 bytes
-rw-r--r--ios/app/img/Icon-Small@2x.pngbin0 -> 1231 bytes
-rw-r--r--ios/app/img/Icon-Spotlight-40.pngbin0 -> 951 bytes
-rw-r--r--ios/app/img/Icon-Spotlight-40@2x.pngbin0 -> 1528 bytes
-rw-r--r--ios/app/img/Icon.pngbin0 -> 1171 bytes
-rw-r--r--ios/app/img/Icon@2x.pngbin0 -> 1931 bytes
-rw-r--r--ios/app/img/iTunesArtworkbin0 -> 264721 bytes
-rw-r--r--ios/app/img/iTunesArtwork.pngbin0 -> 5655 bytes
-rw-r--r--ios/app/img/iTunesArtwork@2xbin0 -> 661346 bytes
-rw-r--r--ios/app/img/iTunesArtwork@2x.pngbin0 -> 9293 bytes
-rw-r--r--ios/app/img/locateUser.pngbin0 -> 1111 bytes
-rw-r--r--ios/app/img/locateUser@2x.pngbin0 -> 1696 bytes
-rw-r--r--ios/app/img/settings.pngbin0 -> 528 bytes
-rw-r--r--ios/app/img/settings@2x.pngbin0 -> 1130 bytes
-rw-r--r--ios/app/main.m10
-rw-r--r--ios/app/mapboxgl-app.gyp57
-rwxr-xr-xios/docs/install_docs.sh24
-rwxr-xr-xios/docs/remove_docs.sh7
m---------ios/mapbox-gl-cocoa0
-rw-r--r--ios/screenshot.pngbin0 -> 248491 bytes
-rw-r--r--mbgl.gyp15
-rw-r--r--platform/darwin/log_nslog.mm2
-rw-r--r--platform/darwin/string_nsstring.mm2
-rw-r--r--platform/ios/MGLMapView.mm1607
-rw-r--r--platform/ios/MGLStyleFunctionValue.m190
-rw-r--r--platform/ios/MGLTypes.m24
-rw-r--r--platform/ios/NSArray+MGLAdditions.m10
-rw-r--r--platform/ios/NSDictionary+MGLAdditions.m10
-rw-r--r--platform/ios/UIColor+MGLAdditions.m167
-rw-r--r--platform/ios/resources/Compass.pngbin0 -> 1736 bytes
-rw-r--r--platform/ios/resources/Compass@2x.pngbin0 -> 2376 bytes
-rw-r--r--platform/ios/resources/mapbox.pngbin0 -> 5947 bytes
-rw-r--r--platform/ios/resources/mapbox@2x.pngbin0 -> 10713 bytes
-rwxr-xr-xscripts/ios_travis/add-key.sh25
-rw-r--r--scripts/ios_travis/apple.crtbin0 -> 1063 bytes
-rw-r--r--scripts/ios_travis/ios-dist.cer.enc30
-rw-r--r--scripts/ios_travis/ios-dist.p12.enc32
-rw-r--r--scripts/ios_travis/ios-in-house.mobileprovision.enc153
-rwxr-xr-xscripts/ios_travis/remove-key.sh4
-rwxr-xr-xscripts/package_ios.sh75
-rwxr-xr-xscripts/travis_before_install.sh3
-rwxr-xr-xscripts/travis_script.sh7
-rw-r--r--test/ios/.gitignore3
-rw-r--r--test/ios/App-Info.plist45
-rw-r--r--test/ios/Bundle-Info.plist22
m---------test/ios/KIF0
-rw-r--r--test/ios/KIFTestActor+MapboxGL.h12
-rw-r--r--test/ios/KIFTestActor+MapboxGL.m24
-rw-r--r--test/ios/MGLTAppDelegate.h7
-rw-r--r--test/ios/MGLTAppDelegate.m18
-rw-r--r--test/ios/MGLTViewController.h5
-rw-r--r--test/ios/MGLTViewController.m18
-rw-r--r--test/ios/MapViewTests.h5
-rw-r--r--test/ios/MapViewTests.m353
-rw-r--r--test/ios/README.md9
-rw-r--r--test/ios/ios-tests.xcodeproj/project.pbxproj659
-rw-r--r--test/ios/ios-tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata7
-rw-r--r--test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/Mapbox GL Tests.xccheckout41
-rw-r--r--test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout65
-rw-r--r--test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme110
-rw-r--r--test/ios/main.m9
96 files changed, 4716 insertions, 79 deletions
diff --git a/.gitignore b/.gitignore
index 4a61e999eb..3ee0136c6c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/build
/macosx/build
/linux/build
+/ios/build
/test/build
/test/node_modules
/include/mbgl/shader/shaders.hpp
diff --git a/.gitmodules b/.gitmodules
index 8242fab9b2..19de5b6f78 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,7 +1,3 @@
-[submodule "ios/mapbox-gl-cocoa"]
- path = ios/mapbox-gl-cocoa
- url = https://github.com/mapbox/mapbox-gl-cocoa.git
-
[submodule "test/suite"]
path = test/suite
url = https://github.com/mapbox/mapbox-gl-test-suite.git
@@ -14,6 +10,6 @@
path = styles
url = https://github.com/mapbox/mapbox-gl-styles.git
-[submodule "app/ios"]
- path = app/ios
- url = https://github.com/mapbox/mapbox-gl-cocoa.git \ No newline at end of file
+[submodule "test/ios/KIF"]
+ path = test/ios/KIF
+ url = https://github.com/mapbox/KIF.git
diff --git a/.travis.yml b/.travis.yml
index e42f9fde7b..ff578458cc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -47,6 +47,13 @@ env:
- secure: "CHBiUM60TolDbQnn+4IRA/tvOKwKs3g9EDvv8YHSJMg3FuHmjKQkprBasvxf3hnTXg4WLZEubmeDcyJ6RRzPP5mMSr/hksYl0pSjj/6TUecE5fHPVVeN7txVqkpOBf9i45Y+iBUQMjBb1NnDK3pHXxpnAs1Q/pe7vReErj4GF1U="
- LD_LIBRARY_PATH: '/usr/local/lib'
- TERM: dumb
+ # begin iOS code signing
+ - secure: "I6Iu75X1E+js5tzijtKi1EGtIuBcA4/25nDYe0svV4HAtujY71ZJZ4eB6355CKhFXpLXrF3i7eKVX3v+zWS0QROPEWacgsqsvNg+Ba9cnznW/faUSOYekCfhzWd/6reYDM7KzKAQwSUHLk9JIWK/kkmi4r+vVJK7h+tjPllK5YA="
+ - IOS_APP_NAME="Mapbox GL"
+ - 'IOS_DEVELOPER_NAME="iPhone Distribution: Mapbox, Inc."'
+ - IOS_PROFILE_NAME="ios-in-house"
+ - secure: "nQqSM8rd7OHtV4MqmNqVnkrVHqxKqQsaWRYk4/nPdhbeVWtTtkk0df711LrF1TUtbEPEewHxYUvTZ/UXmwJNeoKdzTHavI8hnatRkgjyxGERPn1il1Otelht9I+LQQHf+plrpRjVWBrNIW0Zox1B3cqn6d3NglpbXrEQ2EjYGNA="
+ # end iOS code signing
before_install:
- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then sudo service mysql stop; fi
@@ -65,10 +72,19 @@ install:
before_script:
# Set the core file limit to unlimited so a core file is generated upon crash
- ulimit -c unlimited -S
+ # begin iOS code signing
+ - openssl aes-256-cbc -k "$IOS_ENCRYPTION_SECRET" -in scripts/ios_travis/ios-in-house.mobileprovision.enc -d -a -out scripts/ios_travis/ios-in-house.mobileprovision
+ - openssl aes-256-cbc -k "$IOS_ENCRYPTION_SECRET" -in scripts/ios_travis/ios-dist.cer.enc -d -a -out scripts/ios_travis/ios-dist.cer
+ - openssl aes-256-cbc -k "$IOS_ENCRYPTION_SECRET" -in scripts/ios_travis/ios-dist.p12.enc -d -a -out scripts/ios_travis/ios-dist.p12
+ - ./scripts/ios_travis/add-key.sh
+ # end iOS code signing
script:
- ./scripts/travis_script.sh
+after_script:
+- ./scripts/ios_travis/remove-key.sh
+
notifications:
hipchat:
rooms:
diff --git a/Makefile b/Makefile
index d26aa2fbb7..ed7c234228 100644
--- a/Makefile
+++ b/Makefile
@@ -23,7 +23,7 @@ config/%.gypi: configure
#### Library builds ############################################################
-.PRECOIUS: Makefile/mbgl
+.PRECIOUS: Makefile/mbgl
Makefile/mbgl: config/$(HOST).gypi
deps/run_gyp mbgl.gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f make
@@ -36,6 +36,9 @@ standalone: Makefile/mbgl
install: Makefile/mbgl
LINK=`pwd`/gyp/link.py $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) install
+.PRECIOUS: Xcode/mbgl
+Xcode/mbgl: config/$(HOST).gypi
+ deps/run_gyp mbgl.gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f xcode
##### Test builds ##############################################################
@@ -101,15 +104,18 @@ xproj: xosx-proj
#### iOS application builds ####################################################
.PRECIOUS: Xcode/ios
-Xcode/ios: ios/mapbox-gl-cocoa/app/mapboxgl-app.gyp config/ios.gypi
- deps/run_gyp ios/mapbox-gl-cocoa/app/mapboxgl-app.gyp $(CONFIG_ios) $(LIBS_ios) --generator-output=./build/ios -f xcode
+Xcode/ios: ios/app/mapboxgl-app.gyp config/ios.gypi
+ deps/run_gyp ios/app/mapboxgl-app.gyp $(CONFIG_ios) $(LIBS_ios) --generator-output=./build/ios -f xcode
.PHONY: ios-proj ios run-ios
ios-proj: Xcode/ios
- open ./build/ios/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj
+ open ./build/ios/ios/app/mapboxgl-app.xcodeproj
ios: Xcode/ios
- xcodebuild -sdk iphonesimulator ARCHS=x86_64 -project ./build/ios/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj -configuration $(BUILDTYPE) -target iosapp -jobs `sysctl -n hw.ncpu`
+ xcodebuild -sdk iphoneos ARCHS="arm64 armv7 armv7s" PROVISIONING_PROFILE="2b532944-bf3d-4bf4-aa6c-a81676984ae8" -project ./build/ios/ios/app/mapboxgl-app.xcodeproj -configuration Release -target iosapp -jobs `sysctl -n hw.ncpu`
+
+isim: Xcode/ios
+ xcodebuild -sdk iphonesimulator ARCHS="x86_64 i386" -project ./build/ios/ios/app/mapboxgl-app.xcodeproj -configuration Debug -target iosapp -jobs `sysctl -n hw.ncpu`
# Legacy name
iproj: ios-proj
@@ -197,8 +203,7 @@ clean: clear_sqlite_cache clear_xcode_cache
-rm -rf ./build/
-rm -rf ./macosx/build
-rm -rf ./linux/build
- -rm -rf ./ios/mapbox-gl-cocoa/build
- -rm -rf ./ios/mapbox-gl-cocoa/app/build
+ -rm -rf ./ios/build
-rm -rf ./test/build
-rm -rf ./config/*.gypi
-rm -rf ./android/java/build ./android/java/app/build ./android/java/lib/build
diff --git a/README.md b/README.md
index 6c63c02fbe..94c8d7c4f4 100644
--- a/README.md
+++ b/README.md
@@ -45,17 +45,15 @@ Target OS: 10.9+
## iOS
-iOS makes use of a Cocoa-specific API called [`mapbox-gl-cocoa`](https://github.com/mapbox/mapbox-gl-cocoa). If you are just interested in running Mapbox GL on iOS and not developing with it, head to that project and you can use this library as a pre-built static library instead. A `UIView` interface to the map view and bundle resources are provided there.
+If you just want to install the library for iOS and try it out as an Objective-C consumer, run `./scripts/package_ios.sh`. It will require the Boost headers to be installed, so use [Homebrew](http://brew.sh/) to install it via `brew install boost`. The script will produce the statically-linked `libMapboxGL.a`, `MapboxGL.bundle` for resources, and a `Headers` folder.
-If you intend to develop here, `mapbox-gl-cocoa` is included as a submodule of the overall build setup.
+If you want to build from source and/or contribute to development of the project, you can run `make iproj`, which will create and open an Xcode project which can build the entire library from source as well as an Objective-C test app.
-You can run `make iproj` to create and open an Xcode project with an iOS-specific view controller housing. This will automatically install required dependencies as well.
+Target devices: iPhone 4S and above (5, 5c, 5s, 6, 6 Plus) and iPad 2 and above (3, 4, Mini, Air, Mini 2, Air 2).
-Note that if you are doing OS X development as well, to toggle from OS X back to iOS, you will need to `make iproj` again.
+iOS SDK: 8.1
+Supported iOS: 7.0+
-Target devices: iPhone 4S and above (5, 5c, 5s, 6, 6 Plus) and iPad 2 and above (3, 4, Mini , Air, Mini 2, Air 2).
-
-Target OS: 7.0+
## Linux
diff --git a/gyp/install.gypi b/gyp/install.gypi
index 4ed32eea14..01857aa562 100644
--- a/gyp/install.gypi
+++ b/gyp/install.gypi
@@ -1,50 +1,46 @@
{
- 'conditions': [
- ['install_prefix != ""', {
- 'targets': [
- { 'target_name': 'install',
- 'type': 'none',
- 'hard_dependency': 1,
- 'dependencies': [
- 'core',
- 'platform-<(platform_lib)',
- 'http-<(http_lib)',
- 'asset-<(asset_lib)',
- 'cache-<(cache_lib)',
- 'headless-<(headless_lib)',
- 'standalone',
- ],
+ 'targets': [
+ { 'target_name': 'install2',
+ 'type': 'none',
+ 'hard_dependency': 1,
+ 'dependencies': [
+ 'core',
+ 'platform-<(platform_lib)',
+ 'http-<(http_lib)',
+ 'asset-<(asset_lib)',
+ 'cache-<(cache_lib)',
+ 'headless-<(headless_lib)',
+ 'standalone',
+ ],
- 'copies': [
- { 'files': [ '<(PRODUCT_DIR)/libmbgl.a' ], 'destination': '<(install_prefix)/lib' },
- { 'files': [ '<(SHARED_INTERMEDIATE_DIR)/include/mbgl/util/version.hpp' ], 'destination': '<(install_prefix)/include/mbgl/util' },
- ],
+ 'copies': [
+ { 'files': [ '<(PRODUCT_DIR)/libmbgl.a' ], 'destination': '<(install_prefix)/lib' },
+ { 'files': [ '<(SHARED_INTERMEDIATE_DIR)/include/mbgl/util/version.hpp' ], 'destination': '<(install_prefix)/include/mbgl/util' },
+ ],
- 'actions': [
- {
- 'action_name': 'Copy header files',
- 'inputs': [ '../include/mbgl/mbgl.hpp' ],
- 'outputs': [ '../include/mbgl/mbgl.hpp' ],
- 'action': [ 'cp', '-r', 'include', '<(install_prefix)/' ]
- },
+ 'actions': [
+ {
+ 'action_name': 'Copy header files',
+ 'inputs': [ '../include/mbgl/mbgl.hpp' ],
+ 'outputs': [ '<(install_prefix)/include/mbgl/mbgl.hpp' ],
+ 'action': [ 'cp', '-r', 'include', '<(install_prefix)/' ]
+ },
- { 'action_name': 'mbgl-config',
- 'inputs': [
- '../utils/mbgl-config/mbgl-config.template.sh',
- '../utils/mbgl-config/build.sh',
- ],
- 'outputs': [
- '<(install_prefix)/bin/mbgl-config',
- ],
- 'action': [
- './utils/mbgl-config/build.sh',
- '<(install_prefix)',
- '<(PRODUCT_DIR)/libmbgl.a.ldflags',
- ]
- }
+ { 'action_name': 'mbgl-config',
+ 'inputs': [
+ '../utils/mbgl-config/mbgl-config.template.sh',
+ '../utils/mbgl-config/build.sh',
+ ],
+ 'outputs': [
+ '<(install_prefix)/bin/mbgl-config',
+ ],
+ 'action': [
+ './utils/mbgl-config/build.sh',
+ '<(install_prefix)',
+ '<(PRODUCT_DIR)/libmbgl.a.ldflags',
]
- },
- ],
- }],
+ }
+ ]
+ },
],
}
diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi
index bb1b450728..a8a827af65 100644
--- a/gyp/platform-ios.gypi
+++ b/gyp/platform-ios.gypi
@@ -15,6 +15,19 @@
'../platform/darwin/application_root.mm',
'../platform/darwin/asset_root.mm',
'../platform/darwin/image.mm',
+ '../platform/darwin/reachability.m',
+ '../include/mbgl/ios/MGLMapView.h',
+ '../platform/ios/MGLMapView.mm',
+ '../include/mbgl/ios/MGLStyleFunctionValue.h',
+ '../platform/ios/MGLStyleFunctionValue.m',
+ '../include/mbgl/ios/MGLTypes.h',
+ '../platform/ios/MGLTypes.m',
+ '../include/mbgl/ios/NSArray+MGLAdditions.h',
+ '../platform/ios/NSArray+MGLAdditions.m',
+ '../include/mbgl/ios/NSDictionary+MGLAdditions.h',
+ '../platform/ios/NSDictionary+MGLAdditions.m',
+ '../include/mbgl/ios/UIColor+MGLAdditions.h',
+ '../platform/ios/UIColor+MGLAdditions.m',
],
'variables': {
@@ -26,8 +39,14 @@
'<@(uv_static_libs)',
],
'ldflags': [
+ '-framework CoreGraphics',
+ '-framework CoreLocation',
'-framework ImageIO',
+ '-framework GLKit',
'-framework MobileCoreServices',
+ '-framework OpenGLES',
+ '-framework SystemConfiguration',
+ '-framework UIKit',
],
},
@@ -37,6 +56,7 @@
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ 'CLANG_ENABLE_OBJC_ARC': 'YES',
},
'link_settings': {
@@ -50,6 +70,9 @@
'include_dirs': [
'../include',
],
+ 'mac_bundle_resources': [
+ '<!@(find ./platform/ios/resources -type f)',
+ ],
},
},
],
diff --git a/gyp/platform-osx.gypi b/gyp/platform-osx.gypi
index e3fe7b0d53..011f26e836 100644
--- a/gyp/platform-osx.gypi
+++ b/gyp/platform-osx.gypi
@@ -39,6 +39,7 @@
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ 'CLANG_ENABLE_OBJC_ARC': 'YES',
},
'link_settings': {
diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h
new file mode 100644
index 0000000000..6535138d3b
--- /dev/null
+++ b/include/mbgl/ios/MGLMapView.h
@@ -0,0 +1,203 @@
+#import <UIKit/UIKit.h>
+#import <CoreLocation/CoreLocation.h>
+
+@protocol MGLMapViewDelegate;
+
+/** An MGLMapView object provides an embeddable map interface, similar to the one provided by Apple's MapKit. You use this class to display map information and to manipulate the map contents from your application. You can center the map on a given coordinate, specify the size of the area you want to display, and style the features of the map to fit your application's use case.
+*
+* Use of MGLMapView requires a Mapbox API access token. Obtain an access token on the [Mapbox account page](https://www.mapbox.com/account/apps/). If you instantiate an MGLMapView from Interface Builder, rendering of the map won't begin until the accessToken property has been set.
+*
+* @warning Please note that you are responsible for getting permission to use the map data, and for ensuring your use adheres to the relevant terms of use. */
+@interface MGLMapView : UIView
+
+#pragma mark - Initializing a Map View
+
+/** @name Initializing a Map View */
+
+/** Initialize a map view with a given frame, style, and access token.
+* @param frame The frame with which to initialize the map view.
+* @param styleJSON The map stylesheet as JSON text.
+* @param accessToken A Mapbox API access token.
+* @return An initialized map view, or `nil` if the map view was unable to be initialized. */
+- (instancetype)initWithFrame:(CGRect)frame styleJSON:(NSString *)styleJSON accessToken:(NSString *)accessToken;
+
+/** Initialize a map view with a given frame, the default style, and an access token.
+* @param frame The frame with which to initialize the map view.
+* @param accessToken A Mapbox API access token.
+* @return An initialized map view, or `nil` if the map view was unable to be initialized. */
+- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken;
+
+- (instancetype)initWithFrame:(CGRect)frame __attribute__((unavailable("Instantiating an MGLMapView requires setting a style and/or an access token.")));
+
+#pragma mark - Authorizing Access
+
+/** @name Authorizing Access */
+
+/** Sets a Mapbox API access token for the map view.
+* @param accessToken A Mapbox API token. */
+- (void)setAccessToken:(NSString *)accessToken;
+
+#pragma mark - Managing Constraints
+
+/** @name Managing Constraints */
+
+/** A view controller whose top and bottom layout guides to use for proper setup of constraints in the map view internals.
+*
+* Certain components of the map view, such as the heading compass and the data attribution button, need to be aware of the view controller layout in order to avoid positioning content under a top navigation bar or a bottom toolbar. */
+@property (nonatomic, weak) UIViewController *viewControllerForLayoutGuides;
+
+#pragma mark - Accessing Map Properties
+
+/** @name Accessing Map Properties */
+
+/** A Boolean value that determines whether the user may zoom the map.
+*
+* This property controls only user interactions with the map. If you set the value of this property to `NO`, you may still change the map zoom programmatically.
+*
+* The default value of this property is `YES`. */
+@property(nonatomic, getter=isZoomEnabled) BOOL zoomEnabled;
+
+/** A Boolean value that determines whether the user may scroll around the map.
+*
+* This property controls only user interactions with the map. If you set the value of this property to `NO`, you may still change the map location programmatically.
+*
+* The default value of this property is `YES`. */
+@property(nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
+
+/** A Boolean value that determines whether the user may rotate the map.
+*
+* This property controls only user interactions with the map. If you set the value of this property to `NO`, you may still rotate the map programmatically.
+*
+* The default value of this property is `YES`. */
+@property(nonatomic, getter=isRotateEnabled) BOOL rotateEnabled;
+
+#pragma mark - Accessing the Delegate
+
+/** @name Accessing the Delegate */
+
+// TODO
+@property(nonatomic, weak) id<MGLMapViewDelegate> delegate;
+
+#pragma mark - Manipulating the Visible Portion of the Map
+
+/** @name Manipulating the Visible Portion of the Map */
+
+/** The map coordinate at the center of the map view.
+*
+* Changing the value in this property centers the map on the new coordinate without changing the current zoom level.
+*
+* Changing the value of this property updates the map view immediately. If you want to animate the change, use the setCenterCoordinate:animated: method instead. */
+@property (nonatomic) CLLocationCoordinate2D centerCoordinate;
+
+/** Changes the center coordinate of the map and optionally animates the change.
+* @param coordinate The new center coordinate for the map.
+* @param animated Specify `YES` if you want the map view to scroll to the new location or `NO` if you want the map to display the new location immediately.
+*
+* Changing the center coordinate centers the map on the new coordinate without changing the current zoom level. */
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated;
+
+/** The zoom level of the map view.
+*
+* Changing the value in this property zooms the map in or out without changing the center coordinate. At zoom level 0, tiles cover the entire world map; at zoom level 1, tiles cover 1/4 of the world; at zoom level 2, tiles cover 1/16 of the world, and so on.
+*
+* Changing the value of this property updates the map view immediately. If you want to animate the change, use the setZoomLevel:animated: method instead. */
+@property (nonatomic) double zoomLevel;
+
+/** Changes the zoom level of the map and optionally animates the change.
+* @param zoomLevel The new zoom level for the map.
+* @param animated Specify `YES` if you want the map view to animate the change to the new zoom level or `NO` if you want the map to display the new zoom level immediately.
+*
+* Changing the zoom level scales the map without changing the current center coordinate. At zoom level 0, tiles cover the entire world map; at zoom level 1, tiles cover 1/4 of the world; at zoom level 2, tiles cover 1/16 of the world, and so on. */
+- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated;
+
+/** Changes the center coordinate and zoom level of the and optionally animates the change.
+* @param centerCoordinate The new center coordinate for the map.
+* @param zoomLevel The new zoom level for the map.
+* @param animated Specify `YES` if you want the map view to animate scrolling and zooming to the new location or `NO` if you want the map to display the new location immediately. */
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated;
+
+/** The heading of the map (measured in degrees) relative to true north.
+*
+* The value `0` means that the top edge of the map view corresponds to true north. The value `90` means the top of the map is pointing due east. The value `180` means the top of the map points due south, and so on. */
+@property (nonatomic) CLLocationDirection direction;
+
+/** Changes the heading of the map and optionally animates the changes.
+* @param direction The heading of the map (measured in degrees) relative to true north.
+* @param animated Specify `YES` if you want the map view to animate the change to the new heading or `NO` if you want the map to display the new heading immediately.
+*
+* Changing the heading rotates the map without changing the current center coordinate or zoom level. */
+- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated;
+
+/** Resets the map rotation to a northern heading. */
+- (void)resetNorth;
+
+#pragma mark - Styling the Map
+
+/** @name Styling the Map */
+
+/** Sets the map style.
+* @param styleJSON The map stylesheet as JSON text. */
+- (void)setStyleJSON:(NSString *)styleJSON;
+
+/** Returns the raw JSON style as a native dictionary object. */
+- (NSDictionary *)getRawStyle;
+
+/** Sets the raw JSON style as a native dictionary object with a transition animation.
+* @param style The style JSON as a dictionary object. */
+- (void)setRawStyle:(NSDictionary *)style;
+
+/** Returns the names of the styles bundled with the library. */
+- (NSArray *)bundledStyleNames;
+
+/** Sets the map style to a named, bundled style.
+* @param styleName The map style name to use. */
+- (void)useBundledStyleNamed:(NSString *)styleName;
+
+#pragma mark - Debugging
+
+/** @name Debugging */
+
+/** A Boolean value that determines whether map debugging information is shown.
+*
+* The default value of this property is `NO`. */
+@property (nonatomic, getter=isDebugActive) BOOL debugActive;
+
+/** Toggle the current value of debugActive. */
+- (void)toggleDebug;
+
+/** Resets the map to the minimum zoom level, a center coordinate of (0, 0), and a northern heading. */
+- (void)resetPosition;
+
+@end
+
+// TODO
+@protocol MGLMapViewDelegate <NSObject>
+
+@optional
+
+// Responding to Map Position Changes
+
+// TODO
+- (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated;
+
+// TODO
+- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated;
+
+// Loading the Map Data
+
+// TODO
+- (void)mapViewWillStartLoadingMap:(MGLMapView *)mapView;
+
+// TODO
+- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView;
+
+// TODO
+- (void)mapViewDidFailLoadingMap:(MGLMapView *)mapView withError:(NSError *)error;
+
+// TODO
+- (void)mapViewWillStartRenderingMap:(MGLMapView *)mapView;
+
+// TODO
+- (void)mapViewDidFinishRenderingMap:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered;
+
+@end
diff --git a/include/mbgl/ios/MGLStyleFunctionValue.h b/include/mbgl/ios/MGLStyleFunctionValue.h
new file mode 100644
index 0000000000..27a867548b
--- /dev/null
+++ b/include/mbgl/ios/MGLStyleFunctionValue.h
@@ -0,0 +1,23 @@
+#import <UIKit/UIKit.h>
+
+@interface MGLStyleFunctionValue : NSObject
+
++ (instancetype)linearFunctionWithBaseZoomLevel:(CGFloat)zBase
+ initialValue:(CGFloat)val
+ slope:(CGFloat)slope
+ minimumValue:(CGFloat)min
+ maximumValue:(CGFloat)max;
+
++ (instancetype)exponentialFunctionWithBaseZoomLevel:(CGFloat)zBase
+ initialValue:(CGFloat)val
+ slope:(CGFloat)slope
+ minimumValue:(CGFloat)min
+ maximumValue:(CGFloat)max;
+
++ (instancetype)minimumZoomLevelFunction:(CGFloat)minimumZoom;
+
++ (instancetype)maximumZoomLevelFunction:(CGFloat)maximumZoom;
+
++ (instancetype)stopsFunctionWithZoomLevelsAndValues:(NSNumber *)firstZoom, ... NS_REQUIRES_NIL_TERMINATION;
+
+@end
diff --git a/include/mbgl/ios/MGLTypes.h b/include/mbgl/ios/MGLTypes.h
new file mode 100644
index 0000000000..7a17445770
--- /dev/null
+++ b/include/mbgl/ios/MGLTypes.h
@@ -0,0 +1,17 @@
+#import <Foundation/Foundation.h>
+
+// style property value types
+//
+extern NSString *const MGLStyleValueTypeBoolean;
+extern NSString *const MGLStyleValueTypeNumber;
+extern NSString *const MGLStyleValueTypeNumberPair;
+extern NSString *const MGLStyleValueTypeColor;
+extern NSString *const MGLStyleValueTypeString;
+
+// style property function types
+//
+extern NSString *const MGLStyleValueTypeFunctionMinimumZoom;
+extern NSString *const MGLStyleValueTypeFunctionMaximumZoom;
+extern NSString *const MGLStyleValueTypeFunctionLinear;
+extern NSString *const MGLStyleValueTypeFunctionExponential;
+extern NSString *const MGLStyleValueTypeFunctionStops;
diff --git a/include/mbgl/ios/NSArray+MGLAdditions.h b/include/mbgl/ios/NSArray+MGLAdditions.h
new file mode 100644
index 0000000000..4b87614330
--- /dev/null
+++ b/include/mbgl/ios/NSArray+MGLAdditions.h
@@ -0,0 +1,7 @@
+#import <Foundation/Foundation.h>
+
+@interface NSArray (MGLAdditions)
+
+- (NSMutableArray *)deepMutableCopy;
+
+@end
diff --git a/include/mbgl/ios/NSDictionary+MGLAdditions.h b/include/mbgl/ios/NSDictionary+MGLAdditions.h
new file mode 100644
index 0000000000..04c3396d41
--- /dev/null
+++ b/include/mbgl/ios/NSDictionary+MGLAdditions.h
@@ -0,0 +1,7 @@
+#import <Foundation/Foundation.h>
+
+@interface NSDictionary (MGLAdditions)
+
+- (NSMutableDictionary *)deepMutableCopy;
+
+@end
diff --git a/include/mbgl/ios/UIColor+MGLAdditions.h b/include/mbgl/ios/UIColor+MGLAdditions.h
new file mode 100644
index 0000000000..3758c04eef
--- /dev/null
+++ b/include/mbgl/ios/UIColor+MGLAdditions.h
@@ -0,0 +1,11 @@
+#import <UIKit/UIKit.h>
+
+@interface UIColor (MGLAdditions)
+
++ (UIColor *)colorWithRGBAString:(NSString *)rgbaString;
+- (NSString *)rgbaStringFromColor;
+
++ (UIColor *)colorWithHexString:(NSString *)hexString;
+- (NSString *)hexStringFromColor;
+
+@end
diff --git a/ios/FAQ.md b/ios/FAQ.md
new file mode 100644
index 0000000000..19ae74fdba
--- /dev/null
+++ b/ios/FAQ.md
@@ -0,0 +1,49 @@
+# Mapbox GL for iOS FAQ
+
+Mapbox GL is a completely new renderer technology which will eventually replace and/or merge Mapbox's existing iOS toolsets, the [Mapbox iOS SDK](http://www.mapbox.com/mapbox-ios-sdk/) and [MBXMapKit](https://www.mapbox.com/mbxmapkit/). This FAQ shares our current plans for that migration. The plans are subject to change as Mapbox GL matures, but we wanted to both clarify our thinking as well as set expectations for where things are headed.
+
+### When will Mapbox GL be released?
+
+The library is [open source](https://github.com/mapbox/mapbox-gl-native) right now, but an official, production-recommended release will come later in 2015.
+
+### What iOS versions will be supported?
+
+Mapbox GL currently supports iOS 7 and later. This will continue to be the case. MBXMapKit also supports iOS 7 and greater, but this does mean that moving from the iOS SDK will leave behind iOS 5 and 6.
+
+### Will the API be similar to the Mapbox iOS SDK/MBXMapKit/MapKit?
+
+Yes. We are shooting for bringing the Mapbox GL API in line with Apple's MapKit for the easiest transition ability.
+
+MBXMapKit is already an add-on to MapKit, so Apple's `MKMapView` API is used directly.
+
+The Mapbox iOS SDK is "workalike", since it descends from an [upstream open source project](https://github.com/Alpstein/route-me) that predates Apple's own MapKit. It uses similar concepts like annotations (with the difference that the map view delegate provides `CALayer` instances instead of `UIView`, the intention being that Mapbox GL will support `UIView`), similar API for managing the map view center, bounds, and zoom levels, and an `RMUserLocation` API that is very much like `MKUserLocation`. But the Mapbox iOS SDK also features unique APIs like extensible tile sources, offline caching, UTFGrid interactivity, and point annotation clustering.
+
+### Will the iOS SDK's extra APIs make it over to Mapbox GL?
+
+The intention is yes. This includes:
+
+#### Tile sources
+
+The SDK uses the `RMTileSource` protocol to allow for extensible remote and local raster tile sources, as well as custom on-the-fly raster tile generation. We are planning to build a transitional API in Mapbox GL that acts as a wrapper to `RMTileSource` so that existing sources can be used to plug into the main Mapbox GL source API.
+
+#### Offline caching
+
+Both the Mapbox iOS SDK and MBXMapKit feature robust support for offline caching of raster tiles and the SDK features support for [MBTiles](http://mbtiles.org) tile sources.
+
+We're likely going to take a similar approach to both performance caching and offline maps as is done with the `0.3.0` release of MBXMapKit. That is, `NSURLCache` will be used for performance caching and specific, separate offline map database management will be used for fetching areas of map tiles. This is a cleaner design than exists in the SDK, which combines performance and offline caches and has a non-intuitive configuration API.
+
+We will likely also port over MBTiles support to Mapbox GL.
+
+#### UTFGrid interactivity
+
+[UTFGrid](https://github.com/mapbox/utfgrid-spec) is a technology for enabling interactive features in raster tiles. Support for the technology is provided in the Mapbox iOS SDK with categories in `RMInteractiveSource` in combination with either `RMMapboxSource` or `RMMBTilesSource`.
+
+Mapbox GL obsoletes this sort of pixel-based interactivity since the source data used for rendering can be queried on-device, so we are unlikely to port UTFGrid over to GL.
+
+#### Annotation clustering
+
+We're planning on adding support for point clustering to Mapbox GL and it will likely have a similar API to the SDK's. Clustering will happen on an individual style layer level. The performance for clustering, as well as annotations in general, will hopefully be better than in the SDK, since it will be done with direct OpenGL rendering instead of Core Animation layers.
+
+### What will the migration path look like?
+
+Ideally, the migration will be pretty lightweight because of the APIs supported above. There may be slight syntax changes, but they likely won't be more than would be expected from something like Mapbox iOS SDK version `1.x` to a hypothetical `2.x`.
diff --git a/ios/MapboxGL.podspec b/ios/MapboxGL.podspec
new file mode 100644
index 0000000000..6cdec1826d
--- /dev/null
+++ b/ios/MapboxGL.podspec
@@ -0,0 +1,33 @@
+Pod::Spec.new do |m|
+
+ m.name = 'MapboxGL'
+ m.version = '0.1.0'
+
+ m.summary = 'Open source vector map solution for iOS with full styling capabilities.'
+ m.description = 'Open source OpenGL-based vector map solution for iOS with full styling capabilities and Cocoa bindings.'
+ m.homepage = 'https://www.mapbox.com/blog/mapbox-gl/'
+ m.license = 'BSD'
+ m.author = { 'Mapbox' => 'mobile@mapbox.com' }
+ m.screenshot = 'https://raw.githubusercontent.com/mapbox/mapbox-gl-cocoa/master/pkg/screenshot.png'
+ m.social_media_url = 'https://twitter.com/Mapbox'
+
+ m.source = { :git => 'https://github.com/mapbox/mapbox-gl-cocoa.git', :tag => m.version.to_s }
+
+ m.platform = :ios
+ m.ios.deployment_target = '7.0'
+
+ m.source_files = 'dist/static/Headers/*.h'
+
+ m.requires_arc = true
+
+ m.resource_bundle = { 'MapboxGL' => 'dist/static/MapboxGL.bundle/*' }
+
+ m.frameworks = 'CoreLocation', 'Foundation', 'GLKit', 'SystemConfiguration', 'UIKit'
+
+ m.libraries = 'MapboxGL', 'c++', 'sqlite3', 'z'
+
+ m.vendored_libraries = 'dist/static/libMapboxGL.a'
+
+ m.xcconfig = { 'OTHER_CPLUSPLUSFLAGS' => '-std=gnu++11 -stdlib=libc++' }
+
+end
diff --git a/ios/README.md b/ios/README.md
new file mode 100644
index 0000000000..2af03691e2
--- /dev/null
+++ b/ios/README.md
@@ -0,0 +1,68 @@
+# Mapbox GL for iOS
+
+Use or edit this project to get access to vector maps (via [Mapbox Vector Tiles](https://www.mapbox.com/blog/vector-tiles)) and dynamic OpenGL-based styling in your iOS apps by using `MGLMapView`.
+
+![](./screenshot.png)
+
+## Installation
+
+Currently in flux. See the parent `mapbox-gl-native` project `README` for details on building this library yourself.
+
+Static and dynamic prebuilt binaries are coming back, as is CocoaPods support.
+
+## Example usage
+
+### Objective-C
+
+```objective-c
+MGLMapView *mapView = [[MGLMapView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)
+ accessToken:@"<access token string>"];
+
+[mapView setCenterCoordinate:CLLocationCoordinate2DMake(28.369334, -80.743779)
+ zoomLevel:13
+ animated:NO];
+
+[mapView useBundledStyleNamed:@"outdoors"];
+
+[self.view addSubview:mapView];
+```
+
+### Swift
+
+```swift
+let mapView = MGLMapView(frame: CGRect(x: 0, y: 0, width: 400, height: 400),
+ accessToken: "<access token string>")
+
+mapView.setCenterCoordinate(CLLocationCoordinate2D(latitude: 46.049900, longitude: -122.095678),
+ zoomLevel: 12,
+ animated: false)
+
+mapView.useBundledStyleNamed("outdoors")
+
+view.addSubview(mapView)
+```
+
+## Development
+
+If you'd like to contribute to this project, go up to [Mapbox GL native](https://github.com/mapbox/mapbox-gl-native) and clone the project.
+
+## Testing
+
+Currently in flux and not functioning.
+
+## Requirements
+
+ * iOS 7+
+ * a sense of adventure
+
+## Styling
+
+See the [online style reference](https://www.mapbox.com/mapbox-gl-style-spec/) for the latest documentation. Contained within the `MapboxGL.bundle` assets are a couple of starter styles in JSON format.
+
+The project will eventually get a programmatic styling API as well.
+
+## Related Projects
+
+ * https://github.com/mapbox/mapbox-gl-style-spec
+ * https://github.com/mapbox/mapbox-gl-js
+ * https://github.com/mapbox/vector-tile-spec
diff --git a/ios/app/MBXAppDelegate.h b/ios/app/MBXAppDelegate.h
new file mode 100644
index 0000000000..da081fdcd5
--- /dev/null
+++ b/ios/app/MBXAppDelegate.h
@@ -0,0 +1,7 @@
+#import <UIKit/UIKit.h>
+
+@interface MBXAppDelegate : UIResponder <UIApplicationDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+
+@end \ No newline at end of file
diff --git a/ios/app/MBXAppDelegate.m b/ios/app/MBXAppDelegate.m
new file mode 100644
index 0000000000..f0541b9971
--- /dev/null
+++ b/ios/app/MBXAppDelegate.m
@@ -0,0 +1,15 @@
+#import "MBXAppDelegate.h"
+#import "MBXViewController.h"
+
+@implementation MBXAppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+ self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+ self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[MBXViewController new]];
+ [self.window makeKeyAndVisible];
+
+ return YES;
+}
+
+@end
diff --git a/ios/app/MBXViewController.h b/ios/app/MBXViewController.h
new file mode 100644
index 0000000000..924d3af60c
--- /dev/null
+++ b/ios/app/MBXViewController.h
@@ -0,0 +1,5 @@
+#import <UIKit/UIKit.h>
+
+@interface MBXViewController : UIViewController
+
+@end
diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm
new file mode 100644
index 0000000000..4945b73cc5
--- /dev/null
+++ b/ios/app/MBXViewController.mm
@@ -0,0 +1,266 @@
+#import "MBXViewController.h"
+
+#import <mbgl/ios/MGLMapView.h>
+
+#import <mbgl/platform/darwin/settings_nsuserdefaults.hpp>
+
+#import <CoreLocation/CoreLocation.h>
+
+static UIColor *const kTintColor = [UIColor colorWithRed:0.120 green:0.550 blue:0.670 alpha:1.000];
+
+static NSDictionary *const kStyles = @{
+ @"bright-v6": @"Bright",
+ @"basic-v6": @"Basic",
+ @"outdoors-v6": @"Outdoors",
+ @"satellite-v6": @"Satellite"
+};
+
+@interface MBXViewController () <UIActionSheetDelegate, CLLocationManagerDelegate>
+
+@property (nonatomic) MGLMapView *mapView;
+@property (nonatomic) CLLocationManager *locationManager;
+
+@end
+
+@implementation MBXViewController
+
+mbgl::Settings_NSUserDefaults *settings = nullptr;
+
+#pragma mark - Setup
+
+- (id)init
+{
+ self = [super init];
+
+ if (self)
+ {
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationDidEnterBackgroundNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restoreState:) name:UIApplicationWillEnterForegroundNotification object:nil];
+ }
+
+ return self;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ NSString *accessToken = [[NSProcessInfo processInfo] environment][@"MAPBOX_ACCESS_TOKEN"];
+ if (accessToken) {
+ // Store to preferences so that we can launch the app later on without having to specify
+ // token.
+ [[NSUserDefaults standardUserDefaults] setObject:accessToken forKey:@"access_token"];
+ } else {
+ // Try to retrieve from preferences, maybe we've stored them there previously and can reuse
+ // the token.
+ accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:@"access_token"];
+ }
+
+ if ( ! accessToken) NSLog(@"No access token set. Mapbox vector tiles won't work.");
+
+ self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds accessToken:accessToken];
+ self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ [self.view addSubview:self.mapView];
+
+ self.mapView.viewControllerForLayoutGuides = self;
+
+ self.view.tintColor = kTintColor;
+ self.navigationController.navigationBar.tintColor = kTintColor;
+ self.mapView.tintColor = kTintColor;
+
+ self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"settings.png"]
+ style:UIBarButtonItemStylePlain
+ target:self
+ action:@selector(showSettings)];
+
+ UIButton *titleButton = [UIButton buttonWithType:UIButtonTypeCustom];
+ [titleButton setFrame:CGRectMake(0, 0, 120, 40)];
+ [titleButton setTitle:[[kStyles allValues] firstObject] forState:UIControlStateNormal];
+ [titleButton setTitleColor:kTintColor forState:UIControlStateNormal];
+ [titleButton addTarget:self action:@selector(cycleStyles) forControlEvents:UIControlEventTouchUpInside];
+ self.navigationItem.titleView = titleButton;
+
+ self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"locateUser.png"]
+ style:UIBarButtonItemStylePlain
+ target:self
+ action:@selector(locateUser)];
+
+ settings = new mbgl::Settings_NSUserDefaults();
+ [self restoreState:nil];
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+
+- (void)saveState:(NSNotification *)notification
+{
+ if (self.mapView && settings)
+ {
+ settings->longitude = self.mapView.centerCoordinate.longitude;
+ settings->latitude = self.mapView.centerCoordinate.latitude;
+ settings->zoom = self.mapView.zoomLevel;
+ settings->bearing = self.mapView.direction;
+ settings->debug = self.mapView.isDebugActive;
+ settings->save();
+ }
+}
+
+- (void)restoreState:(NSNotification *)notification
+{
+ if (self.mapView && settings) {
+ settings->load();
+ [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(settings->latitude, settings->longitude) zoomLevel:settings->zoom animated:NO];
+ self.mapView.direction = settings->bearing;
+ [self.mapView setDebugActive:settings->debug];
+ }
+}
+
+#pragma clang diagnostic pop
+
+- (NSUInteger)supportedInterfaceOrientations
+{
+ return UIInterfaceOrientationMaskAll;
+}
+
+#pragma mark - Actions
+
+- (void)showSettings
+{
+ UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"Map Settings"
+ delegate:self
+ cancelButtonTitle:@"Cancel"
+ destructiveButtonTitle:nil
+ otherButtonTitles:@"Reset North", @"Reset Position", @"Toggle Debug", nil];
+
+ [sheet showFromBarButtonItem:self.navigationItem.leftBarButtonItem animated:YES];
+}
+
+- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
+{
+ if (buttonIndex == actionSheet.firstOtherButtonIndex)
+ {
+ [self.mapView resetNorth];
+ }
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 1)
+ {
+ [self.mapView resetPosition];
+ }
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 2)
+ {
+ [self.mapView toggleDebug];
+ }
+}
+
+- (void)cycleStyles
+{
+ UIButton *titleButton = (UIButton *)self.navigationItem.titleView;
+
+ NSString *styleName = [titleButton titleForState:UIControlStateNormal];
+
+ if ( ! styleName)
+ {
+ styleName = [[kStyles allKeys] firstObject];
+ }
+ else
+ {
+ NSUInteger index = [[kStyles allValues] indexOfObject:styleName] + 1;
+ if (index == [[kStyles allKeys] count]) index = 0;
+ styleName = [[kStyles allKeys] objectAtIndex:index];
+ }
+
+ [self.mapView useBundledStyleNamed:styleName];
+
+ [titleButton setTitle:kStyles[styleName] forState:UIControlStateNormal];
+}
+
+- (void)locateUser
+{
+ if ( ! self.locationManager)
+ {
+ self.locationManager = [CLLocationManager new];
+ self.locationManager.delegate = self;
+ }
+
+ if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied)
+ {
+ [[[UIAlertView alloc] initWithTitle:@"Authorization Denied"
+ message:@"Please enable location services for this app in Privacy settings."
+ delegate:nil
+ cancelButtonTitle:nil otherButtonTitles:@"OK", nil] show];
+ }
+ else
+ {
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
+ if ([CLLocationManager instancesRespondToSelector:@selector(requestWhenInUseAuthorization)])
+ {
+ if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse)
+ {
+ [self.locationManager startUpdatingLocation];
+ }
+ else
+ {
+ [_locationManager requestWhenInUseAuthorization];
+ }
+ }
+ else
+ {
+ [self.locationManager startUpdatingLocation];
+ }
+#else
+ [self.locationManager startUpdatingLocation];
+#endif
+ }
+}
+
+#pragma mark - Destruction
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ if (settings)
+ {
+ [self saveState:nil];
+ delete settings;
+ settings = nullptr;
+ }
+}
+
+#pragma mark - Location
+
+- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
+{
+ switch (status)
+ {
+ case kCLAuthorizationStatusAuthorizedAlways:
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
+ case kCLAuthorizationStatusAuthorizedWhenInUse:
+#endif
+ {
+ [manager startUpdatingLocation];
+ break;
+ }
+ default:
+ {
+ }
+ }
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+
+- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
+{
+ CLLocation *latestLocation = locations.lastObject;
+
+ if ([latestLocation distanceFromLocation:[[CLLocation alloc] initWithLatitude:self.mapView.centerCoordinate.latitude longitude:self.mapView.centerCoordinate.longitude]] > 100)
+ {
+ [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(latestLocation.coordinate.latitude, latestLocation.coordinate.longitude) zoomLevel:17 animated:YES];
+ }
+
+ [self.locationManager stopUpdatingLocation];
+}
+
+#pragma clang diagnostic pop
+
+@end
diff --git a/ios/app/README.md b/ios/app/README.md
new file mode 100644
index 0000000000..325670f1ea
--- /dev/null
+++ b/ios/app/README.md
@@ -0,0 +1,3 @@
+This is the app for use in the development of Mapbox GL for iOS.
+
+If you want to run this app, head up to [`mapbox-gl-native`](https://github.com/mapbox/mapbox-gl-native) and follow the iOS installation instructions there. \ No newline at end of file
diff --git a/ios/app/app-info.plist b/ios/app/app-info.plist
new file mode 100644
index 0000000000..c15cbeb64f
--- /dev/null
+++ b/ios/app/app-info.plist
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.mapbox.MapboxGL</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>0.0.1</string>
+ <key>CFBundleSignature</key>
+ <string>MBGL</string>
+ <key>CFBundleVersion</key>
+ <string>0.0.1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>NSHumanReadableCopyright</key>
+ <string>(c) 2014 Mapbox</string>
+ <key>NSLocationWhenInUseUsageDescription</key>
+ <string>The map will be centered on the user's location.</string>
+ <key>UILaunchStoryboardName</key>
+ <string>Default</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/ios/app/img/Default-568h@2x.png b/ios/app/img/Default-568h@2x.png
new file mode 100644
index 0000000000..ea3706427a
--- /dev/null
+++ b/ios/app/img/Default-568h@2x.png
Binary files differ
diff --git a/ios/app/img/Default-667h@2x.png b/ios/app/img/Default-667h@2x.png
new file mode 100644
index 0000000000..03f139de66
--- /dev/null
+++ b/ios/app/img/Default-667h@2x.png
Binary files differ
diff --git a/ios/app/img/Icon-40.png b/ios/app/img/Icon-40.png
new file mode 100644
index 0000000000..eca13393e6
--- /dev/null
+++ b/ios/app/img/Icon-40.png
Binary files differ
diff --git a/ios/app/img/Icon-40@2x.png b/ios/app/img/Icon-40@2x.png
new file mode 100644
index 0000000000..070d037539
--- /dev/null
+++ b/ios/app/img/Icon-40@2x.png
Binary files differ
diff --git a/ios/app/img/Icon-60.png b/ios/app/img/Icon-60.png
new file mode 100644
index 0000000000..ff4c6ab4b1
--- /dev/null
+++ b/ios/app/img/Icon-60.png
Binary files differ
diff --git a/ios/app/img/Icon-60@2x.png b/ios/app/img/Icon-60@2x.png
new file mode 100644
index 0000000000..b7f25955f5
--- /dev/null
+++ b/ios/app/img/Icon-60@2x.png
Binary files differ
diff --git a/ios/app/img/Icon-72.png b/ios/app/img/Icon-72.png
new file mode 100644
index 0000000000..0c876f664d
--- /dev/null
+++ b/ios/app/img/Icon-72.png
Binary files differ
diff --git a/ios/app/img/Icon-72@2x.png b/ios/app/img/Icon-72@2x.png
new file mode 100644
index 0000000000..6da408204a
--- /dev/null
+++ b/ios/app/img/Icon-72@2x.png
Binary files differ
diff --git a/ios/app/img/Icon-76.png b/ios/app/img/Icon-76.png
new file mode 100644
index 0000000000..895b4a1761
--- /dev/null
+++ b/ios/app/img/Icon-76.png
Binary files differ
diff --git a/ios/app/img/Icon-76@2x.png b/ios/app/img/Icon-76@2x.png
new file mode 100644
index 0000000000..7bc5208976
--- /dev/null
+++ b/ios/app/img/Icon-76@2x.png
Binary files differ
diff --git a/ios/app/img/Icon-Small-50.png b/ios/app/img/Icon-Small-50.png
new file mode 100644
index 0000000000..6d17da4b00
--- /dev/null
+++ b/ios/app/img/Icon-Small-50.png
Binary files differ
diff --git a/ios/app/img/Icon-Small-50@2x.png b/ios/app/img/Icon-Small-50@2x.png
new file mode 100644
index 0000000000..ac4ec19282
--- /dev/null
+++ b/ios/app/img/Icon-Small-50@2x.png
Binary files differ
diff --git a/ios/app/img/Icon-Small.png b/ios/app/img/Icon-Small.png
new file mode 100644
index 0000000000..aecbbc8a1d
--- /dev/null
+++ b/ios/app/img/Icon-Small.png
Binary files differ
diff --git a/ios/app/img/Icon-Small@2x.png b/ios/app/img/Icon-Small@2x.png
new file mode 100644
index 0000000000..7773852e7a
--- /dev/null
+++ b/ios/app/img/Icon-Small@2x.png
Binary files differ
diff --git a/ios/app/img/Icon-Spotlight-40.png b/ios/app/img/Icon-Spotlight-40.png
new file mode 100644
index 0000000000..eca13393e6
--- /dev/null
+++ b/ios/app/img/Icon-Spotlight-40.png
Binary files differ
diff --git a/ios/app/img/Icon-Spotlight-40@2x.png b/ios/app/img/Icon-Spotlight-40@2x.png
new file mode 100644
index 0000000000..070d037539
--- /dev/null
+++ b/ios/app/img/Icon-Spotlight-40@2x.png
Binary files differ
diff --git a/ios/app/img/Icon.png b/ios/app/img/Icon.png
new file mode 100644
index 0000000000..9ca8194eef
--- /dev/null
+++ b/ios/app/img/Icon.png
Binary files differ
diff --git a/ios/app/img/Icon@2x.png b/ios/app/img/Icon@2x.png
new file mode 100644
index 0000000000..7c2e8ba037
--- /dev/null
+++ b/ios/app/img/Icon@2x.png
Binary files differ
diff --git a/ios/app/img/iTunesArtwork b/ios/app/img/iTunesArtwork
new file mode 100644
index 0000000000..ac6a0c58e8
--- /dev/null
+++ b/ios/app/img/iTunesArtwork
Binary files differ
diff --git a/ios/app/img/iTunesArtwork.png b/ios/app/img/iTunesArtwork.png
new file mode 100644
index 0000000000..b10824b048
--- /dev/null
+++ b/ios/app/img/iTunesArtwork.png
Binary files differ
diff --git a/ios/app/img/iTunesArtwork@2x b/ios/app/img/iTunesArtwork@2x
new file mode 100644
index 0000000000..fae1dad8bf
--- /dev/null
+++ b/ios/app/img/iTunesArtwork@2x
Binary files differ
diff --git a/ios/app/img/iTunesArtwork@2x.png b/ios/app/img/iTunesArtwork@2x.png
new file mode 100644
index 0000000000..fdee900aa4
--- /dev/null
+++ b/ios/app/img/iTunesArtwork@2x.png
Binary files differ
diff --git a/ios/app/img/locateUser.png b/ios/app/img/locateUser.png
new file mode 100644
index 0000000000..aa8d1cf52e
--- /dev/null
+++ b/ios/app/img/locateUser.png
Binary files differ
diff --git a/ios/app/img/locateUser@2x.png b/ios/app/img/locateUser@2x.png
new file mode 100644
index 0000000000..29c4905938
--- /dev/null
+++ b/ios/app/img/locateUser@2x.png
Binary files differ
diff --git a/ios/app/img/settings.png b/ios/app/img/settings.png
new file mode 100644
index 0000000000..5d7643eef5
--- /dev/null
+++ b/ios/app/img/settings.png
Binary files differ
diff --git a/ios/app/img/settings@2x.png b/ios/app/img/settings@2x.png
new file mode 100644
index 0000000000..2bb9f0ebad
--- /dev/null
+++ b/ios/app/img/settings@2x.png
Binary files differ
diff --git a/ios/app/main.m b/ios/app/main.m
new file mode 100644
index 0000000000..954584f141
--- /dev/null
+++ b/ios/app/main.m
@@ -0,0 +1,10 @@
+#import <UIKit/UIKit.h>
+
+#import "MBXAppDelegate.h"
+
+int main(int argc, char * argv[])
+{
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([MBXAppDelegate class]));
+ }
+}
diff --git a/ios/app/mapboxgl-app.gyp b/ios/app/mapboxgl-app.gyp
new file mode 100644
index 0000000000..7287932fc5
--- /dev/null
+++ b/ios/app/mapboxgl-app.gyp
@@ -0,0 +1,57 @@
+{
+ 'includes': [
+ '../../gyp/common.gypi',
+ ],
+ 'targets': [
+ { 'target_name': 'iosapp',
+ 'product_name': 'Mapbox GL',
+ 'type': 'executable',
+ 'product_extension': 'app',
+ 'mac_bundle': 1,
+ 'mac_bundle_resources': [
+ '<!@(find ./img -type f)',
+ ],
+
+ 'dependencies': [
+ '../../mbgl.gyp:bundle_styles',
+ '../../mbgl.gyp:core',
+ '../../mbgl.gyp:platform-<(platform_lib)',
+ '../../mbgl.gyp:http-<(http_lib)',
+ '../../mbgl.gyp:asset-<(asset_lib)',
+ '../../mbgl.gyp:cache-<(cache_lib)',
+ ],
+
+ 'sources': [
+ './main.m',
+ './MBXAppDelegate.h',
+ './MBXAppDelegate.m',
+ './MBXViewController.h',
+ './MBXViewController.mm',
+ '../../platform/darwin/settings_nsuserdefaults.mm',
+ ],
+
+ 'xcode_settings': {
+ 'SDKROOT': 'iphoneos',
+ 'SUPPORTED_PLATFORMS': 'iphonesimulator iphoneos',
+ 'INFOPLIST_FILE': 'app-info.plist',
+ 'TARGETED_DEVICE_FAMILY': '1,2',
+ 'COMBINE_HIDPI_IMAGES': 'NO', # don't merge @2x.png images into .tiff files
+ 'CLANG_ENABLE_OBJC_ARC': 'YES',
+ },
+
+ 'configurations': {
+ 'Debug': {
+ 'xcode_settings': {
+ 'CODE_SIGN_IDENTITY': 'iPhone Developer',
+ },
+ },
+ 'Release': {
+ 'xcode_settings': {
+ 'CODE_SIGN_IDENTITY': 'iPhone Distribution',
+ 'ARCHS': [ "armv7", "armv7s", "arm64", "i386", "x86_64" ],
+ },
+ },
+ },
+ }
+ ]
+}
diff --git a/ios/docs/install_docs.sh b/ios/docs/install_docs.sh
new file mode 100755
index 0000000000..4d116c0332
--- /dev/null
+++ b/ios/docs/install_docs.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+if [ -z `which appledoc` ]; then
+ echo "Unable to find appledoc. Consider installing it from source or Homebrew."
+ exit 1
+fi
+
+VERSION=$( git tag | sort -r | sed -n '1p' )
+echo "Creating new docs for $VERSION..."
+echo
+
+appledoc \
+ --output /tmp/`uuidgen` \
+ --project-name "Mapbox GL $VERSION" \
+ --project-company Mapbox \
+ --create-docset \
+ --company-id com.mapbox \
+ --ignore app \
+ --ignore dist \
+ --ignore pkg \
+ --ignore test \
+ --ignore .m \
+ --ignore .mm \
+ . \ No newline at end of file
diff --git a/ios/docs/remove_docs.sh b/ios/docs/remove_docs.sh
new file mode 100755
index 0000000000..bb8c008dc2
--- /dev/null
+++ b/ios/docs/remove_docs.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+echo
+echo "Removing docs from ~/Library/Developer/Shared/Documentation/DocSets..."
+echo
+
+rm -rfv ~/Library/Developer/Shared/Documentation/DocSets/com.mapbox.Mapbox-GL-* \ No newline at end of file
diff --git a/ios/mapbox-gl-cocoa b/ios/mapbox-gl-cocoa
deleted file mode 160000
-Subproject f36ee137496c8cf11ab604cacbb63ca645e6741
diff --git a/ios/screenshot.png b/ios/screenshot.png
new file mode 100644
index 0000000000..58db4413db
--- /dev/null
+++ b/ios/screenshot.png
Binary files differ
diff --git a/mbgl.gyp b/mbgl.gyp
index b6b00455e1..c5ede28233 100644
--- a/mbgl.gyp
+++ b/mbgl.gyp
@@ -8,19 +8,20 @@
'./gyp/standalone.gypi',
'./gyp/core.gypi',
'./gyp/none.gypi',
- './gyp/install.gypi',
],
'conditions': [
- ['headless_lib == "cgl"', { 'includes': [ './gyp/headless-cgl.gypi' ] } ],
- ['headless_lib == "glx"', { 'includes': [ './gyp/headless-glx.gypi' ] } ],
- ['platform_lib == "osx"', { 'includes': [ './gyp/platform-osx.gypi' ] } ],
- ['platform_lib == "ios"', { 'includes': [ './gyp/platform-ios.gypi' ] } ],
+ ['headless_lib == "cgl" and host == "osx"', { 'includes': [ './gyp/headless-cgl.gypi' ] } ],
+ ['headless_lib == "glx" and host == "linux"', { 'includes': [ './gyp/headless-glx.gypi' ] } ],
+ ['platform_lib == "osx" and host == "osx"', { 'includes': [ './gyp/platform-osx.gypi' ] } ],
+ ['platform_lib == "ios" and host == "ios"', { 'includes': [ './gyp/platform-ios.gypi' ] } ],
['platform_lib == "linux"', { 'includes': [ './gyp/platform-linux.gypi' ] } ],
- ['platform_lib == "android"', { 'includes': [ './gyp/platform-android.gypi' ] } ],
+ ['platform_lib == "android" and host == "android"', { 'includes': [ './gyp/platform-android.gypi' ] } ],
['http_lib == "curl"', { 'includes': [ './gyp/http-curl.gypi' ] } ],
- ['http_lib == "nsurl"', { 'includes': [ './gyp/http-nsurl.gypi' ] } ],
+ ['http_lib == "nsurl" and (host == "osx" or host == "ios")', { 'includes': [ './gyp/http-nsurl.gypi' ] } ],
['asset_lib == "fs"', { 'includes': [ './gyp/asset-fs.gypi' ] } ],
['asset_lib == "zip"', { 'includes': [ './gyp/asset-zip.gypi' ] } ],
['cache_lib == "sqlite"', { 'includes': [ './gyp/cache-sqlite.gypi' ] } ],
+
+ ['install_prefix != ""', { 'includes': ['./gyp/install.gypi' ] } ],
],
}
diff --git a/platform/darwin/log_nslog.mm b/platform/darwin/log_nslog.mm
index ea5fddf0b9..a82d78a01f 100644
--- a/platform/darwin/log_nslog.mm
+++ b/platform/darwin/log_nslog.mm
@@ -10,7 +10,6 @@ void NSLogBackend::record(EventSeverity severity, Event event, const std::string
NSString *message =
[[NSString alloc] initWithBytes:msg.data() length:msg.size() encoding:NSUTF8StringEncoding];
NSLog(@"[%s] %s: %@", EventSeverityClass(severity).c_str(), EventClass(event).c_str(), message);
- [message release];
}
void NSLogBackend::record(EventSeverity severity, Event event, const char *format, ...) {
@@ -37,7 +36,6 @@ void NSLogBackend::record(EventSeverity severity, Event event, int64_t code,
[[NSString alloc] initWithBytes:msg.data() length:msg.size() encoding:NSUTF8StringEncoding];
NSLog(@"[%s] %s: (%lld) %@", EventSeverityClass(severity).c_str(), EventClass(event).c_str(),
code, message);
- [message release];
}
}
diff --git a/platform/darwin/string_nsstring.mm b/platform/darwin/string_nsstring.mm
index 86c2c07edd..9bf199afc0 100644
--- a/platform/darwin/string_nsstring.mm
+++ b/platform/darwin/string_nsstring.mm
@@ -13,7 +13,6 @@ std::string uppercase(const std::string &string) {
NSString *uppercase = [original uppercaseString];
const std::string result{[uppercase cStringUsingEncoding : NSUTF8StringEncoding],
[uppercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
- [original release];
return result;
}
@@ -25,7 +24,6 @@ std::string lowercase(const std::string &string) {
NSString *lowercase = [original lowercaseString];
const std::string result{[lowercase cStringUsingEncoding : NSUTF8StringEncoding],
[lowercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
- [original release];
return result;
}
diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm
new file mode 100644
index 0000000000..dde15567a2
--- /dev/null
+++ b/platform/ios/MGLMapView.mm
@@ -0,0 +1,1607 @@
+#import "MGLMapView.h"
+
+#import <mbgl/platform/darwin/log_nslog.hpp>
+#import <mbgl/platform/gl.hpp>
+
+#import <GLKit/GLKit.h>
+#import <OpenGLES/EAGL.h>
+
+#include <mbgl/mbgl.hpp>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/platform/darwin/reachability.h>
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/storage/default/sqlite_cache.hpp>
+#include <mbgl/storage/network_status.hpp>
+
+#import "MGLTypes.h"
+#import "MGLStyleFunctionValue.h"
+
+#import "UIColor+MGLAdditions.h"
+#import "NSArray+MGLAdditions.h"
+#import "NSDictionary+MGLAdditions.h"
+
+
+// Returns the path to the default cache database on this system.
+const std::string &defaultCacheDatabase() {
+ static const std::string path = []() -> std::string {
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
+ if ([paths count] == 0) {
+ // Disable the cache if we don't have a location to write.
+ return "";
+ }
+
+ NSString *libraryDirectory = [paths objectAtIndex:0];
+ return [[libraryDirectory stringByAppendingPathComponent:@"cache.db"] UTF8String];
+ }();
+ return path;
+}
+
+
+extern NSString *const MGLStyleKeyGeneric;
+extern NSString *const MGLStyleKeyFill;
+extern NSString *const MGLStyleKeyLine;
+extern NSString *const MGLStyleKeyIcon;
+extern NSString *const MGLStyleKeyText;
+extern NSString *const MGLStyleKeyRaster;
+extern NSString *const MGLStyleKeyComposite;
+extern NSString *const MGLStyleKeyBackground;
+
+extern NSString *const MGLStyleValueFunctionAllowed;
+
+NSTimeInterval const MGLAnimationDuration = 0.3;
+
+#pragma mark - Private -
+
+@interface MGLMapView () <UIGestureRecognizerDelegate, GLKViewDelegate>
+
+@property (nonatomic) EAGLContext *context;
+@property (nonatomic) GLKView *glView;
+@property (nonatomic) NSOperationQueue *regionChangeDelegateQueue;
+@property (nonatomic) UIImageView *compass;
+@property (nonatomic) UIImageView *logoBug;
+@property (nonatomic) UIButton *attributionButton;
+@property (nonatomic) UIPanGestureRecognizer *pan;
+@property (nonatomic) UIPinchGestureRecognizer *pinch;
+@property (nonatomic) UIRotationGestureRecognizer *rotate;
+@property (nonatomic) UILongPressGestureRecognizer *quickZoom;
+@property (nonatomic, readonly) NSDictionary *allowedStyleTypes;
+@property (nonatomic) CGPoint centerPoint;
+@property (nonatomic) CGFloat scale;
+@property (nonatomic) CGFloat angle;
+@property (nonatomic) CGFloat quickZoomStart;
+@property (nonatomic, getter=isAnimatingGesture) BOOL animatingGesture;
+@property (nonatomic, readonly, getter=isRotationAllowed) BOOL rotationAllowed;
+
+@end
+
+@interface MGLStyleFunctionValue (MGLMapViewFriend)
+
+@property (nonatomic) NSString *functionType;
+@property (nonatomic) NSDictionary *stops;
+@property (nonatomic) CGFloat zBase;
+@property (nonatomic) CGFloat val;
+@property (nonatomic) CGFloat slope;
+@property (nonatomic) CGFloat min;
+@property (nonatomic) CGFloat max;
+@property (nonatomic) CGFloat minimumZoom;
+@property (nonatomic) CGFloat maximumZoom;
+
+- (id)rawStyle;
+
+@end
+
+@implementation MGLMapView
+
+#pragma mark - Setup -
+
+@dynamic debugActive;
+
+class MBGLView;
+
+std::chrono::steady_clock::duration secondsAsDuration(float duration)
+{
+ return std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<float, std::chrono::seconds::period>(duration));
+}
+
+mbgl::Map *mbglMap = nullptr;
+MBGLView *mbglView = nullptr;
+mbgl::SQLiteCache *mbglFileCache = nullptr;
+mbgl::DefaultFileSource *mbglFileSource = nullptr;
+
+- (instancetype)initWithFrame:(CGRect)frame styleJSON:(NSString *)styleJSON accessToken:(NSString *)accessToken
+{
+ self = [super initWithFrame:frame];
+
+ if (self && [self commonInit])
+ {
+ if (accessToken) [self setAccessToken:accessToken];
+
+ if (styleJSON || accessToken)
+ {
+ // If style is set directly, pass it on. If not, if we have an access
+ // token, we can pass nil and use the default style.
+ //
+ [self setStyleJSON:styleJSON];
+ }
+ }
+
+ return self;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken
+{
+ return [self initWithFrame:frame styleJSON:nil accessToken:accessToken];
+}
+
+- (instancetype)initWithCoder:(NSCoder *)decoder
+{
+ self = [super initWithCoder:decoder];
+
+ if (self && [self commonInit])
+ {
+ return self;
+ }
+
+ return nil;
+}
+
+- (void)setAccessToken:(NSString *)accessToken
+{
+ if (accessToken)
+ {
+ mbglMap->setAccessToken((std::string)[accessToken cStringUsingEncoding:[NSString defaultCStringEncoding]]);
+ }
+}
+
+- (void)setStyleJSON:(NSString *)styleJSON
+{
+ if ( ! styleJSON)
+ {
+ [self useBundledStyleNamed:@"bright-v6"];
+ }
+ else
+ {
+ if ([@(mbglMap->getStyleJSON().c_str()) length]) mbglMap->stop();
+ mbglMap->setStyleJSON((std::string)[styleJSON cStringUsingEncoding:[NSString defaultCStringEncoding]]);
+ mbglMap->start();
+ }
+}
+
+- (void)setStyleURL:(NSString *)filePathURL
+{
+ if ([@(mbglMap->getStyleJSON().c_str()) length]) mbglMap->stop();
+ mbglMap->setStyleURL(std::string("asset://") + [filePathURL UTF8String]);
+ mbglMap->start();
+}
+
+- (BOOL)commonInit
+{
+ // set logging backend
+ //
+ mbgl::Log::Set<mbgl::NSLogBackend>();
+
+ // create context
+ //
+ _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+
+ if ( ! _context)
+ {
+ mbgl::Log::Error(mbgl::Event::Setup, "Failed to create OpenGL ES context");
+
+ return NO;
+ }
+
+ // setup accessibility
+ //
+ self.accessibilityLabel = @"Map";
+
+ // create GL view
+ //
+ _glView = [[GLKView alloc] initWithFrame:self.bounds context:_context];
+ _glView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ _glView.enableSetNeedsDisplay = NO;
+ _glView.drawableStencilFormat = GLKViewDrawableStencilFormat8;
+ _glView.drawableDepthFormat = GLKViewDrawableDepthFormat16;
+ _glView.delegate = self;
+ [_glView bindDrawable];
+ [self addSubview:_glView];
+
+
+ // load extensions
+ //
+ const std::string extensions = (char *)glGetString(GL_EXTENSIONS);
+ {
+ using namespace mbgl;
+
+ if (extensions.find("GL_OES_vertex_array_object") != std::string::npos) {
+ gl::BindVertexArray = glBindVertexArrayOES;
+ gl::DeleteVertexArrays = glDeleteVertexArraysOES;
+ gl::GenVertexArrays = glGenVertexArraysOES;
+ gl::IsVertexArray = glIsVertexArrayOES;
+ }
+ }
+
+ // setup mbgl map
+ //
+ mbglView = new MBGLView(self);
+ mbglFileCache = new mbgl::SQLiteCache(defaultCacheDatabase());
+ mbglFileSource = new mbgl::DefaultFileSource(mbglFileCache);
+ mbglMap = new mbgl::Map(*mbglView, *mbglFileSource);
+ mbglMap->resize(self.bounds.size.width, self.bounds.size.height, _glView.contentScaleFactor, _glView.drawableWidth, _glView.drawableHeight);
+
+ // Notify map object when network reachability status changes.
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(reachabilityChanged:)
+ name:kReachabilityChangedNotification
+ object:nil];
+
+ Reachability* reachability = [Reachability reachabilityForInternetConnection];
+ [reachability startNotifier];
+
+ // setup logo bug
+ //
+ _logoBug = [[UIImageView alloc] initWithImage:[MGLMapView resourceImageNamed:@"mapbox.png"]];
+ _logoBug.accessibilityLabel = @"Mapbox logo";
+ _logoBug.frame = CGRectMake(8, self.bounds.size.height - _logoBug.bounds.size.height - 4, _logoBug.bounds.size.width, _logoBug.bounds.size.height);
+ _logoBug.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:_logoBug];
+
+ // setup attribution
+ //
+ _attributionButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
+ _attributionButton.accessibilityLabel = @"Attribution info";
+ [_attributionButton addTarget:self action:@selector(showAttribution:) forControlEvents:UIControlEventTouchUpInside];
+ _attributionButton.frame = CGRectMake(self.bounds.size.width - _attributionButton.bounds.size.width - 8, self.bounds.size.height - _attributionButton.bounds.size.height - 8, _attributionButton.bounds.size.width, _attributionButton.bounds.size.height);
+ _attributionButton.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:_attributionButton];
+
+ // setup compass
+ //
+ _compass = [[UIImageView alloc] initWithImage:[MGLMapView resourceImageNamed:@"Compass.png"]];
+ _compass.accessibilityLabel = @"Compass";
+ UIImage *compassImage = [MGLMapView resourceImageNamed:@"Compass.png"];
+ _compass.frame = CGRectMake(0, 0, compassImage.size.width, compassImage.size.height);
+ _compass.alpha = 0;
+ UIView *container = [[UIView alloc] initWithFrame:CGRectMake(self.bounds.size.width - compassImage.size.width - 5, 5, compassImage.size.width, compassImage.size.height)];
+ [container addSubview:_compass];
+ container.translatesAutoresizingMaskIntoConstraints = NO;
+ [container addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleCompassTapGesture:)]];
+ [self addSubview:container];
+
+ self.viewControllerForLayoutGuides = nil;
+
+ // setup interaction
+ //
+ _pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
+ _pan.delegate = self;
+ [self addGestureRecognizer:_pan];
+ _scrollEnabled = YES;
+
+ _pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
+ _pinch.delegate = self;
+ [self addGestureRecognizer:_pinch];
+ _zoomEnabled = YES;
+
+ _rotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotateGesture:)];
+ _rotate.delegate = self;
+ [self addGestureRecognizer:_rotate];
+ _rotateEnabled = YES;
+
+ UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTapGesture:)];
+ doubleTap.numberOfTapsRequired = 2;
+ [self addGestureRecognizer:doubleTap];
+
+ UITapGestureRecognizer *twoFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerTapGesture:)];
+ twoFingerTap.numberOfTouchesRequired = 2;
+ [twoFingerTap requireGestureRecognizerToFail:_pinch];
+ [twoFingerTap requireGestureRecognizerToFail:_rotate];
+ [self addGestureRecognizer:twoFingerTap];
+
+ if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
+ {
+ _quickZoom = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleQuickZoomGesture:)];
+ _quickZoom.numberOfTapsRequired = 1;
+ _quickZoom.minimumPressDuration = 0.25;
+ [self addGestureRecognizer:_quickZoom];
+ }
+
+ // observe app activity
+ //
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
+
+ // set initial position
+ //
+ mbglMap->setLonLatZoom(0, 0, mbglMap->getMinZoom());
+
+ // setup change delegate queue
+ //
+ _regionChangeDelegateQueue = [NSOperationQueue new];
+ _regionChangeDelegateQueue.maxConcurrentOperationCount = 1;
+
+ return YES;
+}
+
+-(void)reachabilityChanged:(NSNotification*)notification
+{
+ Reachability *reachability = [notification object];
+ if ([reachability isReachable]) {
+ mbgl::NetworkStatus::Reachable();
+ }
+}
+
+- (void)dealloc
+{
+ [_regionChangeDelegateQueue cancelAllOperations];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ if (mbglMap)
+ {
+ delete mbglMap;
+ mbglMap = nullptr;
+ }
+
+ if (mbglFileSource)
+ {
+ delete mbglFileSource;
+ mbglFileSource = nullptr;
+ }
+
+ if (mbglView)
+ {
+ delete mbglView;
+ mbglView = nullptr;
+ }
+
+ if ([[EAGLContext currentContext] isEqual:_context])
+ {
+ [EAGLContext setCurrentContext:nil];
+ }
+}
+
+#pragma mark - Layout -
+
+- (void)setFrame:(CGRect)frame
+{
+ [super setFrame:frame];
+
+ [self setNeedsLayout];
+}
+
+- (void)setBounds:(CGRect)bounds
+{
+ [super setBounds:bounds];
+
+ [self setNeedsLayout];
+}
+
++ (BOOL)requiresConstraintBasedLayout
+{
+ return YES;
+}
+
+- (void)didMoveToSuperview
+{
+ [self.compass.superview removeConstraints:self.compass.superview.constraints];
+ [self.logoBug removeConstraints:self.logoBug.constraints];
+ [self.attributionButton removeConstraints:self.attributionButton.constraints];
+
+ [self setNeedsUpdateConstraints];
+}
+
+- (void)setViewControllerForLayoutGuides:(UIViewController *)viewController
+{
+ _viewControllerForLayoutGuides = viewController;
+
+ [self.compass.superview removeConstraints:self.compass.superview.constraints];
+ [self.logoBug removeConstraints:self.logoBug.constraints];
+ [self.attributionButton removeConstraints:self.attributionButton.constraints];
+
+ [self setNeedsUpdateConstraints];
+}
+
+- (void)updateConstraints
+{
+ // If we have a view controller reference, use its layout guides for our various top & bottom
+ // views so they don't underlap navigation or tool bars. If we don't have a reference, apply
+ // constraints against ourself to maintain (albeit less ideal) placement of the subviews.
+ //
+ NSString *topGuideFormatString = (self.viewControllerForLayoutGuides ? @"[topLayoutGuide]" : @"|");
+ NSString *bottomGuideFormatString = (self.viewControllerForLayoutGuides ? @"[bottomLayoutGuide]" : @"|");
+
+ id topGuideViewsObject = (self.viewControllerForLayoutGuides ? (id)self.viewControllerForLayoutGuides.topLayoutGuide : (id)@"");
+ id bottomGuideViewsObject = (self.viewControllerForLayoutGuides ? (id)self.viewControllerForLayoutGuides.bottomLayoutGuide : (id)@"");
+
+ UIView *constraintParentView = (self.viewControllerForLayoutGuides.view ? self.viewControllerForLayoutGuides.view : self);
+
+ // compass
+ //
+ UIView *compassContainer = self.compass.superview;
+
+ [constraintParentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:%@-topSpacing-[container]", topGuideFormatString]
+ options:0
+ metrics:@{ @"topSpacing" : @(5) }
+ views:@{ @"topLayoutGuide" : topGuideViewsObject,
+ @"container" : compassContainer }]];
+
+ [constraintParentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[container]-rightSpacing-|"
+ options:0
+ metrics:@{ @"rightSpacing" : @(5) }
+ views:@{ @"container" : compassContainer }]];
+
+ [compassContainer addConstraint:[NSLayoutConstraint constraintWithItem:compassContainer
+ attribute:NSLayoutAttributeWidth
+ relatedBy:NSLayoutRelationEqual
+ toItem:nil
+ attribute:NSLayoutAttributeNotAnAttribute
+ multiplier:1
+ constant:self.compass.image.size.width]];
+
+ [compassContainer addConstraint:[NSLayoutConstraint constraintWithItem:compassContainer
+ attribute:NSLayoutAttributeHeight
+ relatedBy:NSLayoutRelationEqual
+ toItem:nil
+ attribute:NSLayoutAttributeNotAnAttribute
+ multiplier:1
+ constant:self.compass.image.size.height]];
+
+ // logo bug
+ //
+ [constraintParentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:[logoBug]-bottomSpacing-%@", bottomGuideFormatString]
+ options:0
+ metrics:@{ @"bottomSpacing" : @(4) }
+ views:@{ @"logoBug" : self.logoBug,
+ @"bottomLayoutGuide" : bottomGuideViewsObject }]];
+
+ [constraintParentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-leftSpacing-[logoBug]"
+ options:0
+ metrics:@{ @"leftSpacing" : @(8) }
+ views:@{ @"logoBug" : self.logoBug }]];
+
+ // attribution button
+ //
+ [constraintParentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:[attributionButton]-bottomSpacing-%@", bottomGuideFormatString]
+ options:0
+ metrics:@{ @"bottomSpacing" : @(8) }
+ views:@{ @"attributionButton" : self.attributionButton,
+ @"bottomLayoutGuide" : bottomGuideViewsObject }]];
+
+ [constraintParentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[attributionButton]-rightSpacing-|"
+ options:0
+ metrics:@{ @"rightSpacing" : @(8) }
+ views:@{ @"attributionButton" : self.attributionButton }]];
+
+ [super updateConstraints];
+}
+
+- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
+{
+ mbglMap->resize(rect.size.width, rect.size.height, view.contentScaleFactor, view.drawableWidth, view.drawableHeight);
+}
+
+- (void)layoutSubviews
+{
+ mbglMap->update();
+
+ [super layoutSubviews];
+}
+
+#pragma mark - Conversions -
+
++ (CGFloat)degreesToRadians:(CGFloat)degrees
+{
+ return degrees * M_PI / 180;
+}
+
++ (CGFloat)radiansToDegrees:(CGFloat)radians
+{
+ return radians * 180 / M_PI;
+}
+
+#pragma mark - Life Cycle -
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+
+- (void)appDidBackground:(NSNotification *)notification
+{
+ mbglMap->stop();
+
+ [self.glView deleteDrawable];
+}
+
+- (void)appWillForeground:(NSNotification *)notification
+{
+ [self.glView bindDrawable];
+
+ mbglMap->start();
+}
+
+#pragma mark - Gestures -
+
+- (void)handleCompassTapGesture:(id)sender
+{
+ [self resetNorthAnimated:YES];
+}
+
+#pragma clang diagnostic pop
+
+- (void)handlePanGesture:(UIPanGestureRecognizer *)pan
+{
+ if ( ! self.isScrollEnabled) return;
+
+ mbglMap->cancelTransitions();
+
+ if (pan.state == UIGestureRecognizerStateBegan)
+ {
+ self.centerPoint = CGPointMake(0, 0);
+ }
+ else if (pan.state == UIGestureRecognizerStateChanged)
+ {
+ CGPoint delta = CGPointMake([pan translationInView:pan.view].x - self.centerPoint.x,
+ [pan translationInView:pan.view].y - self.centerPoint.y);
+
+ mbglMap->moveBy(delta.x, delta.y);
+
+ self.centerPoint = CGPointMake(self.centerPoint.x + delta.x, self.centerPoint.y + delta.y);
+ }
+ else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled)
+ {
+ CGPoint velocity = [pan velocityInView:pan.view];
+ CGFloat duration = 0;
+
+ if ( ! CGPointEqualToPoint(velocity, CGPointZero))
+ {
+ CGFloat ease = 0.25;
+
+ velocity.x = velocity.x * ease;
+ velocity.y = velocity.y * ease;
+
+ CGFloat speed = sqrt(velocity.x * velocity.x + velocity.y * velocity.y);
+ CGFloat deceleration = 2500;
+ duration = speed / (deceleration * ease);
+ }
+
+ CGPoint offset = CGPointMake(velocity.x * duration / 2, velocity.y * duration / 2);
+
+ mbglMap->moveBy(offset.x, offset.y, secondsAsDuration(duration));
+
+ if (duration)
+ {
+ self.animatingGesture = YES;
+
+ __weak MGLMapView *weakSelf = self;
+
+ [self animateWithDelay:duration animations:^
+ {
+ weakSelf.animatingGesture = NO;
+
+ [weakSelf notifyMapChange:@(mbgl::MapChangeRegionDidChangeAnimated)];
+ }];
+ }
+ }
+}
+
+- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch
+{
+ if ( ! self.isZoomEnabled) return;
+
+ if (mbglMap->getZoom() <= mbglMap->getMinZoom() && pinch.scale < 1) return;
+
+ mbglMap->cancelTransitions();
+
+ if (pinch.state == UIGestureRecognizerStateBegan)
+ {
+ mbglMap->startScaling();
+
+ self.scale = mbglMap->getScale();
+ }
+ else if (pinch.state == UIGestureRecognizerStateChanged)
+ {
+ CGFloat newScale = self.scale * pinch.scale;
+
+ if (log2(newScale) < mbglMap->getMinZoom()) return;
+
+ double scale = mbglMap->getScale();
+
+ mbglMap->scaleBy(newScale / scale, [pinch locationInView:pinch.view].x, [pinch locationInView:pinch.view].y);
+ }
+ else if (pinch.state == UIGestureRecognizerStateEnded || pinch.state == UIGestureRecognizerStateCancelled)
+ {
+ mbglMap->stopScaling();
+
+ [self unrotateIfNeededAnimated:YES];
+
+ [self notifyMapChange:@(mbgl::MapChangeRegionDidChangeAnimated)];
+ }
+}
+
+- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate
+{
+ if ( ! self.isRotateEnabled) return;
+
+ mbglMap->cancelTransitions();
+
+ if (rotate.state == UIGestureRecognizerStateBegan)
+ {
+ mbglMap->startRotating();
+
+ self.angle = [MGLMapView degreesToRadians:mbglMap->getBearing()] * -1;
+ }
+ else if (rotate.state == UIGestureRecognizerStateChanged)
+ {
+ CGFloat newDegrees = [MGLMapView radiansToDegrees:(self.angle + rotate.rotation)] * -1;
+
+ // constrain to +/-30 degrees when merely rotating like Apple does
+ //
+ if ( ! self.isRotationAllowed && fabsf(self.pinch.scale) < 10)
+ {
+ newDegrees = fminf(newDegrees, 30);
+ newDegrees = fmaxf(newDegrees, -30);
+ }
+
+ mbglMap->setBearing(newDegrees,
+ [rotate locationInView:rotate.view].x,
+ [rotate locationInView:rotate.view].y);
+ }
+ else if (rotate.state == UIGestureRecognizerStateEnded || rotate.state == UIGestureRecognizerStateCancelled)
+ {
+ mbglMap->stopRotating();
+
+ [self unrotateIfNeededAnimated:YES];
+
+ [self notifyMapChange:@(mbgl::MapChangeRegionDidChangeAnimated)];
+ }
+}
+
+- (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap
+{
+ if ( ! self.isZoomEnabled) return;
+
+ mbglMap->cancelTransitions();
+
+ if (doubleTap.state == UIGestureRecognizerStateEnded)
+ {
+ mbglMap->scaleBy(2, [doubleTap locationInView:doubleTap.view].x, [doubleTap locationInView:doubleTap.view].y, secondsAsDuration(MGLAnimationDuration));
+
+ self.animatingGesture = YES;
+
+ __weak MGLMapView *weakSelf = self;
+
+ [self animateWithDelay:MGLAnimationDuration animations:^
+ {
+ weakSelf.animatingGesture = NO;
+
+ [weakSelf unrotateIfNeededAnimated:YES];
+
+ [weakSelf notifyMapChange:@(mbgl::MapChangeRegionDidChangeAnimated)];
+ }];
+ }
+}
+
+- (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap
+{
+ if ( ! self.isZoomEnabled) return;
+
+ if (mbglMap->getZoom() == mbglMap->getMinZoom()) return;
+
+ mbglMap->cancelTransitions();
+
+ if (twoFingerTap.state == UIGestureRecognizerStateEnded)
+ {
+ mbglMap->scaleBy(0.5, [twoFingerTap locationInView:twoFingerTap.view].x, [twoFingerTap locationInView:twoFingerTap.view].y, secondsAsDuration(MGLAnimationDuration));
+
+ self.animatingGesture = YES;
+
+ __weak MGLMapView *weakSelf = self;
+
+ [self animateWithDelay:MGLAnimationDuration animations:^
+ {
+ weakSelf.animatingGesture = NO;
+
+ [weakSelf unrotateIfNeededAnimated:YES];
+
+ [weakSelf notifyMapChange:@(mbgl::MapChangeRegionDidChangeAnimated)];
+ }];
+ }
+}
+
+- (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom
+{
+ if ( ! self.isZoomEnabled) return;
+
+ mbglMap->cancelTransitions();
+
+ if (quickZoom.state == UIGestureRecognizerStateBegan)
+ {
+ self.scale = mbglMap->getScale();
+
+ self.quickZoomStart = [quickZoom locationInView:quickZoom.view].y;
+ }
+ else if (quickZoom.state == UIGestureRecognizerStateChanged)
+ {
+ CGFloat distance = self.quickZoomStart - [quickZoom locationInView:quickZoom.view].y;
+
+ CGFloat newZoom = log2f(self.scale) + (distance / 100);
+
+ if (newZoom < mbglMap->getMinZoom()) return;
+
+ mbglMap->scaleBy(powf(2, newZoom) / mbglMap->getScale(), self.bounds.size.width / 2, self.bounds.size.height / 2);
+ }
+ else if (quickZoom.state == UIGestureRecognizerStateEnded || quickZoom.state == UIGestureRecognizerStateCancelled)
+ {
+ [self unrotateIfNeededAnimated:YES];
+
+ [self notifyMapChange:@(mbgl::MapChangeRegionDidChangeAnimated)];
+ }
+}
+
+- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
+{
+ NSArray *validSimultaneousGestures = @[ self.pan, self.pinch, self.rotate ];
+
+ return ([validSimultaneousGestures containsObject:gestureRecognizer] && [validSimultaneousGestures containsObject:otherGestureRecognizer]);
+}
+
+#pragma mark - Settings -
+
+- (void)tintColorDidChange
+{
+ for (UIView *subview in self.subviews)
+ {
+ if ([subview respondsToSelector:@selector(setTintColor:)])
+ {
+ subview.tintColor = self.tintColor;
+ }
+ }
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+
+- (void)showAttribution:(id)sender
+{
+ [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://www.mapbox.com/about/maps/"]];
+}
+
+#pragma clang diagnostic pop
+
+- (void)setDebugActive:(BOOL)debugActive
+{
+ mbglMap->setDebug(debugActive);
+}
+
+- (BOOL)isDebugActive
+{
+ return mbglMap->getDebug();
+}
+
+- (void)resetNorth
+{
+ [self resetNorthAnimated:YES];
+}
+
+- (void)resetNorthAnimated:(BOOL)animated
+{
+ CGFloat duration = (animated ? MGLAnimationDuration : 0);
+
+ mbglMap->setBearing(0, secondsAsDuration(duration));
+
+ [UIView animateWithDuration:duration
+ animations:^
+ {
+ self.compass.transform = CGAffineTransformIdentity;
+ }
+ completion:^(BOOL finished)
+ {
+ if (finished)
+ {
+ [UIView animateWithDuration:MGLAnimationDuration
+ animations:^
+ {
+ self.compass.alpha = 0;
+ }];
+ }
+ }];
+}
+
+- (void)resetPosition
+{
+ mbglMap->resetPosition();
+}
+
+- (void)toggleDebug
+{
+ mbglMap->toggleDebug();
+}
+
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated
+{
+ CGFloat duration = (animated ? MGLAnimationDuration : 0);
+
+ mbglMap->setLonLat(coordinate.longitude, coordinate.latitude, secondsAsDuration(duration));
+}
+
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
+{
+ [self setCenterCoordinate:centerCoordinate animated:NO];
+}
+
+- (CLLocationCoordinate2D)centerCoordinate
+{
+ double lon, lat;
+ mbglMap->getLonLat(lon, lat);
+
+ return CLLocationCoordinate2DMake(lat, lon);
+}
+
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated
+{
+ CGFloat duration = (animated ? MGLAnimationDuration : 0);
+
+ mbglMap->setLonLatZoom(centerCoordinate.longitude, centerCoordinate.latitude, zoomLevel, secondsAsDuration(duration));
+
+ [self unrotateIfNeededAnimated:animated];
+}
+
+- (double)zoomLevel
+{
+ return mbglMap->getZoom();
+}
+
+- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated
+{
+ CGFloat duration = (animated ? MGLAnimationDuration : 0);
+
+ mbglMap->setZoom(zoomLevel, secondsAsDuration(duration));
+
+ [self unrotateIfNeededAnimated:animated];
+}
+
+- (void)setZoomLevel:(double)zoomLevel
+{
+ [self setZoomLevel:zoomLevel animated:NO];
+}
+
+- (CLLocationDirection)direction
+{
+ double direction = mbglMap->getBearing() * -1;
+
+ while (direction > 360) direction -= 360;
+ while (direction < 0) direction += 360;
+
+ return direction;
+}
+
+- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated
+{
+ if ( ! animated && ! self.rotationAllowed) return;
+
+ CGFloat duration = (animated ? MGLAnimationDuration : 0);
+
+ mbglMap->setBearing(direction * -1, secondsAsDuration(duration));
+}
+
+- (void)setDirection:(CLLocationDirection)direction
+{
+ [self setDirection:direction animated:NO];
+}
+
+#pragma mark - Styling -
+
+- (NSDictionary *)getRawStyle
+{
+ const std::string styleJSON = mbglMap->getStyleJSON();
+
+ return [NSJSONSerialization JSONObjectWithData:[@(styleJSON.c_str()) dataUsingEncoding:[NSString defaultCStringEncoding]] options:0 error:nil];
+}
+
+- (void)setRawStyle:(NSDictionary *)style
+{
+ NSData *data = [NSJSONSerialization dataWithJSONObject:style options:0 error:nil];
+
+ [self setStyleJSON:[[NSString alloc] initWithData:data encoding:[NSString defaultCStringEncoding]]];
+}
+
+- (NSArray *)bundledStyleNames
+{
+ NSString *stylesPath = [[MGLMapView resourceBundlePath] stringByAppendingString:@"/styles"];
+
+ NSArray *styleNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:stylesPath error:nil];
+
+ return styleNames;
+}
+
+- (void)useBundledStyleNamed:(NSString *)styleName
+{
+ [self setStyleURL:[NSString stringWithFormat:@"styles/%@.json", styleName]];
+}
+
+- (NSArray *)getStyleOrderedLayerNames
+{
+ return [[self getRawStyle] valueForKeyPath:@"layers.id"];
+}
+
+- (void)setStyleOrderedLayerNames:(NSArray *)orderedLayerNames
+{
+ NSMutableDictionary *style = [[self getRawStyle] deepMutableCopy];
+ NSArray *oldLayers = style[@"layers"];
+ NSMutableArray *newLayers = [NSMutableArray array];
+
+ if ([orderedLayerNames count] != [[oldLayers valueForKeyPath:@"id"] count])
+ {
+ [NSException raise:@"invalid layer count"
+ format:@"new layer count (%lu) should equal existing layer count (%lu)",
+ (unsigned long)[orderedLayerNames count],
+ (unsigned long)[[oldLayers valueForKeyPath:@"id"] count]];
+ }
+ else
+ {
+ for (NSString *newLayerName in orderedLayerNames)
+ {
+ if ( ! [[oldLayers valueForKeyPath:@"id"] containsObject:newLayerName])
+ {
+ [NSException raise:@"invalid layer name"
+ format:@"layer name %@ unknown",
+ newLayerName];
+ }
+ else
+ {
+ NSDictionary *newLayer = [oldLayers objectAtIndex:[[oldLayers valueForKeyPath:@"id"] indexOfObject:newLayerName]];
+ [newLayers addObject:newLayer];
+ }
+ }
+ }
+
+ [style setValue:newLayers forKey:@"layers"];
+
+ [self setRawStyle:style];
+}
+
+- (NSArray *)getAppliedStyleClasses
+{
+ NSMutableArray *returnArray = [NSMutableArray array];
+
+ const std::vector<std::string> &appliedClasses = mbglMap->getClasses();
+
+ for (auto class_it = appliedClasses.begin(); class_it != appliedClasses.end(); class_it++)
+ {
+ [returnArray addObject:@(class_it->c_str())];
+ }
+
+ return returnArray;
+}
+
+- (void)setAppliedStyleClasses:(NSArray *)appliedClasses
+{
+ [self setAppliedStyleClasses:appliedClasses transitionDuration:0];
+}
+
+- (void)setAppliedStyleClasses:(NSArray *)appliedClasses transitionDuration:(NSTimeInterval)transitionDuration
+{
+ std::vector<std::string> newAppliedClasses;
+
+ for (NSString *appliedClass in appliedClasses)
+ {
+ newAppliedClasses.insert(newAppliedClasses.end(), [appliedClass cStringUsingEncoding:[NSString defaultCStringEncoding]]);
+ }
+
+ mbglMap->setDefaultTransitionDuration(secondsAsDuration(transitionDuration));
+ mbglMap->setClasses(newAppliedClasses);
+}
+
+- (NSString *)getKeyTypeForLayer:(NSString *)layerName
+{
+ NSDictionary *style = [self getRawStyle];
+
+ NSString *bucketType;
+
+ if ([layerName isEqualToString:@"background"])
+ {
+ bucketType = @"background";
+ }
+ else
+ {
+ for (NSDictionary *layer in style[@"structure"])
+ {
+ if ([layer[@"name"] isEqualToString:layerName])
+ {
+ bucketType = style[@"buckets"][layer[@"bucket"]][@"type"];
+ break;
+ }
+ }
+ }
+
+ NSString *keyType;
+
+ if ([bucketType isEqualToString:@"fill"])
+ {
+ keyType = MGLStyleKeyFill;
+ }
+ else if ([bucketType isEqualToString:@"line"])
+ {
+ keyType = MGLStyleKeyLine;
+ }
+ else if ([bucketType isEqualToString:@"point"])
+ {
+ keyType = MGLStyleKeyIcon;
+ }
+ else if ([bucketType isEqualToString:@"text"])
+ {
+ keyType = MGLStyleKeyText;
+ }
+ else if ([bucketType isEqualToString:@"raster"])
+ {
+ keyType = MGLStyleKeyRaster;
+ }
+ else if ([bucketType isEqualToString:@"composite"])
+ {
+ keyType = MGLStyleKeyComposite;
+ }
+ else if ([bucketType isEqualToString:@"background"])
+ {
+ keyType = MGLStyleKeyBackground;
+ }
+ else
+ {
+ [NSException raise:@"invalid bucket type"
+ format:@"bucket type %@ unknown",
+ bucketType];
+ }
+
+ return keyType;
+}
+
+- (NSDictionary *)getStyleDescriptionForLayer:(NSString *)layerName inClass:(NSString *)className
+{
+ NSDictionary *style = [self getRawStyle];
+
+ if ( ! [[style valueForKeyPath:@"classes.name"] containsObject:className])
+ {
+ [NSException raise:@"invalid class name"
+ format:@"class name %@ unknown",
+ className];
+ }
+
+ NSUInteger classNumber = [[style valueForKeyPath:@"classes.name"] indexOfObject:className];
+
+ if ( ! [[style[@"classes"][classNumber][@"layers"] allKeys] containsObject:layerName])
+ {
+ // layer specified in structure, but not styled
+ //
+ return nil;
+ }
+
+ NSDictionary *layerStyle = style[@"classes"][classNumber][@"layers"][layerName];
+
+ NSMutableDictionary *styleDescription = [NSMutableDictionary dictionary];
+
+ for (NSString *keyName in [layerStyle allKeys])
+ {
+ id value = layerStyle[keyName];
+
+ while ([[style[@"constants"] allKeys] containsObject:value])
+ {
+ value = style[@"constants"][value];
+ }
+
+ if ([[self.allowedStyleTypes[MGLStyleKeyGeneric] allKeys] containsObject:keyName])
+ {
+ [styleDescription setValue:[self typedPropertyForKeyName:keyName
+ ofType:MGLStyleKeyGeneric
+ withValue:value]
+ forKey:keyName];
+ }
+
+ NSString *keyType = [self getKeyTypeForLayer:layerName];
+
+ if ([[self.allowedStyleTypes[keyType] allKeys] containsObject:keyName])
+ {
+ [styleDescription setValue:[self typedPropertyForKeyName:keyName
+ ofType:keyType
+ withValue:value]
+ forKey:keyName];
+ }
+ }
+
+ return styleDescription;
+}
+
+- (NSDictionary *)typedPropertyForKeyName:(NSString *)keyName ofType:(NSString *)keyType withValue:(id)value
+{
+ if ( ! [[self.allowedStyleTypes[keyType] allKeys] containsObject:keyName])
+ {
+ [NSException raise:@"invalid property name"
+ format:@"property name %@ unknown",
+ keyName];
+ }
+
+ NSArray *typeInfo = self.allowedStyleTypes[keyType][keyName];
+
+ if ([value isKindOfClass:[NSArray class]] && ! [typeInfo containsObject:MGLStyleValueTypeColor])
+ {
+ if ([typeInfo containsObject:MGLStyleValueFunctionAllowed])
+ {
+ if ([[(NSArray *)value firstObject] isKindOfClass:[NSString class]])
+ {
+ NSString *functionType;
+
+ if ([[(NSArray *)value firstObject] isEqualToString:@"linear"])
+ {
+ functionType = MGLStyleValueTypeFunctionLinear;
+ }
+ else if ([[(NSArray *)value firstObject] isEqualToString:@"stops"])
+ {
+ functionType = MGLStyleValueTypeFunctionStops;
+ }
+ else if ([[(NSArray *)value firstObject] isEqualToString:@"exponential"])
+ {
+ functionType = MGLStyleValueTypeFunctionExponential;
+ }
+ else if ([[(NSArray *)value firstObject] isEqualToString:@"min"])
+ {
+ functionType = MGLStyleValueTypeFunctionMinimumZoom;
+ }
+ else if ([[(NSArray *)value firstObject] isEqualToString:@"max"])
+ {
+ functionType = MGLStyleValueTypeFunctionMaximumZoom;
+ }
+
+ if (functionType)
+ {
+ return @{ @"type" : functionType,
+ @"value" : value };
+ }
+ }
+ }
+ else if ([typeInfo containsObject:MGLStyleValueTypeNumberPair])
+ {
+ return @{ @"type" : MGLStyleValueTypeNumberPair,
+ @"value" : value };
+ }
+ }
+ else if ([typeInfo containsObject:MGLStyleValueTypeNumber])
+ {
+ return @{ @"type" : MGLStyleValueTypeNumber,
+ @"value" : value };
+ }
+ else if ([typeInfo containsObject:MGLStyleValueTypeBoolean])
+ {
+ return @{ @"type" : MGLStyleValueTypeBoolean,
+ @"value" : @([(NSString *)value boolValue]) };
+ }
+ else if ([typeInfo containsObject:MGLStyleValueTypeString])
+ {
+ return @{ @"type" : MGLStyleValueTypeString,
+ @"value" : value };
+ }
+ else if ([typeInfo containsObject:MGLStyleValueTypeColor])
+ {
+ UIColor *color;
+
+ if ([(NSString *)value hasPrefix:@"#"])
+ {
+ color = [UIColor colorWithHexString:value];
+ }
+ else if ([(NSString *)value hasPrefix:@"rgb"])
+ {
+ color = [UIColor colorWithRGBAString:value];
+ }
+ else if ([(NSString *)value hasPrefix:@"hsl"])
+ {
+ [NSException raise:@"invalid color format"
+ format:@"HSL color format not yet supported natively"];
+ }
+ else if ([value isKindOfClass:[NSArray class]] && [(NSArray *)value count] == 4)
+ {
+ color = [UIColor colorWithRed:[value[0] floatValue]
+ green:[value[1] floatValue]
+ blue:[value[2] floatValue]
+ alpha:[value[3] floatValue]];
+ }
+ else if ([[UIColor class] respondsToSelector:NSSelectorFromString([NSString stringWithFormat:@"%@Color", [(NSString *)value lowercaseString]])])
+ {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+
+ color = [[UIColor class] performSelector:NSSelectorFromString([NSString stringWithFormat:@"%@Color", [(NSString *)value lowercaseString]])];
+
+#pragma clang diagnostic pop
+ }
+
+ return @{ @"type" : MGLStyleValueTypeColor,
+ @"value" : color };
+ }
+
+ return nil;
+}
+
+- (void)setStyleDescription:(NSDictionary *)styleDescription forLayer:(NSString *)layerName inClass:(NSString *)className
+{
+#pragma unused(className)
+
+ NSMutableDictionary *convertedStyle = [NSMutableDictionary dictionary];
+
+ for (NSString *key in [styleDescription allKeys])
+ {
+ NSArray *styleParameters = nil;
+
+ if ([[self.allowedStyleTypes[MGLStyleKeyGeneric] allKeys] containsObject:key])
+ {
+ styleParameters = self.allowedStyleTypes[MGLStyleKeyGeneric][key];
+ }
+ else
+ {
+ NSString *keyType = [self getKeyTypeForLayer:layerName];
+
+ if ([[self.allowedStyleTypes[keyType] allKeys] containsObject:key])
+ {
+ styleParameters = self.allowedStyleTypes[keyType][key];
+ }
+ }
+
+ if (styleParameters)
+ {
+ if ([styleDescription[key][@"value"] isKindOfClass:[MGLStyleFunctionValue class]])
+ {
+ convertedStyle[key] = [(MGLStyleFunctionValue *)styleDescription[key][@"value"] rawStyle];
+ }
+ else if ([styleParameters containsObject:styleDescription[key][@"type"]])
+ {
+ NSString *valueType = styleDescription[key][@"type"];
+
+ if ([valueType isEqualToString:MGLStyleValueTypeColor])
+ {
+ convertedStyle[key] = [@"#" stringByAppendingString:[(UIColor *)styleDescription[key][@"value"] hexStringFromColor]];
+ }
+ else
+ {
+ // the rest (bool/number/pair/string) are already JSON-convertible types
+ //
+ convertedStyle[key] = styleDescription[key][@"value"];
+ }
+ }
+ }
+ else
+ {
+ [NSException raise:@"invalid style description format"
+ format:@"unable to parse key '%@'",
+ key];
+ }
+ }
+
+// NSMutableDictionary *style = [[self getRawStyle] deepMutableCopy];
+//
+// NSUInteger classIndex = [[[self getAllStyleClasses] valueForKey:@"name"] indexOfObject:className];
+//
+// style[@"classes"][classIndex][@"layers"][layerName] = convertedStyle;
+//
+// [self setRawStyle:style];
+}
+
+- (NSDictionary *)allowedStyleTypes
+{
+ static NSDictionary *MGLStyleAllowedTypes = @{
+ MGLStyleKeyGeneric : @{
+ @"enabled" : @[ MGLStyleValueTypeBoolean, MGLStyleValueFunctionAllowed ],
+ @"translate" : @[ MGLStyleValueTypeNumberPair, MGLStyleValueFunctionAllowed ],
+ @"translate-anchor" : @[ MGLStyleValueTypeString, MGLStyleValueFunctionAllowed ],
+ @"opacity" : @[ MGLStyleValueTypeNumber, MGLStyleValueFunctionAllowed ],
+ @"prerender" : @[ MGLStyleValueTypeBoolean ],
+ @"prerender-buffer" : MGLStyleValueTypeNumber,
+ @"prerender-size" : @[ MGLStyleValueTypeNumber ],
+ @"prerender-blur" : @[ MGLStyleValueTypeNumber ] },
+ MGLStyleKeyFill : @{
+ @"color" : @[ MGLStyleValueTypeColor ],
+ @"stroke" : @[ MGLStyleValueTypeColor ],
+ @"antialias" : @[ MGLStyleValueTypeBoolean ],
+ @"image" : @[ MGLStyleValueTypeString ] },
+ MGLStyleKeyLine : @{
+ @"color" : @[ MGLStyleValueTypeColor ],
+ @"width" : @[ MGLStyleValueTypeNumber, MGLStyleValueFunctionAllowed ],
+ @"dasharray" : @[ MGLStyleValueTypeNumberPair, MGLStyleValueFunctionAllowed ] },
+ MGLStyleKeyIcon : @{
+ @"color" : @[ MGLStyleValueTypeColor ],
+ @"image" : @[ MGLStyleValueTypeString ],
+ @"size" : @[ MGLStyleValueTypeNumber, MGLStyleValueFunctionAllowed ],
+ @"radius" : @[ MGLStyleValueTypeNumber, MGLStyleValueFunctionAllowed],
+ @"blur" : @[ MGLStyleValueTypeNumber, MGLStyleValueFunctionAllowed ] },
+ MGLStyleKeyText : @{
+ @"color" : @[ MGLStyleValueTypeColor ],
+ @"stroke" : @[ MGLStyleValueTypeColor ],
+ @"strokeWidth" : @[ MGLStyleValueTypeNumber, MGLStyleValueFunctionAllowed ],
+ @"strokeBlur" : @[ MGLStyleValueTypeNumber, MGLStyleValueFunctionAllowed ],
+ @"size" : @[ MGLStyleValueTypeNumber, MGLStyleValueFunctionAllowed ],
+ @"rotate" : @[ MGLStyleValueTypeNumber, MGLStyleValueFunctionAllowed ],
+ @"alwaysVisible" : @[ MGLStyleValueTypeBoolean ] },
+ MGLStyleKeyRaster : @{},
+ MGLStyleKeyComposite : @{},
+ MGLStyleKeyBackground : @{
+ @"color" : @[ MGLStyleValueTypeColor ] }
+ };
+
+ return MGLStyleAllowedTypes;
+}
+
+#pragma mark - Utility -
+
+- (void)animateWithDelay:(NSTimeInterval)delay animations:(void (^)(void))animations
+{
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), animations);
+}
+
+- (BOOL)isRotationAllowed
+{
+ return (self.zoomLevel > 3);
+}
+
+// correct rotations to north as needed
+//
+- (void)unrotateIfNeededAnimated:(BOOL)animated
+{
+ // don't worry about it in the midst of pinch or rotate gestures
+ //
+ if (self.pinch.state == UIGestureRecognizerStateChanged || self.rotate.state == UIGestureRecognizerStateChanged) return;
+
+ // but otherwise, do
+ //
+ if (self.direction != 0 && ! self.isRotationAllowed)
+ {
+ if (animated)
+ {
+ self.animatingGesture = YES;
+
+ self.userInteractionEnabled = NO;
+
+ __weak MGLMapView *weakSelf = self;
+
+ [self animateWithDelay:0.1 animations:^
+ {
+ [weakSelf resetNorthAnimated:YES];
+
+ [self animateWithDelay:MGLAnimationDuration animations:^
+ {
+ weakSelf.userInteractionEnabled = YES;
+
+ self.animatingGesture = NO;
+ }];
+
+ }];
+ }
+ else
+ {
+ [self resetNorthAnimated:NO];
+ }
+ }
+}
+
+- (void)unsuspendRegionChangeDelegateQueue
+{
+ @synchronized (self.regionChangeDelegateQueue)
+ {
+ [self.regionChangeDelegateQueue setSuspended:NO];
+ }
+}
+
+- (void)notifyMapChange:(NSNumber *)change
+{
+ switch ([change unsignedIntegerValue])
+ {
+ case mbgl::MapChangeRegionWillChange:
+ case mbgl::MapChangeRegionWillChangeAnimated:
+ {
+ BOOL animated = ([change unsignedIntegerValue] == mbgl::MapChangeRegionWillChangeAnimated);
+
+ @synchronized (self.regionChangeDelegateQueue)
+ {
+ if ([self.regionChangeDelegateQueue operationCount] == 0)
+ {
+ if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)])
+ {
+ [self.delegate mapView:self regionWillChangeAnimated:animated];
+ }
+ }
+
+ [self.regionChangeDelegateQueue setSuspended:YES];
+
+ if ([self.regionChangeDelegateQueue operationCount] == 0)
+ {
+ [self.regionChangeDelegateQueue addOperationWithBlock:^
+ {
+ dispatch_async(dispatch_get_main_queue(), ^
+ {
+ if ([self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)])
+ {
+ [self.delegate mapView:self regionDidChangeAnimated:animated];
+ }
+ });
+ }];
+ }
+ }
+ break;
+ }
+ case mbgl::MapChangeRegionDidChange:
+ case mbgl::MapChangeRegionDidChangeAnimated:
+ {
+ [self updateCompass];
+
+ if (self.pan.state == UIGestureRecognizerStateChanged ||
+ self.pinch.state == UIGestureRecognizerStateChanged ||
+ self.rotate.state == UIGestureRecognizerStateChanged ||
+ self.quickZoom.state == UIGestureRecognizerStateChanged) return;
+
+ if (self.isAnimatingGesture) return;
+
+ [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(unsuspendRegionChangeDelegateQueue) object:nil];
+ [self performSelector:@selector(unsuspendRegionChangeDelegateQueue) withObject:nil afterDelay:0];
+
+ break;
+ }
+ case mbgl::MapChangeWillStartLoadingMap:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewWillStartLoadingMap:)])
+ {
+ [self.delegate mapViewWillStartLoadingMap:self];
+ }
+ break;
+ }
+ case mbgl::MapChangeDidFinishLoadingMap:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewDidFinishLoadingMap:)])
+ {
+ [self.delegate mapViewDidFinishLoadingMap:self];
+ }
+ break;
+ }
+ case mbgl::MapChangeDidFailLoadingMap:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewDidFailLoadingMap:withError::)])
+ {
+ [self.delegate mapViewDidFailLoadingMap:self withError:nil];
+ }
+ break;
+ }
+ case mbgl::MapChangeWillStartRenderingMap:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingMap:)])
+ {
+ [self.delegate mapViewWillStartRenderingMap:self];
+ }
+ break;
+ }
+ case mbgl::MapChangeDidFinishRenderingMap:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)])
+ {
+ [self.delegate mapViewDidFinishRenderingMap:self fullyRendered:NO];
+ }
+ break;
+ }
+ case mbgl::MapChangeDidFinishRenderingMapFullyRendered:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)])
+ {
+ [self.delegate mapViewDidFinishRenderingMap:self fullyRendered:YES];
+ }
+ break;
+ }
+ }
+}
+
+- (void)updateCompass
+{
+ double degrees = mbglMap->getBearing() * -1;
+ while (degrees >= 360) degrees -= 360;
+ while (degrees < 0) degrees += 360;
+
+ self.compass.transform = CGAffineTransformMakeRotation([MGLMapView degreesToRadians:degrees]);
+
+ if (mbglMap->getBearing() && self.compass.alpha < 1)
+ {
+ [UIView animateWithDuration:MGLAnimationDuration
+ delay:0
+ options:UIViewAnimationOptionBeginFromCurrentState
+ animations:^
+ {
+ self.compass.alpha = 1;
+ }
+ completion:nil];
+ }
+}
+
++ (UIImage *)resourceImageNamed:(NSString *)imageName
+{
+ if ( ! [[imageName pathExtension] length])
+ {
+ imageName = [imageName stringByAppendingString:@".png"];
+ }
+
+ return [UIImage imageWithContentsOfFile:[MGLMapView pathForBundleResourceNamed:imageName ofType:nil inDirectory:@""]];
+}
+
++ (NSString *)pathForBundleResourceNamed:(NSString *)name ofType:(NSString *)extension inDirectory:(NSString *)directory
+{
+ NSString *path = [[NSBundle bundleWithPath:[MGLMapView resourceBundlePath]] pathForResource:name ofType:extension inDirectory:directory];
+
+ NSAssert(path, @"Resource not found in application.");
+
+ return path;
+}
+
++ (NSString *)resourceBundlePath
+{
+ NSString *resourceBundlePath = [[NSBundle bundleForClass:[MGLMapView class]] pathForResource:@"MapboxGL" ofType:@"bundle"];
+
+ if ( ! resourceBundlePath) resourceBundlePath = [[NSBundle mainBundle] bundlePath];
+
+ return resourceBundlePath;
+}
+
+- (void)swap
+{
+ if (mbglMap->needsSwap())
+ {
+ [self.glView display];
+ mbglMap->swapped();
+ }
+}
+
+class MBGLView : public mbgl::View
+{
+ public:
+ MBGLView(MGLMapView *nativeView_) : nativeView(nativeView_) {}
+ virtual ~MBGLView() {}
+
+
+ void notify()
+ {
+ // no-op
+ }
+
+ void notifyMapChange(mbgl::MapChange change, std::chrono::steady_clock::duration delay = std::chrono::steady_clock::duration::zero())
+ {
+ if (delay != std::chrono::steady_clock::duration::zero())
+ {
+ __weak MGLMapView *weakNativeView = nativeView;
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, std::chrono::duration_cast<std::chrono::nanoseconds>(delay).count()), dispatch_get_main_queue(), ^
+ {
+ [weakNativeView performSelector:@selector(notifyMapChange:)
+ withObject:@(change)
+ afterDelay:0];
+ });
+ }
+ else
+ {
+ dispatch_async(dispatch_get_main_queue(), ^
+ {
+ [nativeView performSelector:@selector(notifyMapChange:)
+ withObject:@(change)
+ afterDelay:0];
+ });
+ }
+ }
+
+ void activate()
+ {
+ [EAGLContext setCurrentContext:nativeView.context];
+ }
+
+ void deactivate()
+ {
+ [EAGLContext setCurrentContext:nil];
+ }
+
+ void swap()
+ {
+ [nativeView performSelectorOnMainThread:@selector(swap)
+ withObject:nil
+ waitUntilDone:NO];
+ }
+
+ private:
+ MGLMapView *nativeView = nullptr;
+};
+
+@end
diff --git a/platform/ios/MGLStyleFunctionValue.m b/platform/ios/MGLStyleFunctionValue.m
new file mode 100644
index 0000000000..a319114eef
--- /dev/null
+++ b/platform/ios/MGLStyleFunctionValue.m
@@ -0,0 +1,190 @@
+#import "MGLStyleFunctionValue.h"
+
+#import "MGLTypes.h"
+
+@interface MGLStyleFunctionValue ()
+
+@property (nonatomic) NSString *functionType;
+@property (nonatomic) NSDictionary *stops;
+@property (nonatomic) CGFloat zBase;
+@property (nonatomic) CGFloat val;
+@property (nonatomic) CGFloat slope;
+@property (nonatomic) CGFloat min;
+@property (nonatomic) CGFloat max;
+@property (nonatomic) CGFloat minimumZoom;
+@property (nonatomic) CGFloat maximumZoom;
+
+- (NSDictionary *)rawStyle;
+
+@end
+
+@implementation MGLStyleFunctionValue
+
+- (id)initWithFunctionType:(NSString *)functionType
+ stops:(NSDictionary *)stops
+ zBase:(CGFloat)zBase
+ val:(CGFloat)val
+ slope:(CGFloat)slope
+ min:(CGFloat)min
+ max:(CGFloat)max
+ minimumZoom:(CGFloat)minimumZoom
+ maximumZoom:(CGFloat)maximumZoom
+{
+ self = [super init];
+
+ if (self)
+ {
+ _functionType = functionType;
+ _stops = stops;
+ _zBase = zBase;
+ _val = val;
+ _slope = slope;
+ _min = min;
+ _max = max;
+ _minimumZoom = minimumZoom;
+ _maximumZoom = maximumZoom;
+ }
+
+ return self;
+}
+
+- (id)rawStyle
+{
+ if ([self.functionType isEqualToString:MGLStyleValueTypeFunctionMinimumZoom])
+ {
+ return @[ @"min", @(self.minimumZoom) ];
+ }
+ else if ([self.functionType isEqualToString:MGLStyleValueTypeFunctionMaximumZoom])
+ {
+ return @[ @"max", @(self.maximumZoom) ];
+ }
+ else if ([self.functionType isEqualToString:MGLStyleValueTypeFunctionLinear])
+ {
+ return @[ @"linear", @(self.zBase), @(self.val), @(self.slope), @(self.min), @(self.max) ];
+ }
+ else if ([self.functionType isEqualToString:MGLStyleValueTypeFunctionExponential])
+ {
+ return @[ @"exponential", @(self.zBase), @(self.val), @(self.slope), @(self.min), @(self.max) ];
+ }
+ else if ([self.functionType isEqualToString:MGLStyleValueTypeFunctionStops])
+ {
+ NSMutableArray *returnArray = [NSMutableArray array];
+
+ for (NSNumber *z in [self.stops allKeys])
+ {
+ [returnArray addObject:@{ @"z" : z, @"val" : self.stops[z] }];
+ }
+
+ [returnArray insertObject:@"stops" atIndex:0];
+
+ return returnArray;
+ }
+
+ return nil;
+}
+
++ (instancetype)stopsFunctionWithZoomLevelsAndValues:(NSNumber *)firstZoom, ...
+{
+ NSMutableArray *numbersArray = [NSMutableArray array];
+
+ va_list args;
+ va_start(args, firstZoom);
+
+ for (NSNumber *arg = firstZoom; arg != nil; arg = va_arg(args, NSNumber *))
+ {
+ [numbersArray addObject:arg];
+ }
+
+ va_end(args);
+
+ NSAssert([numbersArray count] % 2 == 0, @"invalid number of arguments");
+
+ NSMutableDictionary *stops = [NSMutableDictionary dictionary];
+
+ for (NSUInteger i = 0; i < [numbersArray count]; i = i + 2)
+ {
+ stops[numbersArray[i]] = stops[numbersArray[i + 1]];
+ }
+
+ return [[self alloc] initWithFunctionType:MGLStyleValueTypeFunctionStops
+ stops:stops
+ zBase:0
+ val:0
+ slope:0
+ min:0
+ max:0
+ minimumZoom:0
+ maximumZoom:0];
+}
+
++ (instancetype)linearFunctionWithBaseZoomLevel:(CGFloat)zBase
+ initialValue:(CGFloat)val
+ slope:(CGFloat)slope
+ minimumValue:(CGFloat)min
+ maximumValue:(CGFloat)max
+{
+ NSAssert(zBase >= 0 && zBase <= 18, @"invalid base zoom level");
+ NSAssert(min < max, @"minimum value must be less than maximum value");
+
+ return [[self alloc] initWithFunctionType:MGLStyleValueTypeFunctionLinear
+ stops:nil
+ zBase:zBase
+ val:val
+ slope:slope
+ min:min
+ max:max
+ minimumZoom:0
+ maximumZoom:0];
+}
+
++ (instancetype)exponentialFunctionWithBaseZoomLevel:(CGFloat)zBase
+ initialValue:(CGFloat)val
+ slope:(CGFloat)slope
+ minimumValue:(CGFloat)min
+ maximumValue:(CGFloat)max
+{
+ NSAssert(zBase >= 0 && zBase <= 18, @"invalid base zoom level");
+ NSAssert(min < max, @"minimum value must be less than maximum value");
+
+ return [[self alloc] initWithFunctionType:MGLStyleValueTypeFunctionExponential
+ stops:nil
+ zBase:zBase
+ val:val
+ slope:slope
+ min:min
+ max:max
+ minimumZoom:0
+ maximumZoom:0];
+}
+
++ (instancetype)minimumZoomLevelFunction:(CGFloat)minimumZoom
+{
+ NSAssert(minimumZoom >= 0 && minimumZoom <= 18, @"invalid minimum zoom value");
+
+ return [[self alloc] initWithFunctionType:MGLStyleValueTypeFunctionMinimumZoom
+ stops:nil
+ zBase:0
+ val:0
+ slope:0
+ min:0
+ max:0
+ minimumZoom:minimumZoom
+ maximumZoom:0];
+}
+
++ (instancetype)maximumZoomLevelFunction:(CGFloat)maximumZoom
+{
+ NSAssert(maximumZoom >= 0 && maximumZoom <= 18, @"invalid maximum zoom value");
+
+ return [[self alloc] initWithFunctionType:MGLStyleValueTypeFunctionMaximumZoom
+ stops:nil
+ zBase:0
+ val:0
+ slope:0
+ min:0
+ max:0
+ minimumZoom:0
+ maximumZoom:maximumZoom];
+}
+
+@end
diff --git a/platform/ios/MGLTypes.m b/platform/ios/MGLTypes.m
new file mode 100644
index 0000000000..2928bc2f4c
--- /dev/null
+++ b/platform/ios/MGLTypes.m
@@ -0,0 +1,24 @@
+#import "MGLTypes.h"
+
+NSString *const MGLStyleKeyGeneric = @"MGLStyleKeyGeneric";
+NSString *const MGLStyleKeyFill = @"MGLStyleKeyFill";
+NSString *const MGLStyleKeyLine = @"MGLStyleKeyLine";
+NSString *const MGLStyleKeyIcon = @"MGLStyleKeyIcon";
+NSString *const MGLStyleKeyText = @"MGLStyleKeyText";
+NSString *const MGLStyleKeyRaster = @"MGLStyleKeyRaster";
+NSString *const MGLStyleKeyComposite = @"MGLStyleKeyComposite";
+NSString *const MGLStyleKeyBackground = @"MGLStyleKeyBackground";
+
+NSString *const MGLStyleValueTypeBoolean = @"MGLStyleValueTypeBoolean";
+NSString *const MGLStyleValueTypeNumber = @"MGLStyleValueTypeNumber";
+NSString *const MGLStyleValueTypeNumberPair = @"MGLStyleValueTypeNumberPair";
+NSString *const MGLStyleValueTypeColor = @"MGLStyleValueTypeColor";
+NSString *const MGLStyleValueTypeString = @"MGLStyleValueTypeString";
+
+NSString *const MGLStyleValueFunctionAllowed = @"MGLStyleValueFunctionAllowed";
+
+NSString *const MGLStyleValueTypeFunctionMinimumZoom = @"MGLStyleValueTypeFunctionMinimumZoom";
+NSString *const MGLStyleValueTypeFunctionMaximumZoom = @"MGLStyleValueTypeFunctionMaximumZoom";
+NSString *const MGLStyleValueTypeFunctionLinear = @"MGLStyleValueTypeFunctionLinear";
+NSString *const MGLStyleValueTypeFunctionExponential = @"MGLStyleValueTypeFunctionExponential";
+NSString *const MGLStyleValueTypeFunctionStops = @"MGLStyleValueTypeFunctionStops";
diff --git a/platform/ios/NSArray+MGLAdditions.m b/platform/ios/NSArray+MGLAdditions.m
new file mode 100644
index 0000000000..2bac42ce0b
--- /dev/null
+++ b/platform/ios/NSArray+MGLAdditions.m
@@ -0,0 +1,10 @@
+#import "NSArray+MGLAdditions.h"
+
+@implementation NSArray (MGLAdditions)
+
+- (NSMutableArray *)deepMutableCopy
+{
+ return (NSMutableArray *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFArrayRef)self, kCFPropertyListMutableContainersAndLeaves));
+}
+
+@end
diff --git a/platform/ios/NSDictionary+MGLAdditions.m b/platform/ios/NSDictionary+MGLAdditions.m
new file mode 100644
index 0000000000..3228d56533
--- /dev/null
+++ b/platform/ios/NSDictionary+MGLAdditions.m
@@ -0,0 +1,10 @@
+#import "NSDictionary+MGLAdditions.h"
+
+@implementation NSDictionary (MGLAdditions)
+
+- (NSMutableDictionary *)deepMutableCopy
+{
+ return (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)self, kCFPropertyListMutableContainersAndLeaves));
+}
+
+@end
diff --git a/platform/ios/UIColor+MGLAdditions.m b/platform/ios/UIColor+MGLAdditions.m
new file mode 100644
index 0000000000..ae40735d15
--- /dev/null
+++ b/platform/ios/UIColor+MGLAdditions.m
@@ -0,0 +1,167 @@
+#import "UIColor+MGLAdditions.h"
+
+/* Portions based on Erica Sadun's uicolor-utilities
+ https://github.com/erica/uicolor-utilities */
+
+@interface UIColor (MGLAdditionsPrivate)
+
++ (UIColor *)colorWithRGBHex:(UInt32)hex;
+- (CGColorSpaceModel)colorSpaceModel;
+- (BOOL)canProvideRGBComponents;
+- (BOOL)red:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha;
+- (UInt32)rgbHex;
+
+@end
+
+@implementation UIColor (MGLAdditions)
+
++ (UIColor *)colorWithRGBAString:(NSString *)rgbaString
+{
+ UIColor *color;
+
+ NSString *numberString = [rgbaString stringByReplacingOccurrencesOfString:@"rgba("
+ withString:@""];
+ numberString = [numberString stringByReplacingOccurrencesOfString:@"rgb("
+ withString:@""];
+ numberString = [numberString stringByReplacingOccurrencesOfString:@")"
+ withString:@""];
+
+ NSArray *numbers = [numberString componentsSeparatedByString:@","];
+
+ if ([rgbaString hasPrefix:@"rgb("] && [numbers count] == 3)
+ {
+ color = [UIColor colorWithRed:[numbers[0] floatValue] / 255
+ green:[numbers[1] floatValue] / 255
+ blue:[numbers[2] floatValue] / 255
+ alpha:1.0];
+ }
+ else if ([rgbaString hasPrefix:@"rgba("] && [numbers count] == 4)
+ {
+ color = [UIColor colorWithRed:[numbers[0] floatValue] / 255
+ green:[numbers[1] floatValue] / 255
+ blue:[numbers[2] floatValue] / 255
+ alpha:[numbers[3] floatValue]];
+ }
+
+ return color;
+}
+
+- (NSString *)rgbaStringFromColor
+{
+ CGFloat r,g,b,a;
+
+ [self getRed:&r green:&g blue:&b alpha:&a];
+
+ r *= 255;
+ g *= 255;
+ b *= 255;
+ a *= 255;
+
+ return [NSString stringWithFormat:@"rgba(%lu,%lu,%lu,%lu)", (unsigned long)r, (unsigned long)g, (unsigned long)b, (unsigned long)a];
+}
+
++ (UIColor *)colorWithRGBHex:(UInt32)hex
+{
+ int r = (hex >> 16) & 0xFF;
+ int g = (hex >> 8) & 0xFF;
+ int b = (hex) & 0xFF;
+
+ return [UIColor colorWithRed:r / 255.0f
+ green:g / 255.0f
+ blue:b / 255.0f
+ alpha:1.0f];
+}
+
+- (CGColorSpaceModel)colorSpaceModel
+{
+ return CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor));
+}
+
+- (BOOL)canProvideRGBComponents
+{
+ switch (self.colorSpaceModel)
+ {
+ case kCGColorSpaceModelRGB:
+ case kCGColorSpaceModelMonochrome:
+ {
+ return YES;
+ }
+ default:
+ {
+ return NO;
+ }
+ }
+}
+
+- (BOOL)red:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha
+{
+ const CGFloat *components = CGColorGetComponents(self.CGColor);
+
+ CGFloat r,g,b,a;
+
+ switch (self.colorSpaceModel)
+ {
+ case kCGColorSpaceModelMonochrome:
+ {
+ r = g = b = components[0];
+ a = components[1];
+
+ break;
+ }
+ case kCGColorSpaceModelRGB:
+ {
+ r = components[0];
+ g = components[1];
+ b = components[2];
+ a = components[3];
+
+ break;
+ }
+ default:
+ {
+ return NO;
+ }
+ }
+
+ if (red) *red = r;
+ if (green) *green = g;
+ if (blue) *blue = b;
+ if (alpha) *alpha = a;
+
+ return YES;
+}
+
+- (UInt32)rgbHex
+{
+ NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use rgbHex");
+
+ CGFloat r, g, b, a;
+
+ if ( ! [self red:&r green:&g blue:&b alpha:&a])
+ return 0;
+
+ r = fminf(fmaxf(r, 0.0f), 1.0f);
+ g = fminf(fmaxf(g, 0.0f), 1.0f);
+ b = fminf(fmaxf(b, 0.0f), 1.0f);
+
+ return (((int)roundf(r * 255)) << 16) | (((int)roundf(g * 255)) << 8) | (((int)roundf(b * 255)));
+}
+
+- (NSString *)hexStringFromColor
+{
+ return [NSString stringWithFormat:@"%0.6X", (unsigned int)(self.rgbHex)];
+}
+
++ (UIColor *)colorWithHexString:(NSString *)hexString
+{
+ NSScanner *scanner = [NSScanner scannerWithString:[hexString stringByReplacingOccurrencesOfString:@"#" withString:@""]];
+
+ unsigned hexNum;
+
+ if ( ! [scanner scanHexInt:&hexNum])
+ return nil;
+
+ return [UIColor colorWithRGBHex:hexNum];
+}
+
+@end
diff --git a/platform/ios/resources/Compass.png b/platform/ios/resources/Compass.png
new file mode 100644
index 0000000000..fd3afe6f68
--- /dev/null
+++ b/platform/ios/resources/Compass.png
Binary files differ
diff --git a/platform/ios/resources/Compass@2x.png b/platform/ios/resources/Compass@2x.png
new file mode 100644
index 0000000000..58e7e08d24
--- /dev/null
+++ b/platform/ios/resources/Compass@2x.png
Binary files differ
diff --git a/platform/ios/resources/mapbox.png b/platform/ios/resources/mapbox.png
new file mode 100644
index 0000000000..01b5596b67
--- /dev/null
+++ b/platform/ios/resources/mapbox.png
Binary files differ
diff --git a/platform/ios/resources/mapbox@2x.png b/platform/ios/resources/mapbox@2x.png
new file mode 100644
index 0000000000..ff4ef2558b
--- /dev/null
+++ b/platform/ios/resources/mapbox@2x.png
Binary files differ
diff --git a/scripts/ios_travis/add-key.sh b/scripts/ios_travis/add-key.sh
new file mode 100755
index 0000000000..b581445303
--- /dev/null
+++ b/scripts/ios_travis/add-key.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+# This is all taken from http://www.objc.io/issue-6/travis-ci.html
+
+# Create a custom keychain
+security create-keychain -p travis ios-build.keychain
+
+# Make the custom keychain default, so xcodebuild will use it for signing
+security default-keychain -s ios-build.keychain
+
+# Unlock the keychain
+security unlock-keychain -p travis ios-build.keychain
+
+# Set keychain timeout to 1 hour for long builds
+# see http://www.egeek.me/2013/02/23/jenkins-and-xcode-user-interaction-is-not-allowed/
+security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain
+
+# Add certificates to keychain and allow codesign to access them
+security import ./scripts/ios_travis/apple.crt -k ~/Library/Keychains/ios-build.keychain -T /usr/bin/codesign
+security import ./scripts/ios_travis/ios-dist.cer -k ~/Library/Keychains/ios-build.keychain -T /usr/bin/codesign
+security import ./scripts/ios_travis/ios-dist.p12 -k ~/Library/Keychains/ios-build.keychain -P $IOS_KEY_PASSWORD -T /usr/bin/codesign
+
+# Put the provisioning profile in place
+mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
+cp "./scripts/ios_travis/$IOS_PROFILE_NAME.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/
diff --git a/scripts/ios_travis/apple.crt b/scripts/ios_travis/apple.crt
new file mode 100644
index 0000000000..0de099b869
--- /dev/null
+++ b/scripts/ios_travis/apple.crt
Binary files differ
diff --git a/scripts/ios_travis/ios-dist.cer.enc b/scripts/ios_travis/ios-dist.cer.enc
new file mode 100644
index 0000000000..dfdd0ca997
--- /dev/null
+++ b/scripts/ios_travis/ios-dist.cer.enc
@@ -0,0 +1,30 @@
+U2FsdGVkX1/Dn0fx6y0q6CHTK5UmCnel5Nw0kQlpeCqrqpWvXnYS//Ep0updcMIQ
+xj1hu+baBWjw7ccysFG/zcReSh/xgP6JrW7QKiA/kIQvqOvH0vhtzvDsnAQCB52T
+VWVy492mIwFRCBANVTPbdmBbVIeHAkeQcrVkFJJIKOv4dC+CFAw9ArHm8iYrg7FV
+FDK3cmrfR/pgvlQ1lQDa1C0vV1k/R5KuhqwDM3xMML1WbuBlR0RRHccs4xJRmRV7
+NuAuWg2Ob+p5gOW4L3+7PnBTBgxUZl2nbukd3oUlAPBOxgwwbgQSJPDvnsw2zmgp
+uv2X0Q0/fT9R8VxuoSXYkLkgIlOnVVRpyG4C4ou7HPS8VDiLP5iRWKx2PoTvaBLz
+9lNPTXhXu45n2k9L9SbkYk0024u6I8x9EkAmu0wU/rQpdudw4uLO7Pxz7BZHq3/D
+frwBMvtQXRogeQcsi3dKmsISrxdSEO1hax6toVnqujPSXKmFOMHXyhqJg/K9DDhs
+8GAUUO0cVfAaviofVFBZAXwkOd0SaYWz3WY4qHOZHnFiuGN0MCd2lXe5kVKFXRdE
+6ElXMm2+M0YLH+L55MA79hIDjqjv9JXTAAKpLy5wsOkMYEaaXvevdAEhkSW9QzIh
+HGUqBED5jqdrNlm7qOPSMWDcB/9FlnizKFsqrZOjquWFQZ4L34t++8FPT0ipxKCI
+XkYxBqVesZlHBOLPBYQ2bdJUuYaACkyMtB5eRebEieoWRXTB0aD4otvh6F6MYpiY
+4q66GO3faCnPxcZY4OxxwSSuyotBP8qKJcACqf3/Ji0nt42IHOIWJmiwROjBzMQ2
+DgYLtjsi1/b8eXLO5g+6S8o404j8g+2Z2rYXG+Hw6TaxY5bXrAH51fbUNDJP6qFR
+X3K3q8YvJlXcKIU6aZi5CDqaJUz6e22FvsOeWVHjdQYB23kk+LWYQY58hvkFUDXd
+/Nn2FxGPn80280sF4s8EX77YEXLGLqIo293Yg9F9JBwrq76ObY6DJbRlB4EG0Hx+
+vo0WJhEWHahu2uCfwqMJPDhMTGYcYRdHcNSepJlbj+gFTmH8bdKZGI/g13ZsDYEK
+HKP72rtaGQT4JYkS06TC6/Vvi+0+PxXr9SKbODCskN2yJpp0S2wXy8H6IwlpPe1R
+v82oCJQAXrcD8wd65TR2Q7fg4akHzI9VX1q8UGjzNt/Y/EsPEVv48PM/zngk/Hat
+Ptq6SoOFAj/JqbXOMuW0fidBI7uhq5575lgxaQ+VL4nKdDrdoHZXWJgjG/ZQO1j4
+8o12Z+Fk8tNpMPofJaAf3ebuArAE/IUDK7KKB7UoSwT1XPOFI9gCVtG654i7CAtN
+LfkDruwjTcD2XyaVLcVTeDUr2wMMrugE+OS4B/d0nD7jU4hVHTVn5fJxRiAFx+ql
+cBG/TIf951aDZ1x7eIj4xhehletdeWTZ2wGT8RJcFPD3LIB1hddeDIFQrkfEP7Gp
+IY+VdYo3tZtYcj9x0dXey/n9y0kxZw2w8oAY5iJB7NydeSANL4btPWO8y4KxXta9
+P+HNq01JS78ufYxfIOnOxD8l7YtD6Z6Evr1elzGFJOzRl4nCbPUg+VXekR94Lmf8
+0MBLgyG6xSoRBHvIYK6SRHEc+NuWvyfZGSOKBQVTwvf4/y4+xGcbPZy/YnUX6u+/
+zRWaYPJBdRhTrwAqawz9ZLL65uFYM7jhpZPEYGhOVqFoarNsy0m0gF5HNy8r/s71
+N1DBKUyVzdhR+4JZEHkkAr8JnN108d1uDIkaiI1F+hLCK2m5wnMYlZBMj2pP4kSC
+1E7JiIr/EKDiIHE7rOzf3yTYwug0xusBDdNTappMzTRsTCEyJf330k3kS7y0+zoH
+lwtG/9HS6qcwPY2h5buxTb6v9xQqmyWNT28Q8qEb3h8b2XS/NUyB9SQvRpkn9oUR
diff --git a/scripts/ios_travis/ios-dist.p12.enc b/scripts/ios_travis/ios-dist.p12.enc
new file mode 100644
index 0000000000..ad6507ef9f
--- /dev/null
+++ b/scripts/ios_travis/ios-dist.p12.enc
@@ -0,0 +1,32 @@
+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
diff --git a/scripts/ios_travis/ios-in-house.mobileprovision.enc b/scripts/ios_travis/ios-in-house.mobileprovision.enc
new file mode 100644
index 0000000000..51036a7415
--- /dev/null
+++ b/scripts/ios_travis/ios-in-house.mobileprovision.enc
@@ -0,0 +1,153 @@
+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
+mCKBZzE+OCJOYS4bO4/Lbg==
diff --git a/scripts/ios_travis/remove-key.sh b/scripts/ios_travis/remove-key.sh
new file mode 100755
index 0000000000..56b769ac21
--- /dev/null
+++ b/scripts/ios_travis/remove-key.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+security delete-keychain ios-build.keychain
+rm -f "~/Library/MobileDevice/Provisioning Profiles/$IOS_PROFILE_NAME.mobileprovision"
diff --git a/scripts/package_ios.sh b/scripts/package_ios.sh
new file mode 100755
index 0000000000..89aac5ee79
--- /dev/null
+++ b/scripts/package_ios.sh
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+
+set -e
+set -o pipefail
+set -u
+
+NAME=MapboxGL
+OUTPUT=build/ios/pkg
+IOS_SDK_VERSION=8.1
+LIBUV_VERSION=0.10.28
+
+function step { >&2 echo -e "\033[1m\033[36m* $@\033[0m"; }
+function finish { >&2 echo -en "\033[0m"; }
+trap finish EXIT
+
+
+rm -rf ${OUTPUT}
+mkdir -p "${OUTPUT}"/static
+
+
+step "Recording library version..."
+VERSION="${OUTPUT}"/static/version.txt
+echo -n "https://github.com/mapbox/mapbox-gl-native/commit/" > ${VERSION}
+HASH=`git log | head -1 | awk '{ print $2 }' | cut -c 1-10` && true
+echo -n "mapbox-gl-native "
+echo ${HASH}
+echo ${HASH} >> ${VERSION}
+
+
+step "Creating build files..."
+export MASON_PLATFORM=ios
+export BUILDTYPE=${BUILDTYPE:-Release}
+export HOST=ios
+make Xcode/mbgl
+
+step "Building iOS targets..."
+xcodebuild -sdk iphoneos${IOS_SDK_VERSION} \
+ ARCHS="arm64 armv7 armv7s" \
+ -project ./build/ios/mbgl.xcodeproj \
+ -configuration ${BUILDTYPE} \
+ -target everything \
+ -jobs `sysctl -n hw.ncpu` | xcpretty -c
+
+
+step "Building iOS Simulator targets..."
+xcodebuild -sdk iphonesimulator${IOS_SDK_VERSION} \
+ ARCHS="x86_64 i386" \
+ -project ./build/ios/mbgl.xcodeproj \
+ -configuration ${BUILDTYPE} \
+ -target everything \
+ -jobs `sysctl -n hw.ncpu` | xcpretty -c
+
+
+step "Building static library..."
+LIBS=(core.a platform-ios.a asset-fs.a cache-sqlite.a http-nsurl.a)
+libtool -static -no_warning_for_no_symbols \
+ -o ${OUTPUT}/static/lib${NAME}.a \
+ ${LIBS[@]/#/build/${BUILDTYPE}-iphoneos/libmbgl-} \
+ ${LIBS[@]/#/build/${BUILDTYPE}-iphonesimulator/libmbgl-} \
+ `find mason_packages/ios-${IOS_SDK_VERSION} -type f -name libuv.a`
+echo "Created ${OUTPUT}/static/lib${NAME}.a"
+
+
+step "Copying Headers..."
+mkdir -p "${OUTPUT}/static/Headers"
+cp -pv include/mbgl/ios/* "${OUTPUT}/static/Headers"
+
+
+# Manually create resource bundle. We don't use a GYP target here because of
+# complications between faked GYP bundles-as-executables, device build
+# dependencies, and code signing.
+step "Copying Resources..."
+mkdir -p "${OUTPUT}/static/${NAME}.bundle"
+cp -pv platform/ios/resources/* "${OUTPUT}/static/${NAME}.bundle"
+cp -prv styles/styles "${OUTPUT}/static/${NAME}.bundle/styles"
diff --git a/scripts/travis_before_install.sh b/scripts/travis_before_install.sh
index 060a142093..ccda31c994 100755
--- a/scripts/travis_before_install.sh
+++ b/scripts/travis_before_install.sh
@@ -47,4 +47,7 @@ elif [[ ${TRAVIS_OS_NAME} == "osx" ]]; then
mapbox_time "install_awscli" \
sudo pip install awscli
+
+ mapbox_time "install_xcpretty" \
+ gem install xcpretty --no-rdoc --no-ri --no-document --quiet
fi
diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh
index 71635b5c7f..45bda62c37 100755
--- a/scripts/travis_script.sh
+++ b/scripts/travis_script.sh
@@ -44,9 +44,8 @@ elif [[ ${TRAVIS_OS_NAME} == "osx" ]]; then
#
# build iOS
#
- mapbox_time "checkout_cocoa_bindings" \
- git submodule update --init ios/mapbox-gl-cocoa
-
- mapbox_time "build_ios_project" \
+ mapbox_time "build_ios_project_device_release" \
make ios -j$JOBS
+ mapbox_time "build_ios_project_simulator_debug" \
+ make isim -j$JOBS
fi
diff --git a/test/ios/.gitignore b/test/ios/.gitignore
new file mode 100644
index 0000000000..516812b72d
--- /dev/null
+++ b/test/ios/.gitignore
@@ -0,0 +1,3 @@
+!*.xcodeproj
+!xcshareddata
+xcuserdata
diff --git a/test/ios/App-Info.plist b/test/ios/App-Info.plist
new file mode 100644
index 0000000000..d7e9d5c462
--- /dev/null
+++ b/test/ios/App-Info.plist
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.mapbox.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/test/ios/Bundle-Info.plist b/test/ios/Bundle-Info.plist
new file mode 100644
index 0000000000..4ea0aed89c
--- /dev/null
+++ b/test/ios/Bundle-Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.mapbox.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/test/ios/KIF b/test/ios/KIF
new file mode 160000
+Subproject ab5a46ff7e970de5578df48a1e1f013bd5e1bd4
diff --git a/test/ios/KIFTestActor+MapboxGL.h b/test/ios/KIFTestActor+MapboxGL.h
new file mode 100644
index 0000000000..199091b29f
--- /dev/null
+++ b/test/ios/KIFTestActor+MapboxGL.h
@@ -0,0 +1,12 @@
+#import <KIF/KIF.h>
+
+@class MGLMapView;
+
+@interface KIFTestActor (MapboxGL)
+
+@property (nonatomic, readonly) UIWindow *window;
+@property (nonatomic, readonly) UIViewController *viewController;
+@property (nonatomic, readonly) MGLMapView *mapView;
+@property (nonatomic, readonly) UIView *compass;
+
+@end
diff --git a/test/ios/KIFTestActor+MapboxGL.m b/test/ios/KIFTestActor+MapboxGL.m
new file mode 100644
index 0000000000..b267e0c0be
--- /dev/null
+++ b/test/ios/KIFTestActor+MapboxGL.m
@@ -0,0 +1,24 @@
+#import "KIFTestActor+MapboxGL.h"
+#import <KIF/UIApplication-KIFAdditions.h>
+#import <KIF/UIAccessibilityElement-KIFAdditions.h>
+#import "MGLMapView.h"
+
+@implementation KIFTestActor (MapboxGL)
+
+- (UIWindow *)window {
+ return [[UIApplication sharedApplication] statusBarWindow];
+}
+
+- (UIViewController *)viewController {
+ return (UIViewController *)[[tester.mapView nextResponder] nextResponder];
+}
+
+- (MGLMapView *)mapView {
+ return (MGLMapView *)[tester waitForViewWithAccessibilityLabel:@"Map"];
+}
+
+- (UIView *)compass {
+ return [tester waitForViewWithAccessibilityLabel:@"Compass"];
+}
+
+@end
diff --git a/test/ios/MGLTAppDelegate.h b/test/ios/MGLTAppDelegate.h
new file mode 100644
index 0000000000..e5c459beb0
--- /dev/null
+++ b/test/ios/MGLTAppDelegate.h
@@ -0,0 +1,7 @@
+#import <UIKit/UIKit.h>
+
+@interface MGLTAppDelegate : UIResponder <UIApplicationDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/test/ios/MGLTAppDelegate.m b/test/ios/MGLTAppDelegate.m
new file mode 100644
index 0000000000..4bd6b64882
--- /dev/null
+++ b/test/ios/MGLTAppDelegate.m
@@ -0,0 +1,18 @@
+#import "MGLTAppDelegate.h"
+#import "MGLTViewController.h"
+
+@implementation MGLTAppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+ self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+ UINavigationController *wrapper = [[UINavigationController alloc] initWithRootViewController:[MGLTViewController new]];
+ self.window.rootViewController = wrapper;
+ wrapper.navigationBarHidden = YES;
+ wrapper.toolbarHidden = YES;
+ [self.window makeKeyAndVisible];
+
+ return YES;
+}
+
+@end
diff --git a/test/ios/MGLTViewController.h b/test/ios/MGLTViewController.h
new file mode 100644
index 0000000000..39df59bb5e
--- /dev/null
+++ b/test/ios/MGLTViewController.h
@@ -0,0 +1,5 @@
+#import <UIKit/UIKit.h>
+
+@interface MGLTViewController : UIViewController
+
+@end
diff --git a/test/ios/MGLTViewController.m b/test/ios/MGLTViewController.m
new file mode 100644
index 0000000000..9caa64c79a
--- /dev/null
+++ b/test/ios/MGLTViewController.m
@@ -0,0 +1,18 @@
+#import "MGLTViewController.h"
+#import "MGLMapView.h"
+
+@implementation MGLTViewController
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ MGLMapView *mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds
+ accessToken:@"pk.eyJ1IjoianVzdGluIiwiYSI6Ik9RX3RRQzAifQ.dmOg_BAp1ywuDZMM7YsXRg"];
+ mapView.viewControllerForLayoutGuides = self;
+ mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+
+ [self.view addSubview:mapView];
+}
+
+@end
diff --git a/test/ios/MapViewTests.h b/test/ios/MapViewTests.h
new file mode 100644
index 0000000000..5057ffd641
--- /dev/null
+++ b/test/ios/MapViewTests.h
@@ -0,0 +1,5 @@
+#import <KIF/KIF.h>
+
+@interface MapViewTests : KIFTestCase
+
+@end
diff --git a/test/ios/MapViewTests.m b/test/ios/MapViewTests.m
new file mode 100644
index 0000000000..adf3aeba06
--- /dev/null
+++ b/test/ios/MapViewTests.m
@@ -0,0 +1,353 @@
+#import "MapViewTests.h"
+#import <KIF/KIFTestStepValidation.h>
+#import "KIFTestActor+MapboxGL.h"
+#import "MGLMapView.h"
+
+@interface MapViewTests () <MGLMapViewDelegate>
+
+@end
+
+@implementation MapViewTests
+
+- (void)beforeEach {
+ [system simulateDeviceRotationToOrientation:UIDeviceOrientationPortrait];
+ tester.mapView.viewControllerForLayoutGuides = tester.viewController;
+ tester.mapView.centerCoordinate = CLLocationCoordinate2DMake(38.913175, -77.032458);
+ tester.mapView.zoomLevel = 14;
+ tester.mapView.direction = 0;
+ tester.mapView.zoomEnabled = YES;
+ tester.mapView.scrollEnabled = YES;
+ tester.mapView.rotateEnabled = YES;
+ tester.viewController.navigationController.navigationBarHidden = YES;
+ tester.viewController.navigationController.toolbarHidden = YES;
+ tester.mapView.delegate = self;
+}
+
+- (void)testDirectionSet {
+ tester.mapView.direction = 270;
+ __KIFAssertEqual(tester.mapView.direction,
+ 270,
+ @"setting direction should take effect");
+
+ [tester waitForTimeInterval:1];
+
+ __KIFAssertEqual(tester.compass.alpha,
+ 1,
+ @"compass should be visible when map is rotated");
+ __KIFAssertEqualObjects([NSValue valueWithCGAffineTransform:tester.compass.transform],
+ [NSValue valueWithCGAffineTransform:CGAffineTransformMakeRotation(M_PI * 1.5)],
+ @"compass rotation should indicate map rotation");
+}
+
+- (void)testCompassTap {
+ tester.mapView.direction = 180;
+ __KIFAssertEqual(tester.mapView.direction,
+ 180,
+ @"setting direction should take effect");
+
+ [tester waitForTimeInterval:1];
+
+ [tester.compass tap];
+
+ [tester waitForTimeInterval:1];
+
+ __KIFAssertEqual(tester.mapView.direction,
+ 0,
+ @"tapping compass should reset map direction");
+ __KIFAssertEqual(tester.compass.alpha,
+ 0,
+ @"compass should not be visible when map is unrotated");
+ __KIFAssertEqualObjects([NSValue valueWithCGAffineTransform:tester.compass.transform],
+ [NSValue valueWithCGAffineTransform:CGAffineTransformIdentity],
+ @"compass rotation should indicate map rotation");
+}
+
+- (void)testDirectionReset {
+ tester.mapView.direction = 100;
+ __KIFAssertEqual(tester.mapView.direction,
+ 100,
+ @"setting direction should take effect");
+
+ [tester.mapView resetNorth];
+
+ [tester waitForTimeInterval:1];
+
+ __KIFAssertEqual(tester.mapView.direction,
+ 0,
+ @"resetting north should reset map direction");
+ __KIFAssertEqual(tester.compass.alpha,
+ 0,
+ @"compass should not be visible when map is unrotated");
+ __KIFAssertEqualObjects([NSValue valueWithCGAffineTransform:tester.compass.transform],
+ [NSValue valueWithCGAffineTransform:CGAffineTransformIdentity],
+ @"compass rotation should indicate map rotation");
+}
+
+- (void)testZoom {
+ double zoom = tester.mapView.zoomLevel;
+
+ [tester.mapView zoomAtPoint:CGPointMake(tester.mapView.bounds.size.width / 2,
+ tester.mapView.bounds.size.height / 2)
+ distance:50
+ steps:10];
+
+ XCTAssertTrue(tester.mapView.zoomLevel > zoom,
+ @"zoom gesture should increase zoom level");
+
+ zoom = tester.mapView.zoomLevel;
+ [tester.mapView pinchAtPoint:CGPointMake(tester.mapView.bounds.size.width / 2,
+ tester.mapView.bounds.size.height / 2)
+ distance:50
+ steps:10];
+
+ XCTAssertTrue(tester.mapView.zoomLevel < zoom,
+ @"pinch gesture should decrease zoom level");
+}
+
+- (void)testZoomDisabled {
+ tester.mapView.zoomEnabled = NO;
+ double zoom = tester.mapView.zoomLevel;
+
+ [tester.mapView zoomAtPoint:CGPointMake(tester.mapView.bounds.size.width / 2,
+ tester.mapView.bounds.size.height / 2)
+ distance:50
+ steps:10];
+
+ __KIFAssertEqual(tester.mapView.zoomLevel,
+ zoom,
+ @"disabling zoom gesture should disallow zooming");
+
+ [tester.mapView pinchAtPoint:CGPointMake(tester.mapView.bounds.size.width / 2,
+ tester.mapView.bounds.size.height / 2)
+ distance:50
+ steps:10];
+
+ __KIFAssertEqual(tester.mapView.zoomLevel,
+ zoom,
+ @"disabling zoom gesture should disallow pinching");
+}
+
+- (void)testPan {
+ CLLocationCoordinate2D centerCoordinate = tester.mapView.centerCoordinate;
+
+ [tester.mapView dragFromPoint:CGPointMake(10, 10) toPoint:CGPointMake(300, 300) steps:10];
+
+ XCTAssertTrue(tester.mapView.centerCoordinate.latitude > centerCoordinate.latitude,
+ @"panning map down should increase center latitude");
+ XCTAssertTrue(tester.mapView.centerCoordinate.longitude < centerCoordinate.longitude,
+ @"panning map right should decrease center longitude");
+}
+
+- (void)testPanDisabled {
+ tester.mapView.scrollEnabled = NO;
+ CLLocationCoordinate2D centerCoordinate = tester.mapView.centerCoordinate;
+
+ [tester.mapView dragFromPoint:CGPointMake(10, 10) toPoint:CGPointMake(300, 300) steps:10];
+
+ __KIFAssertEqual(centerCoordinate.latitude,
+ tester.mapView.centerCoordinate.latitude,
+ @"disabling pan gesture should disallow vertical panning");
+ __KIFAssertEqual(centerCoordinate.longitude,
+ tester.mapView.centerCoordinate.longitude,
+ @"disabling pan gesture should disallow horizontal panning");
+}
+
+- (void)testCenterSet {
+ CLLocationCoordinate2D newCenterCoordinate = CLLocationCoordinate2DMake(45.23237263, -122.23287129);
+ XCTAssertNotEqual(tester.mapView.centerCoordinate.latitude,
+ newCenterCoordinate.latitude,
+ @"initial setup should have differing center latitude");
+ XCTAssertNotEqual(tester.mapView.centerCoordinate.longitude,
+ newCenterCoordinate.longitude,
+ @"initial setup should have differing center longitude");
+
+ [tester.mapView setCenterCoordinate:newCenterCoordinate];
+
+ XCTAssertTrue(tester.mapView.centerCoordinate.latitude == newCenterCoordinate.latitude,
+ @"setting center should change latitude");
+ XCTAssertTrue(tester.mapView.centerCoordinate.longitude == newCenterCoordinate.longitude,
+ @"setting center should change longitude");
+}
+
+- (void)testZoomSet {
+ double newZoom = 11.65;
+ XCTAssertNotEqual(tester.mapView.zoomLevel,
+ newZoom,
+ @"initial setup should have differing zoom");
+
+ tester.mapView.zoomLevel = newZoom;
+
+ __KIFAssertEqual(tester.mapView.zoomLevel,
+ newZoom,
+ @"setting zoom should take effect");
+}
+
+- (void)testTopLayoutGuide {
+ CGRect statusBarFrame, navigationBarFrame, compassFrame;
+ UINavigationBar *navigationBar = tester.viewController.navigationController.navigationBar;
+
+ compassFrame = [tester.compass.superview convertRect:tester.compass.frame toView:nil];
+ statusBarFrame = [tester.window convertRect:[[UIApplication sharedApplication] statusBarFrame] toView:nil];
+ XCTAssertFalse(CGRectIntersectsRect(compassFrame, statusBarFrame),
+ @"compass should not be under status bar");
+
+ tester.viewController.navigationController.navigationBarHidden = NO;
+ compassFrame = [tester.compass.superview convertRect:tester.compass.frame toView:nil];
+ navigationBarFrame = [tester.window convertRect:navigationBar.frame toView:nil];
+ XCTAssertFalse(CGRectIntersectsRect(compassFrame, navigationBarFrame),
+ @"compass should not be under navigation bar");
+
+ [system simulateDeviceRotationToOrientation:UIDeviceOrientationLandscapeLeft];
+
+ compassFrame = [tester.compass.superview convertRect:tester.compass.frame toView:nil];
+ navigationBarFrame = [tester.window convertRect:navigationBar.frame toView:nil];
+ XCTAssertFalse(CGRectIntersectsRect(compassFrame, navigationBarFrame),
+ @"rotated device should not have compass under navigation bar");
+
+ tester.viewController.navigationController.navigationBarHidden = YES;
+ compassFrame = [tester.compass.superview convertRect:tester.compass.frame toView:nil];
+ statusBarFrame = [tester.window convertRect:[[UIApplication sharedApplication] statusBarFrame] toView:nil];
+ XCTAssertFalse(CGRectIntersectsRect(compassFrame, statusBarFrame),
+ @"rotated device should not have compass under status bar");
+}
+
+- (void)testBottomLayoutGuide {
+ CGRect logoBugFrame, toolbarFrame, attributionButtonFrame;
+ UIView *logoBug = (UIView *)[tester waitForViewWithAccessibilityLabel:@"Mapbox logo"];
+ UIToolbar *toolbar = tester.viewController.navigationController.toolbar;
+ UIView *attributionButton = (UIView *)[tester waitForViewWithAccessibilityLabel:@"Attribution info"];
+
+ tester.viewController.navigationController.toolbarHidden = NO;
+
+ logoBugFrame = [logoBug.superview convertRect:logoBug.frame toView:nil];
+ toolbarFrame = [tester.window convertRect:toolbar.frame toView:nil];
+ XCTAssertFalse(CGRectIntersectsRect(logoBugFrame, toolbarFrame),
+ @"logo bug should not be under toolbar");
+
+ attributionButtonFrame = [attributionButton.superview convertRect:attributionButton.frame toView:nil];
+ XCTAssertFalse(CGRectIntersectsRect(attributionButtonFrame, toolbarFrame),
+ @"attribution button should not be under toolbar");
+
+ [system simulateDeviceRotationToOrientation:UIDeviceOrientationLandscapeRight];
+
+ logoBugFrame = [logoBug.superview convertRect:logoBug.frame toView:nil];
+ toolbarFrame = [tester.window convertRect:toolbar.frame toView:nil];
+ XCTAssertFalse(CGRectIntersectsRect(logoBugFrame, toolbarFrame),
+ @"rotated device should not have logo buy under toolbar");
+
+ attributionButtonFrame = [attributionButton.superview convertRect:attributionButton.frame toView:nil];
+ XCTAssertFalse(CGRectIntersectsRect(attributionButtonFrame, toolbarFrame),
+ @"rotated device should not have attribution button under toolbar");
+}
+
+- (void)testDelegateRegionWillChange {
+ __block NSUInteger unanimatedCount = 0;
+ __block NSUInteger animatedCount = 0;
+ [[NSNotificationCenter defaultCenter] addObserverForName:@"regionWillChangeAnimated"
+ object:tester.mapView
+ queue:[NSOperationQueue mainQueue]
+ usingBlock:^(NSNotification *note) {
+ if ([note.userInfo[@"animated"] boolValue]) {
+ animatedCount++;
+ } else {
+ unanimatedCount++;
+ }
+ }];
+
+ NSNotification *notification = [system waitForNotificationName:@"regionWillChangeAnimated"
+ object:tester.mapView
+ whileExecutingBlock:^{
+ tester.mapView.centerCoordinate = CLLocationCoordinate2DMake(0, 0);
+ }];
+ [tester waitForTimeInterval:1];
+ XCTAssertNotNil(notification,
+ @"regionWillChange delegate should produce a notification");
+ __KIFAssertEqual([notification.userInfo[@"animated"] boolValue],
+ NO,
+ @"regionWillChange delegate should not indicate animated change");
+ __KIFAssertEqual(unanimatedCount,
+ 1,
+ @"regionWillChange delegate should indicate one unanimated change");
+
+ notification = [system waitForNotificationName:@"regionWillChangeAnimated"
+ object:tester.mapView
+ whileExecutingBlock:^{
+ [tester.mapView setCenterCoordinate:CLLocationCoordinate2DMake(45, 100) animated:YES];
+ }];
+ [tester waitForTimeInterval:1];
+ XCTAssertNotNil(notification,
+ @"regionWillChange delegate should produce a notification");
+ __KIFAssertEqual([notification.userInfo[@"animated"] boolValue],
+ YES,
+ @"regionWillChange delegate should indicate an animated change");
+ __KIFAssertEqual(animatedCount,
+ 1,
+ @"regionWillChange delegate should indicate one animated change");
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:@"regionWillChangeAnimated"
+ object:tester.mapView];
+}
+
+- (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated {
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"regionWillChangeAnimated"
+ object:mapView
+ userInfo:@{ @"animated" : @(animated) }];
+}
+
+- (void)testDelegateRegionDidChange {
+ __block NSUInteger unanimatedCount = 0;
+ __block NSUInteger animatedCount = 0;
+ [[NSNotificationCenter defaultCenter] addObserverForName:@"regionDidChangeAnimated"
+ object:tester.mapView
+ queue:[NSOperationQueue mainQueue]
+ usingBlock:^(NSNotification *note) {
+ if ([note.userInfo[@"animated"] boolValue]) {
+ animatedCount++;
+ } else {
+ unanimatedCount++;
+ }
+ }];
+
+ NSNotification *notification = [system waitForNotificationName:@"regionDidChangeAnimated"
+ object:tester.mapView
+ whileExecutingBlock:^{
+ tester.mapView.centerCoordinate = CLLocationCoordinate2DMake(0, 0);
+ }];
+ [tester waitForTimeInterval:1];
+ XCTAssertNotNil(notification,
+ @"regionDidChange delegate should produce a notification");
+ __KIFAssertEqual([notification.userInfo[@"animated"] boolValue],
+ NO,
+ @"regionDidChange delegate should not indicate animated change");
+ __KIFAssertEqual(unanimatedCount,
+ 1,
+ @"regionDidChange delegate should indicate one unanimated change");
+
+ notification = [system waitForNotificationName:@"regionDidChangeAnimated"
+ object:tester.mapView
+ whileExecutingBlock:^{
+ [tester.mapView setCenterCoordinate:CLLocationCoordinate2DMake(45, 100) animated:YES];
+ }];
+ [tester waitForTimeInterval:1];
+ XCTAssertNotNil(notification,
+ @"regionDidChange delegate should produce a notification");
+ __KIFAssertEqual([notification.userInfo[@"animated"] boolValue],
+ YES,
+ @"regionDidChange delegate should indicate animated change");
+ __KIFAssertEqual(animatedCount,
+ 1,
+ @"regionDidChange delegate should indicate one animated change");
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:@"regionDidChangeAnimated"
+ object:tester.mapView];
+}
+
+- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"regionDidChangeAnimated"
+ object:mapView
+ userInfo:@{ @"animated" : @(animated) }];
+}
+
+@end
diff --git a/test/ios/README.md b/test/ios/README.md
new file mode 100644
index 0000000000..21975e177e
--- /dev/null
+++ b/test/ios/README.md
@@ -0,0 +1,9 @@
+This is a harness app for integration testing of Mapbox GL Cocoa. It uses the static library build of Mapbox GL Cocoa in order to be entirely self-contained and not need the `mapbox-gl-native` upstream C++ project.
+
+To run tests, either open the enclosed `ios-tests.xcodeproj` and run the tests or use `xcodebuild test` at the command line from this directory. For example:
+
+```bash
+xcodebuild -scheme 'Mapbox GL Tests' \
+ -destination 'platform=iOS Simulator,name=iPad,OS=7.1' \
+ test
+```
diff --git a/test/ios/ios-tests.xcodeproj/project.pbxproj b/test/ios/ios-tests.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000..86f1251324
--- /dev/null
+++ b/test/ios/ios-tests.xcodeproj/project.pbxproj
@@ -0,0 +1,659 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ DD043327196DB9BC00E6F39D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD043326196DB9BC00E6F39D /* Foundation.framework */; };
+ DD043329196DB9BC00E6F39D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD043328196DB9BC00E6F39D /* CoreGraphics.framework */; };
+ DD04332B196DB9BC00E6F39D /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD04332A196DB9BC00E6F39D /* UIKit.framework */; };
+ DD043363196DBBD500E6F39D /* MGLTAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DD04335F196DBBD500E6F39D /* MGLTAppDelegate.m */; };
+ DD043364196DBBD500E6F39D /* MGLTViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DD043360196DBBD500E6F39D /* MGLTViewController.m */; };
+ DD043366196DBBE000E6F39D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DD043365196DBBE000E6F39D /* main.m */; };
+ DD1A9EA8199BEA0D007BC651 /* libc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DD1A9EA7199BEA0D007BC651 /* libc++.dylib */; };
+ DD61240819CCF06E006845B1 /* libMapboxGL.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DD61240619CCF06E006845B1 /* libMapboxGL.a */; };
+ DD61240919CCF06E006845B1 /* MapboxGL.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DD61240719CCF06E006845B1 /* MapboxGL.bundle */; };
+ DD8A790D196DC0A900FAD883 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DD8A790C196DC0A900FAD883 /* libz.dylib */; };
+ DD8A790F196DC0AD00FAD883 /* GLKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD8A790E196DC0AD00FAD883 /* GLKit.framework */; };
+ DD8A7911196DC0BB00FAD883 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD8A7910196DC0BB00FAD883 /* CoreLocation.framework */; };
+ DD96917919F1C08400729E7D /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DD96917819F1C08400729E7D /* libsqlite3.dylib */; };
+ DD96918119F1C09200729E7D /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD96918019F1C09200729E7D /* SystemConfiguration.framework */; };
+ DDBD0154196DC3D70033959E /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDBD0153196DC3D70033959E /* XCTest.framework */; };
+ DDBD0155196DC3D70033959E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD043326196DB9BC00E6F39D /* Foundation.framework */; };
+ DDBD0156196DC3D70033959E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD04332A196DB9BC00E6F39D /* UIKit.framework */; };
+ DDBD016C196DC4740033959E /* MapViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DDBD0168196DC4740033959E /* MapViewTests.m */; };
+ DDBD016D196DC4740033959E /* KIFTestActor+MapboxGL.m in Sources */ = {isa = PBXBuildFile; fileRef = DDBD016A196DC4740033959E /* KIFTestActor+MapboxGL.m */; };
+ DDBD016E196DC4A10033959E /* libKIF.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DDBD0144196DC3AE0033959E /* libKIF.a */; };
+ DDBD016F196DC4A90033959E /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD043328196DB9BC00E6F39D /* CoreGraphics.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ DDBD0143196DC3AE0033959E /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DDBD013A196DC3AE0033959E /* KIF.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = EABD46AA1857A0C700A5F081;
+ remoteInfo = KIF;
+ };
+ DDBD0145196DC3AE0033959E /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DDBD013A196DC3AE0033959E /* KIF.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = EB72047C1680DDAD00278DA2;
+ remoteInfo = "KIF-OCUnit";
+ };
+ DDBD0147196DC3AE0033959E /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DDBD013A196DC3AE0033959E /* KIF.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = EB60ECC1177F8C83005A041A;
+ remoteInfo = "Test Host";
+ };
+ DDBD0149196DC3AE0033959E /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DDBD013A196DC3AE0033959E /* KIF.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = EABD46CD1857A0F300A5F081;
+ remoteInfo = "KIF Tests";
+ };
+ DDBD014B196DC3AE0033959E /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DDBD013A196DC3AE0033959E /* KIF.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = EB60ECEB177F8DB3005A041A;
+ remoteInfo = "KIF Tests-OCUnit";
+ };
+ DDBD0160196DC3D70033959E /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DD04331B196DB9BC00E6F39D /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DD043322196DB9BC00E6F39D;
+ remoteInfo = "Mapbox GL Tests";
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ DD043323196DB9BC00E6F39D /* Mapbox GL Tests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Mapbox GL Tests.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ DD043326196DB9BC00E6F39D /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ DD043328196DB9BC00E6F39D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+ DD04332A196DB9BC00E6F39D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+ DD04335F196DBBD500E6F39D /* MGLTAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLTAppDelegate.m; sourceTree = SOURCE_ROOT; };
+ DD043360196DBBD500E6F39D /* MGLTViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLTViewController.m; sourceTree = SOURCE_ROOT; };
+ DD043361196DBBD500E6F39D /* MGLTViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLTViewController.h; sourceTree = SOURCE_ROOT; };
+ DD043362196DBBD500E6F39D /* MGLTAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLTAppDelegate.h; sourceTree = SOURCE_ROOT; };
+ DD043365196DBBE000E6F39D /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; };
+ DD043367196DBCC200E6F39D /* App-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "App-Info.plist"; sourceTree = SOURCE_ROOT; };
+ DD1A9EA7199BEA0D007BC651 /* libc++.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libc++.dylib"; path = "usr/lib/libc++.dylib"; sourceTree = SDKROOT; };
+ DD61240019CCF06E006845B1 /* MGLMapView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MGLMapView.h; path = ../dist/static/Headers/MGLMapView.h; sourceTree = SOURCE_ROOT; };
+ DD61240119CCF06E006845B1 /* MGLStyleFunctionValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MGLStyleFunctionValue.h; path = ../dist/static/Headers/MGLStyleFunctionValue.h; sourceTree = SOURCE_ROOT; };
+ DD61240219CCF06E006845B1 /* MGLTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MGLTypes.h; path = ../dist/static/Headers/MGLTypes.h; sourceTree = SOURCE_ROOT; };
+ DD61240319CCF06E006845B1 /* NSArray+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSArray+MGLAdditions.h"; path = "../dist/static/Headers/NSArray+MGLAdditions.h"; sourceTree = SOURCE_ROOT; };
+ DD61240419CCF06E006845B1 /* NSDictionary+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+MGLAdditions.h"; path = "../dist/static/Headers/NSDictionary+MGLAdditions.h"; sourceTree = SOURCE_ROOT; };
+ DD61240519CCF06E006845B1 /* UIColor+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIColor+MGLAdditions.h"; path = "../dist/static/Headers/UIColor+MGLAdditions.h"; sourceTree = SOURCE_ROOT; };
+ DD61240619CCF06E006845B1 /* libMapboxGL.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libMapboxGL.a; path = ../dist/static/libMapboxGL.a; sourceTree = SOURCE_ROOT; };
+ DD61240719CCF06E006845B1 /* MapboxGL.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = MapboxGL.bundle; path = ../dist/static/MapboxGL.bundle; sourceTree = SOURCE_ROOT; };
+ DD8A790C196DC0A900FAD883 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
+ DD8A790E196DC0AD00FAD883 /* GLKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GLKit.framework; path = System/Library/Frameworks/GLKit.framework; sourceTree = SDKROOT; };
+ DD8A7910196DC0BB00FAD883 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; };
+ DD96917819F1C08400729E7D /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; };
+ DD96918019F1C09200729E7D /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
+ DDBD013A196DC3AE0033959E /* KIF.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = KIF.xcodeproj; path = KIF/KIF.xcodeproj; sourceTree = SOURCE_ROOT; };
+ DDBD0152196DC3D70033959E /* Test Bundle.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Test Bundle.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
+ DDBD0153196DC3D70033959E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
+ DDBD0165196DC4560033959E /* Bundle-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Bundle-Info.plist"; sourceTree = SOURCE_ROOT; };
+ DDBD0168196DC4740033959E /* MapViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MapViewTests.m; sourceTree = SOURCE_ROOT; };
+ DDBD0169196DC4740033959E /* MapViewTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MapViewTests.h; sourceTree = SOURCE_ROOT; };
+ DDBD016A196DC4740033959E /* KIFTestActor+MapboxGL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "KIFTestActor+MapboxGL.m"; sourceTree = SOURCE_ROOT; };
+ DDBD016B196DC4740033959E /* KIFTestActor+MapboxGL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "KIFTestActor+MapboxGL.h"; sourceTree = SOURCE_ROOT; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ DD043320196DB9BC00E6F39D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DD043329196DB9BC00E6F39D /* CoreGraphics.framework in Frameworks */,
+ DD8A7911196DC0BB00FAD883 /* CoreLocation.framework in Frameworks */,
+ DD043327196DB9BC00E6F39D /* Foundation.framework in Frameworks */,
+ DD8A790F196DC0AD00FAD883 /* GLKit.framework in Frameworks */,
+ DD96918119F1C09200729E7D /* SystemConfiguration.framework in Frameworks */,
+ DD04332B196DB9BC00E6F39D /* UIKit.framework in Frameworks */,
+ DD61240819CCF06E006845B1 /* libMapboxGL.a in Frameworks */,
+ DD1A9EA8199BEA0D007BC651 /* libc++.dylib in Frameworks */,
+ DD96917919F1C08400729E7D /* libsqlite3.dylib in Frameworks */,
+ DD8A790D196DC0A900FAD883 /* libz.dylib in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DDBD014F196DC3D70033959E /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DDBD016F196DC4A90033959E /* CoreGraphics.framework in Frameworks */,
+ DDBD0155196DC3D70033959E /* Foundation.framework in Frameworks */,
+ DDBD0156196DC3D70033959E /* UIKit.framework in Frameworks */,
+ DDBD0154196DC3D70033959E /* XCTest.framework in Frameworks */,
+ DDBD016E196DC4A10033959E /* libKIF.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ DD04331A196DB9BC00E6F39D = {
+ isa = PBXGroup;
+ children = (
+ DD04332C196DB9BC00E6F39D /* App */,
+ DDBD0139196DC38D0033959E /* Tests */,
+ DD043325196DB9BC00E6F39D /* Frameworks */,
+ DD043324196DB9BC00E6F39D /* Products */,
+ );
+ sourceTree = "<group>";
+ };
+ DD043324196DB9BC00E6F39D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ DD043323196DB9BC00E6F39D /* Mapbox GL Tests.app */,
+ DDBD0152196DC3D70033959E /* Test Bundle.xctest */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ DD043325196DB9BC00E6F39D /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ DD96918019F1C09200729E7D /* SystemConfiguration.framework */,
+ DD96917819F1C08400729E7D /* libsqlite3.dylib */,
+ DD043328196DB9BC00E6F39D /* CoreGraphics.framework */,
+ DD8A7910196DC0BB00FAD883 /* CoreLocation.framework */,
+ DD043326196DB9BC00E6F39D /* Foundation.framework */,
+ DD8A790E196DC0AD00FAD883 /* GLKit.framework */,
+ DD04332A196DB9BC00E6F39D /* UIKit.framework */,
+ DDBD0153196DC3D70033959E /* XCTest.framework */,
+ DD1A9EA7199BEA0D007BC651 /* libc++.dylib */,
+ DD8A790C196DC0A900FAD883 /* libz.dylib */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ DD04332C196DB9BC00E6F39D /* App */ = {
+ isa = PBXGroup;
+ children = (
+ DD043362196DBBD500E6F39D /* MGLTAppDelegate.h */,
+ DD04335F196DBBD500E6F39D /* MGLTAppDelegate.m */,
+ DD043361196DBBD500E6F39D /* MGLTViewController.h */,
+ DD043360196DBBD500E6F39D /* MGLTViewController.m */,
+ DD8A77AD196DBFDA00FAD883 /* GL Library */,
+ DD04332D196DB9BC00E6F39D /* Supporting Files */,
+ );
+ name = App;
+ path = "Mapbox GL Tests";
+ sourceTree = "<group>";
+ };
+ DD04332D196DB9BC00E6F39D /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ DD043367196DBCC200E6F39D /* App-Info.plist */,
+ DD043365196DBBE000E6F39D /* main.m */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+ DD6123FF19CCF06E006845B1 /* Headers */ = {
+ isa = PBXGroup;
+ children = (
+ DD61240019CCF06E006845B1 /* MGLMapView.h */,
+ DD61240119CCF06E006845B1 /* MGLStyleFunctionValue.h */,
+ DD61240219CCF06E006845B1 /* MGLTypes.h */,
+ DD61240319CCF06E006845B1 /* NSArray+MGLAdditions.h */,
+ DD61240419CCF06E006845B1 /* NSDictionary+MGLAdditions.h */,
+ DD61240519CCF06E006845B1 /* UIColor+MGLAdditions.h */,
+ );
+ name = Headers;
+ path = ../../dist/static/Headers;
+ sourceTree = SOURCE_ROOT;
+ };
+ DD8A77AD196DBFDA00FAD883 /* GL Library */ = {
+ isa = PBXGroup;
+ children = (
+ DD6123FF19CCF06E006845B1 /* Headers */,
+ DD61240619CCF06E006845B1 /* libMapboxGL.a */,
+ DD61240719CCF06E006845B1 /* MapboxGL.bundle */,
+ );
+ name = "GL Library";
+ sourceTree = "<group>";
+ };
+ DDBD0139196DC38D0033959E /* Tests */ = {
+ isa = PBXGroup;
+ children = (
+ DDBD016B196DC4740033959E /* KIFTestActor+MapboxGL.h */,
+ DDBD016A196DC4740033959E /* KIFTestActor+MapboxGL.m */,
+ DDBD0169196DC4740033959E /* MapViewTests.h */,
+ DDBD0168196DC4740033959E /* MapViewTests.m */,
+ DDBD0167196DC46B0033959E /* Supporting Files */,
+ DDBD014D196DC3B00033959E /* KIF */,
+ );
+ name = Tests;
+ path = "Mapbox GL Tests";
+ sourceTree = "<group>";
+ };
+ DDBD013B196DC3AE0033959E /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ DDBD0144196DC3AE0033959E /* libKIF.a */,
+ DDBD0146196DC3AE0033959E /* libKIF-OCUnit.a */,
+ DDBD0148196DC3AE0033959E /* Test Host.app */,
+ DDBD014A196DC3AE0033959E /* KIF Tests - XCTest.xctest */,
+ DDBD014C196DC3AE0033959E /* KIF Tests-OCUnit.octest */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ DDBD014D196DC3B00033959E /* KIF */ = {
+ isa = PBXGroup;
+ children = (
+ DDBD013A196DC3AE0033959E /* KIF.xcodeproj */,
+ );
+ name = KIF;
+ sourceTree = "<group>";
+ };
+ DDBD0167196DC46B0033959E /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ DDBD0165196DC4560033959E /* Bundle-Info.plist */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ DD043322196DB9BC00E6F39D /* Mapbox GL Tests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DD043358196DB9BC00E6F39D /* Build configuration list for PBXNativeTarget "Mapbox GL Tests" */;
+ buildPhases = (
+ DD04331F196DB9BC00E6F39D /* Sources */,
+ DD043320196DB9BC00E6F39D /* Frameworks */,
+ DD043321196DB9BC00E6F39D /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Mapbox GL Tests";
+ productName = "Mapbox GL Tests";
+ productReference = DD043323196DB9BC00E6F39D /* Mapbox GL Tests.app */;
+ productType = "com.apple.product-type.application";
+ };
+ DDBD0151196DC3D70033959E /* Test Bundle */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DDBD0162196DC3D70033959E /* Build configuration list for PBXNativeTarget "Test Bundle" */;
+ buildPhases = (
+ DDBD014E196DC3D70033959E /* Sources */,
+ DDBD014F196DC3D70033959E /* Frameworks */,
+ DDBD0150196DC3D70033959E /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DDBD0161196DC3D70033959E /* PBXTargetDependency */,
+ );
+ name = "Test Bundle";
+ productName = "Test Bundle";
+ productReference = DDBD0152196DC3D70033959E /* Test Bundle.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ DD04331B196DB9BC00E6F39D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ CLASSPREFIX = MGLT;
+ LastUpgradeCheck = 0510;
+ ORGANIZATIONNAME = Mapbox;
+ TargetAttributes = {
+ DDBD0151196DC3D70033959E = {
+ TestTargetID = DD043322196DB9BC00E6F39D;
+ };
+ };
+ };
+ buildConfigurationList = DD04331E196DB9BC00E6F39D /* Build configuration list for PBXProject "ios-tests" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = DD04331A196DB9BC00E6F39D;
+ productRefGroup = DD043324196DB9BC00E6F39D /* Products */;
+ projectDirPath = "";
+ projectReferences = (
+ {
+ ProductGroup = DDBD013B196DC3AE0033959E /* Products */;
+ ProjectRef = DDBD013A196DC3AE0033959E /* KIF.xcodeproj */;
+ },
+ );
+ projectRoot = "";
+ targets = (
+ DD043322196DB9BC00E6F39D /* Mapbox GL Tests */,
+ DDBD0151196DC3D70033959E /* Test Bundle */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXReferenceProxy section */
+ DDBD0144196DC3AE0033959E /* libKIF.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libKIF.a;
+ remoteRef = DDBD0143196DC3AE0033959E /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ DDBD0146196DC3AE0033959E /* libKIF-OCUnit.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = "libKIF-OCUnit.a";
+ remoteRef = DDBD0145196DC3AE0033959E /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ DDBD0148196DC3AE0033959E /* Test Host.app */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.application;
+ path = "Test Host.app";
+ remoteRef = DDBD0147196DC3AE0033959E /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ DDBD014A196DC3AE0033959E /* KIF Tests - XCTest.xctest */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.cfbundle;
+ path = "KIF Tests - XCTest.xctest";
+ remoteRef = DDBD0149196DC3AE0033959E /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ DDBD014C196DC3AE0033959E /* KIF Tests-OCUnit.octest */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.cfbundle;
+ path = "KIF Tests-OCUnit.octest";
+ remoteRef = DDBD014B196DC3AE0033959E /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+/* End PBXReferenceProxy section */
+
+/* Begin PBXResourcesBuildPhase section */
+ DD043321196DB9BC00E6F39D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DD61240919CCF06E006845B1 /* MapboxGL.bundle in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DDBD0150196DC3D70033959E /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ DD04331F196DB9BC00E6F39D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DD043364196DBBD500E6F39D /* MGLTViewController.m in Sources */,
+ DD043366196DBBE000E6F39D /* main.m in Sources */,
+ DD043363196DBBD500E6F39D /* MGLTAppDelegate.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DDBD014E196DC3D70033959E /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DDBD016D196DC4740033959E /* KIFTestActor+MapboxGL.m in Sources */,
+ DDBD016C196DC4740033959E /* MapViewTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ DDBD0161196DC3D70033959E /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DD043322196DB9BC00E6F39D /* Mapbox GL Tests */;
+ targetProxy = DDBD0160196DC3D70033959E /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ DD043356196DB9BC00E6F39D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 7.1;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ DD043357196DB9BC00E6F39D /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = YES;
+ ENABLE_NS_ASSERTIONS = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 7.1;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ DD043359196DB9BC00E6F39D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = (
+ armv7,
+ armv7s,
+ arm64,
+ i386,
+ x86_64,
+ );
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+ ../dist/static/Headers,
+ );
+ INFOPLIST_FILE = "$(SRCROOT)/App-Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = 7.0;
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ ../dist/static,
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Debug;
+ };
+ DD04335A196DB9BC00E6F39D /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = (
+ armv7,
+ armv7s,
+ arm64,
+ i386,
+ x86_64,
+ );
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+ ../dist/static/Headers,
+ );
+ INFOPLIST_FILE = "$(SRCROOT)/App-Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = 7.0;
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ ../dist/static,
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Release;
+ };
+ DDBD0163196DC3D70033959E /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Mapbox GL Tests.app/Mapbox GL Tests";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ "KIF_XCTEST=1",
+ );
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+ ../dist/static/Headers,
+ );
+ INFOPLIST_FILE = "Bundle-Info.plist";
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-framework",
+ XCTest,
+ "-ObjC",
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUNDLE_LOADER)";
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Debug;
+ };
+ DDBD0164196DC3D70033959E /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Mapbox GL Tests.app/Mapbox GL Tests";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ GCC_PREPROCESSOR_DEFINITIONS = "KIF_XCTEST=1";
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+ ../dist/static/Headers,
+ );
+ INFOPLIST_FILE = "Bundle-Info.plist";
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-framework",
+ XCTest,
+ "-ObjC",
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUNDLE_LOADER)";
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ DD04331E196DB9BC00E6F39D /* Build configuration list for PBXProject "ios-tests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DD043356196DB9BC00E6F39D /* Debug */,
+ DD043357196DB9BC00E6F39D /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DD043358196DB9BC00E6F39D /* Build configuration list for PBXNativeTarget "Mapbox GL Tests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DD043359196DB9BC00E6F39D /* Debug */,
+ DD04335A196DB9BC00E6F39D /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DDBD0162196DC3D70033959E /* Build configuration list for PBXNativeTarget "Test Bundle" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DDBD0163196DC3D70033959E /* Debug */,
+ DDBD0164196DC3D70033959E /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = DD04331B196DB9BC00E6F39D /* Project object */;
+}
diff --git a/test/ios/ios-tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/test/ios/ios-tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000000..4cad7961db
--- /dev/null
+++ b/test/ios/ios-tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "self:Mapbox GL Tests.xcodeproj">
+ </FileRef>
+</Workspace>
diff --git a/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/Mapbox GL Tests.xccheckout b/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/Mapbox GL Tests.xccheckout
new file mode 100644
index 0000000000..68c68a2234
--- /dev/null
+++ b/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/Mapbox GL Tests.xccheckout
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IDESourceControlProjectFavoriteDictionaryKey</key>
+ <false/>
+ <key>IDESourceControlProjectIdentifier</key>
+ <string>A9E9DC14-62C7-4C7A-B782-6B24B7657046</string>
+ <key>IDESourceControlProjectName</key>
+ <string>Mapbox GL Tests</string>
+ <key>IDESourceControlProjectOriginsDictionary</key>
+ <dict>
+ <key>1F4F0A5F-01E4-4945-AE04-F4B1B763C2BF</key>
+ <string>ssh://github.com/mapbox/mapbox-gl-cocoa.git</string>
+ </dict>
+ <key>IDESourceControlProjectPath</key>
+ <string>test/Mapbox GL Tests/Mapbox GL Tests.xcodeproj/project.xcworkspace</string>
+ <key>IDESourceControlProjectRelativeInstallPathDictionary</key>
+ <dict>
+ <key>1F4F0A5F-01E4-4945-AE04-F4B1B763C2BF</key>
+ <string>../../../..</string>
+ </dict>
+ <key>IDESourceControlProjectURL</key>
+ <string>ssh://github.com/mapbox/mapbox-gl-cocoa.git</string>
+ <key>IDESourceControlProjectVersion</key>
+ <integer>110</integer>
+ <key>IDESourceControlProjectWCCIdentifier</key>
+ <string>1F4F0A5F-01E4-4945-AE04-F4B1B763C2BF</string>
+ <key>IDESourceControlProjectWCConfigurations</key>
+ <array>
+ <dict>
+ <key>IDESourceControlRepositoryExtensionIdentifierKey</key>
+ <string>public.vcs.git</string>
+ <key>IDESourceControlWCCIdentifierKey</key>
+ <string>1F4F0A5F-01E4-4945-AE04-F4B1B763C2BF</string>
+ <key>IDESourceControlWCCName</key>
+ <string>mapbox-gl-cocoa</string>
+ </dict>
+ </array>
+</dict>
+</plist>
diff --git a/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout b/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout
new file mode 100644
index 0000000000..8f3ca408b6
--- /dev/null
+++ b/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IDESourceControlProjectFavoriteDictionaryKey</key>
+ <false/>
+ <key>IDESourceControlProjectIdentifier</key>
+ <string>31F71B93-A6C7-4EE6-B62A-5324738E25FE</string>
+ <key>IDESourceControlProjectName</key>
+ <string>ios-tests</string>
+ <key>IDESourceControlProjectOriginsDictionary</key>
+ <dict>
+ <key>10265E242415D473A6A613214DB7AC3EE3D43F93</key>
+ <string>https://github.com/mapbox/KIF.git</string>
+ <key>7E68CB584078A487C0535CC191D3B7551EEE2095</key>
+ <string>github.com:mapbox/mapbox-gl-native.git</string>
+ <key>FC967DACF69B3B67E6F2E9FBE56B64B73B118AFF</key>
+ <string>github.com:mapbox/mapbox-gl-cocoa.git</string>
+ </dict>
+ <key>IDESourceControlProjectPath</key>
+ <string>test/ios-tests.xcodeproj</string>
+ <key>IDESourceControlProjectRelativeInstallPathDictionary</key>
+ <dict>
+ <key>10265E242415D473A6A613214DB7AC3EE3D43F93</key>
+ <string>../../..test/KIF/</string>
+ <key>7E68CB584078A487C0535CC191D3B7551EEE2095</key>
+ <string>../../../../..</string>
+ <key>FC967DACF69B3B67E6F2E9FBE56B64B73B118AFF</key>
+ <string>../../..</string>
+ </dict>
+ <key>IDESourceControlProjectURL</key>
+ <string>github.com:mapbox/mapbox-gl-cocoa.git</string>
+ <key>IDESourceControlProjectVersion</key>
+ <integer>111</integer>
+ <key>IDESourceControlProjectWCCIdentifier</key>
+ <string>FC967DACF69B3B67E6F2E9FBE56B64B73B118AFF</string>
+ <key>IDESourceControlProjectWCConfigurations</key>
+ <array>
+ <dict>
+ <key>IDESourceControlRepositoryExtensionIdentifierKey</key>
+ <string>public.vcs.git</string>
+ <key>IDESourceControlWCCIdentifierKey</key>
+ <string>7E68CB584078A487C0535CC191D3B7551EEE2095</string>
+ <key>IDESourceControlWCCName</key>
+ <string>..</string>
+ </dict>
+ <dict>
+ <key>IDESourceControlRepositoryExtensionIdentifierKey</key>
+ <string>public.vcs.git</string>
+ <key>IDESourceControlWCCIdentifierKey</key>
+ <string>10265E242415D473A6A613214DB7AC3EE3D43F93</string>
+ <key>IDESourceControlWCCName</key>
+ <string>KIF</string>
+ </dict>
+ <dict>
+ <key>IDESourceControlRepositoryExtensionIdentifierKey</key>
+ <string>public.vcs.git</string>
+ <key>IDESourceControlWCCIdentifierKey</key>
+ <string>FC967DACF69B3B67E6F2E9FBE56B64B73B118AFF</string>
+ <key>IDESourceControlWCCName</key>
+ <string>mapbox-gl-cocoa</string>
+ </dict>
+ </array>
+</dict>
+</plist>
diff --git a/test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme b/test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme
new file mode 100644
index 0000000000..f3c2bd7ffe
--- /dev/null
+++ b/test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0510"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "EABD46791857A0C700A5F081"
+ BuildableName = "libKIF.a"
+ BlueprintName = "KIF"
+ ReferencedContainer = "container:KIF/KIF.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DD043322196DB9BC00E6F39D"
+ BuildableName = "Mapbox GL Tests.app"
+ BlueprintName = "Mapbox GL Tests"
+ ReferencedContainer = "container:ios-tests.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ buildConfiguration = "Release">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DDBD0151196DC3D70033959E"
+ BuildableName = "Test Bundle.xctest"
+ BlueprintName = "Test Bundle"
+ ReferencedContainer = "container:ios-tests.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DD043322196DB9BC00E6F39D"
+ BuildableName = "Mapbox GL Tests.app"
+ BlueprintName = "Mapbox GL Tests"
+ ReferencedContainer = "container:ios-tests.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ </TestAction>
+ <LaunchAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Debug"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DD043322196DB9BC00E6F39D"
+ BuildableName = "Mapbox GL Tests.app"
+ BlueprintName = "Mapbox GL Tests"
+ ReferencedContainer = "container:ios-tests.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Release"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DD043322196DB9BC00E6F39D"
+ BuildableName = "Mapbox GL Tests.app"
+ BlueprintName = "Mapbox GL Tests"
+ ReferencedContainer = "container:ios-tests.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/test/ios/main.m b/test/ios/main.m
new file mode 100644
index 0000000000..d79750dcdd
--- /dev/null
+++ b/test/ios/main.m
@@ -0,0 +1,9 @@
+#import <UIKit/UIKit.h>
+#import "MGLTAppDelegate.h"
+
+int main(int argc, char * argv[])
+{
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([MGLTAppDelegate class]));
+ }
+}