summaryrefslogtreecommitdiff
path: root/platform/macos
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2016-06-13 14:47:13 -0700
committerMinh Nguyễn <mxn@1ec5.org>2016-06-18 14:58:07 -0700
commit061854990ffe495910a5f0dc7c6bb5d394f772e9 (patch)
treee0f4581369d44fb3c7501abaa8d624588e2ab3fc /platform/macos
parentd564302c6d376da123f01028124b1702b13ad1ef (diff)
downloadqtlocation-mapboxgl-061854990ffe495910a5f0dc7c6bb5d394f772e9.tar.gz
[macos] Renamed OS X SDK to macOS SDK
Also renamed as many references to OS X as possible to macOS in documentation. Cherry-picked from b9702ef41a4cfdd0ab3107cfe5cec16ba3a4c230.
Diffstat (limited to 'platform/macos')
-rw-r--r--platform/macos/CHANGELOG.md15
-rw-r--r--platform/macos/DEVELOPING.md53
-rw-r--r--platform/macos/INSTALL.md50
-rw-r--r--platform/macos/README.md12
-rw-r--r--platform/macos/WorkspaceSettings.xcsettings18
-rw-r--r--platform/macos/app/AppDelegate.h24
-rw-r--r--platform/macos/app/AppDelegate.m270
-rw-r--r--platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon128x128.pngbin0 -> 3668 bytes
-rw-r--r--platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon16x16.pngbin0 -> 713 bytes
-rw-r--r--platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon256x256-1.pngbin0 -> 8495 bytes
-rw-r--r--platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon256x256.pngbin0 -> 8495 bytes
-rw-r--r--platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon32x32-1.pngbin0 -> 1213 bytes
-rw-r--r--platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon32x32.pngbin0 -> 1213 bytes
-rw-r--r--platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon512x512-1.pngbin0 -> 20280 bytes
-rw-r--r--platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon512x512.pngbin0 -> 20280 bytes
-rw-r--r--platform/macos/app/Assets.xcassets/AppIcon.appiconset/Contents.json68
-rw-r--r--platform/macos/app/Assets.xcassets/AppIcon.appiconset/icon-1.pngbin0 -> 2205 bytes
-rw-r--r--platform/macos/app/Assets.xcassets/AppIcon.appiconset/icon.pngbin0 -> 9293 bytes
-rw-r--r--platform/macos/app/Assets.xcassets/Contents.json6
-rw-r--r--platform/macos/app/Base.lproj/MainMenu.xib845
-rw-r--r--platform/macos/app/Base.lproj/MapDocument.xib142
-rw-r--r--platform/macos/app/Credits.rtf9
-rw-r--r--platform/macos/app/DroppedPinAnnotation.h10
-rw-r--r--platform/macos/app/DroppedPinAnnotation.m68
-rw-r--r--platform/macos/app/Info.plist56
-rw-r--r--platform/macos/app/LocationCoordinate2DTransformer.h5
-rw-r--r--platform/macos/app/LocationCoordinate2DTransformer.m31
-rw-r--r--platform/macos/app/MapDocument.h14
-rw-r--r--platform/macos/app/MapDocument.m778
-rw-r--r--platform/macos/app/OfflinePackNameValueTransformer.h5
-rw-r--r--platform/macos/app/OfflinePackNameValueTransformer.m33
-rw-r--r--platform/macos/app/TimeIntervalTransformer.h5
-rw-r--r--platform/macos/app/TimeIntervalTransformer.m53
-rw-r--r--platform/macos/app/main.m5
-rw-r--r--platform/macos/bitrise.yml54
-rw-r--r--platform/macos/docs/doc-README.md9
-rw-r--r--platform/macos/docs/pod-README.md43
-rw-r--r--platform/macos/jazzy.yml84
-rw-r--r--platform/macos/macos.xcodeproj/project.pbxproj1178
-rw-r--r--platform/macos/macos.xcodeproj/project.xcworkspace/contents.xcworkspacedata7
-rw-r--r--platform/macos/macos.xcodeproj/xcshareddata/xcschemes/CI.xcscheme128
-rw-r--r--platform/macos/macos.xcodeproj/xcshareddata/xcschemes/dynamic.xcscheme113
-rw-r--r--platform/macos/macos.xcodeproj/xcshareddata/xcschemes/macosapp.xcscheme101
-rw-r--r--platform/macos/macos.xcworkspace/contents.xcworkspacedata10
-rw-r--r--platform/macos/macos.xcworkspace/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist25
-rw-r--r--platform/macos/macos.xcworkspace/xcshareddata/xcschemes/mbgl-offline.xcscheme92
-rw-r--r--platform/macos/macos.xcworkspace/xcshareddata/xcschemes/mbgl-render.xcscheme92
-rw-r--r--platform/macos/macos.xcworkspace/xcshareddata/xcschemes/test.xcscheme92
-rw-r--r--platform/macos/platform.gyp104
-rw-r--r--platform/macos/screenshot.pngbin0 -> 680704 bytes
-rw-r--r--platform/macos/scripts/configure.sh18
-rwxr-xr-xplatform/macos/scripts/document.sh45
-rw-r--r--platform/macos/scripts/macostest.xcscheme56
-rwxr-xr-xplatform/macos/scripts/package.sh57
-rw-r--r--platform/macos/sdk/Base.lproj/Localizable.strings30
-rw-r--r--platform/macos/sdk/Base.lproj/MGLAnnotationCallout.xib69
-rw-r--r--platform/macos/sdk/Info.plist24
-rw-r--r--platform/macos/sdk/default_marker.pdfbin0 -> 2601 bytes
-rw-r--r--platform/macos/sdk/mapbox.pdfbin0 -> 3762 bytes
-rw-r--r--platform/macos/src/MGLAnnotationImage.h64
-rw-r--r--platform/macos/src/MGLAnnotationImage.m26
-rw-r--r--platform/macos/src/MGLAnnotationImage_Private.h8
-rw-r--r--platform/macos/src/MGLAttributionButton.h15
-rw-r--r--platform/macos/src/MGLAttributionButton.m50
-rw-r--r--platform/macos/src/MGLCompassCell.h5
-rw-r--r--platform/macos/src/MGLCompassCell.m34
-rw-r--r--platform/macos/src/MGLMapView+IBAdditions.h68
-rw-r--r--platform/macos/src/MGLMapView+IBAdditions.m118
-rw-r--r--platform/macos/src/MGLMapView.h903
-rw-r--r--platform/macos/src/MGLMapView.mm2499
-rw-r--r--platform/macos/src/MGLMapViewDelegate.h220
-rw-r--r--platform/macos/src/MGLMapView_Private.h23
-rw-r--r--platform/macos/src/MGLOpenGLLayer.h12
-rw-r--r--platform/macos/src/MGLOpenGLLayer.mm49
-rw-r--r--platform/macos/src/Mapbox.h34
-rw-r--r--platform/macos/test/Info.plist24
76 files changed, 9158 insertions, 0 deletions
diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md
new file mode 100644
index 0000000000..95cb154201
--- /dev/null
+++ b/platform/macos/CHANGELOG.md
@@ -0,0 +1,15 @@
+# Changelog for Mapbox macOS SDK
+
+## master
+
+* Renamed the SDK to the Mapbox macOS SDK.
+* Fixed an issue in which Mapbox.framework was nested inside another folder named Mapbox.framework. ([#4998](https://github.com/mapbox/mapbox-gl-native/pull/4998))
+* Added methods to MGLMapView for obtaining the underlying map data rendered by the current style, along with additional classes to represent complex geometry in that data. ([#5110](https://github.com/mapbox/mapbox-gl-native/pull/5110))
+* An MGLPolygon can now have interior polygons, representing holes knocked out of the overall shape. ([#5110](https://github.com/mapbox/mapbox-gl-native/pull/5110))
+* Fixed a crash passing a mixture of point and shape annotations into `-[MGLMapView addAnnotations:]`. ([#5097](https://github.com/mapbox/mapbox-gl-native/pull/5097))
+* Added new options to `MGLMapDebugMaskOptions` that show wireframes and the stencil buffer instead of the color buffer. ([#4359](https://github.com/mapbox/mapbox-gl-native/pull/4359))
+* Fixed a memory leak when using raster resources. ([#5141](https://github.com/mapbox/mapbox-gl-native/pull/5141))
+
+## 0.1.0
+
+* This version of the Mapbox OS X SDK roughly corresponds to version 3.3.0-alpha.2 of the Mapbox iOS SDK. The two SDKs have very similar feature sets. The main difference is the lack of user location tracking. Some APIs have been adapted to OS X conventions, particularly the use of NSPopover for callout views.
diff --git a/platform/macos/DEVELOPING.md b/platform/macos/DEVELOPING.md
new file mode 100644
index 0000000000..fc7b0330a3
--- /dev/null
+++ b/platform/macos/DEVELOPING.md
@@ -0,0 +1,53 @@
+# Contributing to the Mapbox macOS SDK
+
+This document explains how to build the Mapbox macOS SDK from source. It is intended for advanced developers who wish to contribute to Mapbox GL and the Mapbox iOS SDK.
+
+## Requirements
+
+The Mapbox macOS SDK and the macosapp demo application run on macOS 10.10.0 and above.
+
+## Building the SDK
+
+1. [Install core dependencies](../../INSTALL.md).
+1. Run `make xproj`.
+1. Switch to the “dynamic” or “macosapp” scheme. The former builds just the Cocoa framework, while the latter also builds a Cocoa demo application based on it.
+
+## Contributing
+
+### Adding a source code file
+
+To add an Objective-C header or implementation file to the macOS SDK:
+
+1. Add the file to the “dynamic” target’s Headers or Compile Sources build phase, as appropriate. You can either use the Build Phases tab of the project editor or the Target Membership section of the File inspector.
+1. _(Optional.)_ If it’s a public header, change its visibility from Project to Public and import it in [the macOS SDK’s umbrella header](./src/Mapbox.h).
+1. _(Optional.)_ If the file would also be used by the iOS SDK, make sure it’s in [platform/darwin/src/](../darwin/src/), then consult [the companion iOS document](../ios/DEVELOPING.md#adding-a-source-code-file) for further instructions.
+
+### Adding a resource
+
+To add a resource (such as an image, SSL certificate, property list, or strings table) to the macOS SDK:
+
+1. Add the header to the Copy Bundle Resources build phase of the “dynamic” target. You can either use the Build Phases tab of the project editor or the Target Membership section of the File inspector.
+1. _(Optional.)_ If the resource would also be used by the iOS SDK, make sure it’s in [platform/darwin/resources/](../darwin/resources/), then consult [the companion iOS document](../ios/DEVELOPING.md#adding-a-resource) for further instructions.
+
+### Adding user-facing text
+
+To add or update text that the user may see in the macOS SDK:
+
+1. Make sure the implementation file imports [NSBundle+MGLAdditions.h](../darwin/src/NSBundle+MGLAdditions.h).
+1. Use the `NSLocalizedStringWithDefaultValue()` macro:
+ * `key` is a unique identifier that won’t change if the user-facing text ever needs to change.
+ * `tbl` is `Foundation` in code shared between the iOS and macOS SDKs, or `nil` otherwise.
+ * `bundle` is `nil`; the redefined macro looks for the SDK bundle at runtime and ignores this argument.
+ * `val` is the English string.
+1. _(Optional.)_ When dealing with a number followed by a pluralized word, do not split the string. Instead, use a format string and make `val` ambiguous, like `%d file(s)`. Then pluralize for English in the appropriate [.stringsdict file](https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPInternational/StringsdictFileFormat/StringsdictFileFormat.html). See [platform/darwin/resources/en.lproj/Foundation.stringsdict](../darwin/resources/en.lproj/Foundation.stringsdict) for an example. Localizers should do likewise for their languages.
+1. Run `make genstrings` and commit any changes it makes to .strings files. The make rule also updates the iOS SDK’s strings tables.
+
+## Access tokens
+
+The demo applications use Mapbox vector tiles, which require a Mapbox account and API access token. Obtain an access token on the [Mapbox account page](https://www.mapbox.com/studio/account/tokens/). You will be prompted for this access token the first time you launch the demo application.
+
+## Using macosapp
+
+Through the macOS SDK, the demo application supports a variety of standard gestures and keyboard shortcuts. For more details, open Mapbox GL Help from the Help menu.
+
+You can also [integrate the Mapbox macOS SDK into your own Cocoa application](INSTALL.md).
diff --git a/platform/macos/INSTALL.md b/platform/macos/INSTALL.md
new file mode 100644
index 0000000000..42896d380d
--- /dev/null
+++ b/platform/macos/INSTALL.md
@@ -0,0 +1,50 @@
+# Integrating the Mapbox macOS SDK into your application
+
+This document explains how to build the Mapbox macOS SDK and integrate it into your own Cocoa application.
+
+### Requirements
+
+The Mapbox macOS SDK requires the macOS 10.10.0 SDK or above.
+
+### Building the SDK
+
+Grab a [prebuilt release](https://github.com/mapbox/mapbox-gl-native/releases/) – look for the releases that begin with “macos-” – or build the SDK from source:
+
+1. [Install core dependencies](../../INSTALL.md).
+1. Run `make xpackage`, which produces a `Mapbox.framework` in the `build/macos/pkg/` folder.
+
+### Installation
+
+1. Open the project editor, select your application target, then go to the General tab. Drag Mapbox.framework into the “Embedded Binaries” section. (Don’t drag it into the “Linked Frameworks and Libraries” section; Xcode will add it there automatically.) In the sheet that appears, make sure “Copy items if needed” is checked, then click Finish.
+
+1. Mapbox vector tiles require a Mapbox account and API access token. In the project editor, select the application target, then go to the Info tab. Under the “Custom macOS Application Target Properties” section, set `MGLMapboxAccessToken` to your access token. You can obtain an access token from the [Mapbox account page](https://www.mapbox.com/studio/account/tokens/).
+
+## Usage
+
+In a storyboard or XIB, add a view to your view controller. (Drag Custom View from the Object library to the View Controller scene on the Interface Builder canvas.) In the Identity inspector, set the view’s custom class to `MGLMapView`. If you need to manipulate the map view programmatically:
+
+1. Switch to the Assistant Editor.
+1. Import the `Mapbox` module.
+1. Connect the map view to a new outlet in your view controller class. (Control-drag from the map view in Interface Builder to a valid location in your view controller implementation.) The resulting outlet declaration should look something like this:
+
+```objc
+// ViewController.m
+@import Mapbox;
+
+@interface ViewController : NSViewController
+
+@property (strong) IBOutlet MGLMapView *mapView;
+
+@end
+```
+
+```swift
+// ViewController.swift
+import Mapbox
+
+class ViewController: NSViewController {
+ @IBOutlet var mapView: MGLMapView!
+}
+```
+
+Run `make xdocument` to generate complete API documentation. The [Mapbox iOS SDK](https://www.mapbox.com/ios-sdk/)’s [API documentation](https://www.mapbox.com/ios-sdk/api/) and [online examples](https://www.mapbox.com/ios-sdk/examples/) apply to the Mapbox macOS SDK with few differences, mostly around unimplemented features like user location tracking.
diff --git a/platform/macos/README.md b/platform/macos/README.md
new file mode 100644
index 0000000000..211b238d55
--- /dev/null
+++ b/platform/macos/README.md
@@ -0,0 +1,12 @@
+# Mapbox macOS SDK
+
+[![Bitrise](https://www.bitrise.io/app/155ef7da24b38dcd.svg?token=4KSOw_gd6WxTnvGE2rMttg&branch=master)](https://www.bitrise.io/app/155ef7da24b38dcd)
+
+A library based on [Mapbox GL Native](../../README.md) for embedding interactive map views with scalable, customizable vector maps into Cocoa applications on macOS 10.10.0 and above using Objective-C, Swift, or Interface Builder.
+
+This SDK is analogous to the Mapbox iOS SDK, and much of the iOS SDK documentation applies here. Mapbox does not officially support the macOS SDK to the same extent as the iOS SDK; however, bug reports and pull requests are certainly welcome.
+
+* [Integrating the Mapbox macOS SDK into your application](INSTALL.md)
+* [Contributing to the Mapbox macOS SDK](DEVELOPING.md)
+
+<img alt="" src="screenshot.png" width="645">
diff --git a/platform/macos/WorkspaceSettings.xcsettings b/platform/macos/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000000..a2d959210c
--- /dev/null
+++ b/platform/macos/WorkspaceSettings.xcsettings
@@ -0,0 +1,18 @@
+<?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>BuildLocationStyle</key>
+ <string>UseAppPreferences</string>
+ <key>CustomBuildLocationType</key>
+ <string>RelativeToDerivedData</string>
+ <key>DerivedDataCustomLocation</key>
+ <string>../../build</string>
+ <key>DerivedDataLocationStyle</key>
+ <string>WorkspaceRelativePath</string>
+ <key>IssueFilterStyle</key>
+ <string>ShowActiveSchemeOnly</string>
+ <key>LiveSourceIssuesEnabled</key>
+ <true/>
+</dict>
+</plist>
diff --git a/platform/macos/app/AppDelegate.h b/platform/macos/app/AppDelegate.h
new file mode 100644
index 0000000000..a1d9297b2f
--- /dev/null
+++ b/platform/macos/app/AppDelegate.h
@@ -0,0 +1,24 @@
+#import <Mapbox/Mapbox.h>
+
+extern NSString * const MGLMapboxAccessTokenDefaultsKey;
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+
+@property (weak) IBOutlet NSWindow *preferencesWindow;
+
+// Normally, an application should respect the “Close windows when quitting an
+// application” setting in the General pane of System Preferences. But the map
+// would only be restored to its last opened location if the user quits the
+// application using Quit and Keep Windows. An application that displays only a
+// map should restore the last viewed map, like Maps.app does. These properties
+// temporarily hold state for the next map window to be opened.
+
+@property (assign) double pendingZoomLevel;
+@property (copy) MGLMapCamera *pendingCamera;
+@property (assign) MGLCoordinateBounds pendingVisibleCoordinateBounds;
+@property (assign) double pendingMinimumZoomLevel;
+@property (assign) double pendingMaximumZoomLevel;
+@property (copy) NSURL *pendingStyleURL;
+@property (assign) MGLMapDebugMaskOptions pendingDebugMask;
+
+@end
diff --git a/platform/macos/app/AppDelegate.m b/platform/macos/app/AppDelegate.m
new file mode 100644
index 0000000000..b7860cf130
--- /dev/null
+++ b/platform/macos/app/AppDelegate.m
@@ -0,0 +1,270 @@
+#import "AppDelegate.h"
+
+#import "MapDocument.h"
+
+NSString * const MGLMapboxAccessTokenDefaultsKey = @"MGLMapboxAccessToken";
+NSString * const MGLLastMapCameraDefaultsKey = @"MGLLastMapCamera";
+NSString * const MGLLastMapStyleURLDefaultsKey = @"MGLLastMapStyleURL";
+NSString * const MGLLastMapDebugMaskDefaultsKey = @"MGLLastMapDebugMask";
+
+/**
+ Some convenience methods to make offline pack properties easier to bind to.
+ */
+@implementation MGLOfflinePack (Additions)
+
++ (NSSet *)keyPathsForValuesAffectingStateImage {
+ return [NSSet setWithObjects:@"state", nil];
+}
+
+- (NSImage *)stateImage {
+ switch (self.state) {
+ case MGLOfflinePackStateComplete:
+ return [NSImage imageNamed:@"NSMenuOnStateTemplate"];
+
+ case MGLOfflinePackStateActive:
+ return [NSImage imageNamed:@"NSFollowLinkFreestandingTemplate"];
+
+ default:
+ return nil;
+ }
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCountOfResourcesCompleted {
+ return [NSSet setWithObjects:@"progress", nil];
+}
+
+- (uint64_t)countOfResourcesCompleted {
+ return self.progress.countOfResourcesCompleted;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCountOfResourcesExpected {
+ return [NSSet setWithObjects:@"progress", nil];
+}
+
+- (uint64_t)countOfResourcesExpected {
+ return self.progress.countOfResourcesExpected;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCountOfBytesCompleted {
+ return [NSSet setWithObjects:@"progress", nil];
+}
+
+- (uint64_t)countOfBytesCompleted {
+ return self.progress.countOfBytesCompleted;
+}
+
+@end
+
+@interface AppDelegate ()
+
+@property (weak) IBOutlet NSArrayController *offlinePacksArrayController;
+@property (weak) IBOutlet NSPanel *offlinePacksPanel;
+
+@end
+
+@implementation AppDelegate
+
+#pragma mark Lifecycle
+
++ (void)load {
+ // Set access token, unless MGLAccountManager already read it in from Info.plist.
+ if (![MGLAccountManager accessToken]) {
+ 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:MGLMapboxAccessTokenDefaultsKey];
+ } else {
+ // Try to retrieve from preferences, maybe we've stored them there previously and can reuse
+ // the token.
+ accessToken = [[NSUserDefaults standardUserDefaults] stringForKey:MGLMapboxAccessTokenDefaultsKey];
+ }
+ [MGLAccountManager setAccessToken:accessToken];
+ }
+}
+
+- (void)applicationWillFinishLaunching:(NSNotification *)notification {
+ [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self
+ andSelector:@selector(handleGetURLEvent:withReplyEvent:)
+ forEventClass:kInternetEventClass
+ andEventID:kAEGetURL];
+
+ if (![[NSUserDefaults standardUserDefaults] boolForKey:@"NSQuitAlwaysKeepsWindows"]) {
+ NSData *cameraData = [[NSUserDefaults standardUserDefaults] objectForKey:MGLLastMapCameraDefaultsKey];
+ if (cameraData) {
+ NSKeyedUnarchiver *coder = [[NSKeyedUnarchiver alloc] initForReadingWithData:cameraData];
+ self.pendingZoomLevel = -1;
+ self.pendingCamera = [[MGLMapCamera alloc] initWithCoder:coder];
+ }
+ NSString *styleURLString = [[NSUserDefaults standardUserDefaults] objectForKey:MGLLastMapStyleURLDefaultsKey];
+ if (styleURLString) {
+ self.pendingStyleURL = [NSURL URLWithString:styleURLString];
+ }
+ self.pendingDebugMask = [[NSUserDefaults standardUserDefaults] integerForKey:MGLLastMapDebugMaskDefaultsKey];
+ }
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
+ // Set access token, unless MGLAccountManager already read it in from Info.plist.
+ if (![MGLAccountManager accessToken]) {
+ NSAlert *alert = [[NSAlert alloc] init];
+ alert.messageText = @"Access token required";
+ alert.informativeText = @"To load Mapbox-hosted tiles and styles, enter your Mapbox access token in Preferences.";
+ [alert addButtonWithTitle:@"Open Preferences"];
+ [alert runModal];
+ [self showPreferences:nil];
+ }
+
+ [self.offlinePacksArrayController bind:@"content" toObject:[MGLOfflineStorage sharedOfflineStorage] withKeyPath:@"packs" options:nil];
+}
+
+- (void)applicationWillTerminate:(NSNotification *)notification {
+ if (![[NSUserDefaults standardUserDefaults] boolForKey:@"NSQuitAlwaysKeepsWindows"]) {
+ NSDocument *currentDocument = [NSDocumentController sharedDocumentController].currentDocument;
+ if ([currentDocument isKindOfClass:[MapDocument class]]) {
+ MGLMapView *mapView = [(MapDocument *)currentDocument mapView];
+ NSMutableData *cameraData = [NSMutableData data];
+ NSKeyedArchiver *coder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:cameraData];
+ [mapView.camera encodeWithCoder:coder];
+ [coder finishEncoding];
+ [[NSUserDefaults standardUserDefaults] setObject:cameraData forKey:MGLLastMapCameraDefaultsKey];
+ [[NSUserDefaults standardUserDefaults] setObject:mapView.styleURL.absoluteString forKey:MGLLastMapStyleURLDefaultsKey];
+ [[NSUserDefaults standardUserDefaults] setInteger:mapView.debugMask forKey:MGLLastMapDebugMaskDefaultsKey];
+ }
+ }
+
+ [self.offlinePacksArrayController unbind:@"content"];
+}
+
+#pragma mark Services
+
+- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
+ // mapboxgl://?center=29.95,-90.066667&zoom=14&bearing=45&pitch=30
+ NSURL *url = [NSURL URLWithString:[event paramDescriptorForKeyword:keyDirectObject].stringValue];
+ NS_MUTABLE_DICTIONARY_OF(NSString *, NSString *) *params = [[NSMutableDictionary alloc] init];
+ for (NSString *param in [url.query componentsSeparatedByString:@"&"]) {
+ NSArray *parts = [param componentsSeparatedByString:@"="];
+ if (parts.count >= 2) {
+ params[parts[0]] = [parts[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ }
+ }
+
+ MGLMapCamera *camera = [MGLMapCamera camera];
+ NSString *zoomLevelString = params[@"zoom"];
+ self.pendingZoomLevel = zoomLevelString.length ? zoomLevelString.doubleValue : -1;
+
+ NSString *directionString = params[@"bearing"];
+ if (directionString.length) {
+ camera.heading = directionString.doubleValue;
+ }
+
+ NSString *centerString = params[@"center"];
+ if (centerString) {
+ NSArray *coordinateValues = [centerString componentsSeparatedByString:@","];
+ if (coordinateValues.count == 2) {
+ camera.centerCoordinate = CLLocationCoordinate2DMake([coordinateValues[0] doubleValue],
+ [coordinateValues[1] doubleValue]);
+ }
+ }
+
+ NSString *pitchString = params[@"pitch"];
+ if (pitchString.length) {
+ camera.pitch = pitchString.doubleValue;
+ }
+
+ self.pendingCamera = camera;
+ [[NSDocumentController sharedDocumentController] openUntitledDocumentAndDisplay:YES error:NULL];
+}
+
+#pragma mark Offline pack management
+
+- (IBAction)showOfflinePacksPanel:(id)sender {
+ [self.offlinePacksPanel makeKeyAndOrderFront:sender];
+
+ for (MGLOfflinePack *pack in self.offlinePacksArrayController.arrangedObjects) {
+ [pack requestProgress];
+ }
+}
+
+- (IBAction)delete:(id)sender {
+ for (MGLOfflinePack *pack in self.offlinePacksArrayController.selectedObjects) {
+ [[MGLOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) {
+ if (error) {
+ [[NSAlert alertWithError:error] runModal];
+ }
+ }];
+ }
+}
+
+- (IBAction)chooseOfflinePack:(id)sender {
+ for (MGLOfflinePack *pack in self.offlinePacksArrayController.selectedObjects) {
+ switch (pack.state) {
+ case MGLOfflinePackStateComplete:
+ {
+ if ([pack.region isKindOfClass:[MGLTilePyramidOfflineRegion class]]) {
+ MGLTilePyramidOfflineRegion *region = (MGLTilePyramidOfflineRegion *)pack.region;
+ self.pendingVisibleCoordinateBounds = region.bounds;
+ self.pendingMinimumZoomLevel = region.minimumZoomLevel;
+ self.pendingMaximumZoomLevel = region.maximumZoomLevel;
+ [[NSDocumentController sharedDocumentController] openUntitledDocumentAndDisplay:YES error:NULL];
+ }
+ break;
+ }
+
+ case MGLOfflinePackStateInactive:
+ [pack resume];
+ break;
+
+ case MGLOfflinePackStateActive:
+ [pack suspend];
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+#pragma mark Help methods
+
+- (IBAction)showShortcuts:(id)sender {
+ NSAlert *alert = [[NSAlert alloc] init];
+ alert.messageText = @"Mapbox GL Help";
+ alert.informativeText = @"\
+• To scroll, swipe with two fingers on a trackpad, or drag the cursor, or press the arrow keys.\n\
+• To zoom in, pinch two fingers apart on a trackpad, or double-click, or hold down Shift while dragging the cursor down, or hold down Option while pressing the up key.\n\
+• To zoom out, pinch two fingers together on a trackpad, or double-tap on a mouse, or hold down Shift while dragging the cursor up, or hold down Option while pressing the down key.\n\
+• To rotate, move two fingers opposite each other in a circle on a trackpad, or hold down Option while dragging the cursor left and right, or hold down Option while pressing the left and right arrow keys.\n\
+• To tilt, hold down Option while dragging the cursor up and down.\n\
+• To drop a pin, click and hold.\
+";
+ [alert runModal];
+}
+
+- (IBAction)showPreferences:(id)sender {
+ [self.preferencesWindow makeKeyAndOrderFront:sender];
+}
+
+- (IBAction)openAccessTokenManager:(id)sender {
+ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.mapbox.com/studio/account/tokens/"]];
+}
+
+#pragma mark User interface validation
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
+ if (menuItem.action == @selector(showShortcuts:)) {
+ return YES;
+ }
+ if (menuItem.action == @selector(showPreferences:)) {
+ return YES;
+ }
+ if (menuItem.action == @selector(showOfflinePacksPanel:)) {
+ return YES;
+ }
+ if (menuItem.action == @selector(delete:)) {
+ return self.offlinePacksArrayController.selectedObjects.count;
+ }
+ return NO;
+}
+
+@end
diff --git a/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon128x128.png b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon128x128.png
new file mode 100644
index 0000000000..145d5a7d85
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon128x128.png
Binary files differ
diff --git a/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon16x16.png b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon16x16.png
new file mode 100644
index 0000000000..fa2588dec3
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon16x16.png
Binary files differ
diff --git a/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon256x256-1.png b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon256x256-1.png
new file mode 100644
index 0000000000..18fec77f84
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon256x256-1.png
Binary files differ
diff --git a/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon256x256.png b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon256x256.png
new file mode 100644
index 0000000000..18fec77f84
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon256x256.png
Binary files differ
diff --git a/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon32x32-1.png b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon32x32-1.png
new file mode 100644
index 0000000000..bf3acc1282
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon32x32-1.png
Binary files differ
diff --git a/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon32x32.png b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon32x32.png
new file mode 100644
index 0000000000..bf3acc1282
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon32x32.png
Binary files differ
diff --git a/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon512x512-1.png b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon512x512-1.png
new file mode 100644
index 0000000000..1ea7683696
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon512x512-1.png
Binary files differ
diff --git a/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon512x512.png b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon512x512.png
new file mode 100644
index 0000000000..1ea7683696
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/AppIcon512x512.png
Binary files differ
diff --git a/platform/macos/app/Assets.xcassets/AppIcon.appiconset/Contents.json b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000000..58e739d056
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+ "images" : [
+ {
+ "size" : "16x16",
+ "idiom" : "mac",
+ "filename" : "AppIcon16x16.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "16x16",
+ "idiom" : "mac",
+ "filename" : "AppIcon32x32-1.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "32x32",
+ "idiom" : "mac",
+ "filename" : "AppIcon32x32.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "32x32",
+ "idiom" : "mac",
+ "filename" : "icon-1.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "128x128",
+ "idiom" : "mac",
+ "filename" : "AppIcon128x128.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "128x128",
+ "idiom" : "mac",
+ "filename" : "AppIcon256x256-1.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "256x256",
+ "idiom" : "mac",
+ "filename" : "AppIcon256x256.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "256x256",
+ "idiom" : "mac",
+ "filename" : "AppIcon512x512-1.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "512x512",
+ "idiom" : "mac",
+ "filename" : "AppIcon512x512.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "512x512",
+ "idiom" : "mac",
+ "filename" : "icon.png",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/platform/macos/app/Assets.xcassets/AppIcon.appiconset/icon-1.png b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/icon-1.png
new file mode 100644
index 0000000000..36dd7acf90
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/icon-1.png
Binary files differ
diff --git a/platform/macos/app/Assets.xcassets/AppIcon.appiconset/icon.png b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/icon.png
new file mode 100644
index 0000000000..fdee900aa4
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/AppIcon.appiconset/icon.png
Binary files differ
diff --git a/platform/macos/app/Assets.xcassets/Contents.json b/platform/macos/app/Assets.xcassets/Contents.json
new file mode 100644
index 0000000000..da4a164c91
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/platform/macos/app/Base.lproj/MainMenu.xib b/platform/macos/app/Base.lproj/MainMenu.xib
new file mode 100644
index 0000000000..c80428ff00
--- /dev/null
+++ b/platform/macos/app/Base.lproj/MainMenu.xib
@@ -0,0 +1,845 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+ <connections>
+ <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject">
+ <connections>
+ <outlet property="delegate" destination="Voe-Tx-rLC" id="z9N-Fm-MUP"/>
+ </connections>
+ </customObject>
+ <customObject id="Voe-Tx-rLC" customClass="AppDelegate">
+ <connections>
+ <outlet property="offlinePacksArrayController" destination="dWe-R6-sRz" id="Ar5-xu-ABm"/>
+ <outlet property="offlinePacksPanel" destination="Jjv-gs-Tx6" id="0vK-rR-3ZX"/>
+ <outlet property="preferencesWindow" destination="UWc-yQ-qda" id="Ota-aT-Mz2"/>
+ </connections>
+ </customObject>
+ <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+ <items>
+ <menuItem title="Mapbox GL" id="1Xt-HY-uBw">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Mapbox GL" systemMenu="apple" id="uQy-DD-JDr">
+ <items>
+ <menuItem title="About Mapbox GL" id="5kV-Vb-QxS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+ <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW">
+ <connections>
+ <action selector="showPreferences:" target="-1" id="Llx-Uy-HTS"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
+ <menuItem title="Services" id="NMo-om-nkz">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
+ <menuItem title="Hide Mapbox GL" keyEquivalent="h" id="Olw-nP-bQN">
+ <connections>
+ <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show All" id="Kd2-mp-pUS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+ <menuItem title="Quit Mapbox GL" keyEquivalent="q" id="4sb-4s-VLi">
+ <connections>
+ <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="File" id="dMs-cI-mzQ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="File" id="bib-Uj-vzu">
+ <items>
+ <menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
+ <connections>
+ <action selector="newDocument:" target="-1" id="4Si-XN-c54"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
+ <connections>
+ <action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Open Recent" id="tXI-mr-wws">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
+ <items>
+ <menuItem title="Clear Menu" id="vNY-rz-j42">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="clearRecentDocuments:" target="-1" id="Daa-9d-B3U"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
+ <menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
+ <connections>
+ <action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
+ <connections>
+ <action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
+ <connections>
+ <action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Save Offline Pack…" id="UXB-sj-C7i">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="addOfflinePack:" target="-1" id="Usu-xO-QEx"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Revert to Saved" id="KaW-ft-85H">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
+ <menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
+ <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
+ <connections>
+ <action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
+ <connections>
+ <action selector="print:" target="-1" id="qaZ-4w-aoO"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Edit" id="5QF-Oa-p0T">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Edit" id="W48-6f-4Dl">
+ <items>
+ <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
+ <connections>
+ <action selector="undo:" target="-1" id="M6e-cu-g7V"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
+ <connections>
+ <action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
+ <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
+ <connections>
+ <action selector="cut:" target="-1" id="YJe-68-I9s"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
+ <connections>
+ <action selector="copy:" target="-1" id="G1f-GL-Joy"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
+ <connections>
+ <action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Delete" id="pa3-QI-u2k">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
+ <connections>
+ <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
+ <menuItem title="Find" id="4EN-yA-p0u">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Find" id="1b7-l0-nxx">
+ <items>
+ <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
+ <connections>
+ <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
+ <items>
+ <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
+ <connections>
+ <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
+ <connections>
+ <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
+ <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Substitutions" id="9ic-FL-obx">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
+ <items>
+ <menuItem title="Show Substitutions" id="z6F-FW-3nz">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
+ <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Quotes" id="hQb-2v-fYv">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Dashes" id="rgM-f4-ycn">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Links" id="cwL-P1-jid">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Data Detectors" id="tRr-pd-1PS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Text Replacement" id="HFQ-gK-NFA">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Transformations" id="2oI-Rn-ZJC">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Transformations" id="c8a-y6-VQd">
+ <items>
+ <menuItem title="Make Upper Case" id="vmV-6d-7jI">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Make Lower Case" id="d9M-CD-aMd">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Capitalize" id="UEZ-Bs-lqG">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Speech" id="xrE-MZ-jX0">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Speech" id="3rS-ZA-NoH">
+ <items>
+ <menuItem title="Start Speaking" id="Ynk-f8-cLZ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Stop Speaking" id="Oyz-dy-DGm">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="View" id="H8h-7b-M4v">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="View" id="HyV-fh-RgO">
+ <items>
+ <menuItem title="Streets" state="on" tag="1" keyEquivalent="1" id="17N-yz-NNo">
+ <connections>
+ <action selector="setStyle:" target="-1" id="I4L-Wx-UXA"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Outdoors" tag="2" keyEquivalent="2" id="BBa-Qa-SQr">
+ <connections>
+ <action selector="setStyle:" target="-1" id="rM1-yG-t5u"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Light" tag="3" keyEquivalent="3" id="HWe-7u-UVJ">
+ <connections>
+ <action selector="setStyle:" target="-1" id="Q9V-O1-oRz"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Dark" tag="4" keyEquivalent="4" id="6HI-q6-AeV">
+ <connections>
+ <action selector="setStyle:" target="-1" id="YfH-1I-G50"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Satellite" tag="5" keyEquivalent="5" id="h0J-5X-kgF">
+ <connections>
+ <action selector="setStyle:" target="-1" id="GXt-oK-Hy1"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Satellite Streets" tag="6" keyEquivalent="6" id="9BL-00-HFt">
+ <connections>
+ <action selector="setStyle:" target="-1" id="oL4-AC-waq"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Custom Style…" id="L0h-86-2cU">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="chooseCustomStyle:" target="-1" id="QJF-fM-Ty3"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="BMF-ml-0Bd"/>
+ <menuItem title="Zoom In" keyEquivalent="+" id="W82-WO-xvB">
+ <connections>
+ <action selector="zoomIn:" target="-1" id="g33-vK-zUu"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Zoom Out" keyEquivalent="-" id="j7h-PY-edM">
+ <connections>
+ <action selector="zoomOut:" target="-1" id="0pP-tO-9ex"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Snap to North" keyEquivalent="" id="Zss-3w-wkz">
+ <connections>
+ <action selector="snapToNorth:" target="-1" id="Ayq-GE-Lb5"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="mkP-YN-G0w"/>
+ <menuItem title="Reload" keyEquivalent="r" id="JvI-nv-KaE">
+ <connections>
+ <action selector="reload:" target="-1" id="xkh-9F-mOe"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="CyM-Wv-Bnc"/>
+ <menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="toggleToolbarShown:" target="-1" id="BXY-wc-z0C"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Debug" id="ZNC-5r-eBw">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Debug" id="McE-ka-r79">
+ <items>
+ <menuItem title="Show Tile Boundaries" id="rDE-dG-rTR">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleTileBoundaries:" target="-1" id="LAO-88-F7h"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show Tile Info" id="LoH-qD-kb0">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleTileInfo:" target="-1" id="KCn-0G-V87"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show Tile Timestamps" id="bY0-2E-LZ7">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleTileTimestamps:" target="-1" id="tBs-2N-KEG"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show Collision Boxes" id="Y0b-3K-mJE">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleCollisionBoxes:" target="-1" id="EYa-7n-iWZ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show Wireframes" id="hSX-Be-8xC">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleWireframes:" target="-1" id="usj-ug-upt"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="2EG-Hp-4FA"/>
+ <menuItem title="Show Color Buffer" id="Eao-WE-BWz">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="showColorBuffer:" target="-1" id="Nuq-Qs-98g"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show Stencil Buffer" id="LlS-Yh-RkN">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="showStencilBuffer:" target="-1" id="WkN-t9-Mpv"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="dYw-bb-tr1"/>
+ <menuItem title="Show Tooltips on Dropped Pins" id="uir-Rx-zmw">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleShowsToolTipsOnDroppedPins:" target="-1" id="1YC-Co-QQ6"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use Random Cursors for Dropped Pins" id="ZTk-lc-Jgu">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleRandomizesCursorsOnDroppedPins:" target="-1" id="Mpw-b8-oub"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="Sl5-nE-kHd"/>
+ <menuItem title="Blanket Map With Pins" id="LMZ-oe-Ngh">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="dropManyPins:" target="-1" id="Rtv-8N-3Z8"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Add Polygon and Polyline" id="DVr-vT-lpe">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="drawPolygonAndPolyLineAnnotations:" target="-1" id="EhT-CB-gee"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Remove All Annotations" id="6rC-68-vk0">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="removeAllAnnotations:" target="-1" id="6v3-0E-LsR"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="wQq-Mx-QY0"/>
+ <menuItem title="Start World Tour" id="VFo-Jh-2sw">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="startWorldTour:" target="-1" id="66Y-Gm-Yn1"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Stop World Tour" id="Pa8-qU-xfr">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="stopWorldTour:" target="-1" id="aq0-7t-AGi"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Window" id="aUF-d1-5bR">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
+ <items>
+ <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
+ <connections>
+ <action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Zoom" keyEquivalent="z" id="R4o-n2-Eq4">
+ <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+ <connections>
+ <action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="Uix-g7-fAt"/>
+ <menuItem title="Offline Packs" id="YW3-jR-knj">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="showOfflinePacksPanel:" target="Voe-Tx-rLC" id="kj9-ht-KmF"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
+ <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Help" id="wpr-3q-Mcd">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
+ <items>
+ <menuItem title="Mapbox GL Help" keyEquivalent="?" id="FKE-Sm-Kum">
+ <connections>
+ <action selector="showShortcuts:" target="-1" id="hNZ-sm-X2q"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="EpY-wQ-SjH"/>
+ <menuItem title="Improve This Map" id="xu5-WN-qYK">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="giveFeedback:" target="-1" id="cil-i9-r39"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ <window title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="Preferences" animationBehavior="default" id="UWc-yQ-qda">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
+ <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
+ <rect key="contentRect" x="109" y="131" width="350" height="84"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/>
+ <view key="contentView" id="eA4-n3-qPe">
+ <rect key="frame" x="0.0" y="0.0" width="350" height="84"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0IK-AW-Gg3">
+ <rect key="frame" x="18" y="45" width="89" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Access token:" id="Ptd-FI-M5A">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <connections>
+ <accessibilityConnection property="link" destination="7sb-sf-oJU" id="U0t-jC-oQ7"/>
+ </connections>
+ </textField>
+ <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7sb-sf-oJU">
+ <rect key="frame" x="113" y="42" width="197" height="22"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="jlV-TC-NUv">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <connections>
+ <binding destination="45S-yT-WUN" name="value" keyPath="values.MGLMapboxAccessToken" id="iJE-S2-ALY"/>
+ </connections>
+ </textField>
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="c3S-LC-PoX">
+ <rect key="frame" x="318" y="47" width="12" height="12"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="12" id="M3J-pU-gKn"/>
+ </constraints>
+ <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSFollowLinkFreestandingTemplate" imagePosition="only" alignment="center" controlSize="small" imageScaling="proportionallyUpOrDown" inset="2" id="38x-37-Ay0">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ </buttonCell>
+ <connections>
+ <action selector="openAccessTokenManager:" target="-1" id="1LX-4G-roC"/>
+ </connections>
+ </button>
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="7IZ-zl-iT1">
+ <rect key="frame" x="18" y="18" width="109" height="18"/>
+ <buttonCell key="cell" type="check" title="Scroll to zoom" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="hVR-66-JSh">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <binding destination="45S-yT-WUN" name="value" keyPath="values.MGLScrollWheelZoomsMapView" id="2AZ-bk-DM5"/>
+ </connections>
+ </button>
+ </subviews>
+ <constraints>
+ <constraint firstAttribute="trailing" secondItem="c3S-LC-PoX" secondAttribute="trailing" constant="20" symbolic="YES" id="7QU-Jd-Rg6"/>
+ <constraint firstItem="c3S-LC-PoX" firstAttribute="top" secondItem="eA4-n3-qPe" secondAttribute="top" constant="25" id="JOS-HU-27c"/>
+ <constraint firstItem="7sb-sf-oJU" firstAttribute="leading" secondItem="0IK-AW-Gg3" secondAttribute="trailing" constant="8" symbolic="YES" id="SS6-VQ-sLK"/>
+ <constraint firstItem="0IK-AW-Gg3" firstAttribute="leading" secondItem="eA4-n3-qPe" secondAttribute="leading" constant="20" symbolic="YES" id="TYG-io-qfV"/>
+ <constraint firstItem="7sb-sf-oJU" firstAttribute="top" secondItem="eA4-n3-qPe" secondAttribute="top" constant="20" symbolic="YES" id="Vzb-q8-ecP"/>
+ <constraint firstItem="7IZ-zl-iT1" firstAttribute="leading" secondItem="0IK-AW-Gg3" secondAttribute="leading" id="aIY-WX-AW9"/>
+ <constraint firstItem="7IZ-zl-iT1" firstAttribute="top" secondItem="7sb-sf-oJU" secondAttribute="bottom" constant="8" symbolic="YES" id="ide-24-GqL"/>
+ <constraint firstItem="c3S-LC-PoX" firstAttribute="leading" secondItem="7sb-sf-oJU" secondAttribute="trailing" constant="8" symbolic="YES" id="pjl-9u-IgM"/>
+ <constraint firstItem="7sb-sf-oJU" firstAttribute="baseline" secondItem="0IK-AW-Gg3" secondAttribute="baseline" id="qIY-Jr-9Ws"/>
+ <constraint firstAttribute="bottom" secondItem="7IZ-zl-iT1" secondAttribute="bottom" constant="20" symbolic="YES" id="wng-pn-VIz"/>
+ <constraint firstItem="7sb-sf-oJU" firstAttribute="centerY" secondItem="c3S-LC-PoX" secondAttribute="centerY" id="zej-gw-fC0"/>
+ </constraints>
+ </view>
+ <connections>
+ <outlet property="initialFirstResponder" destination="7sb-sf-oJU" id="UZe-di-dnA"/>
+ </connections>
+ <point key="canvasLocation" x="754" y="221"/>
+ </window>
+ <userDefaultsController representsSharedInstance="YES" id="45S-yT-WUN"/>
+ <window title="Offline Packs" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="MBXOfflinePacksPanel" animationBehavior="default" id="Jjv-gs-Tx6" customClass="NSPanel">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" utility="YES"/>
+ <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
+ <rect key="contentRect" x="830" y="430" width="400" height="300"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/>
+ <view key="contentView" id="8ha-hw-zOD">
+ <rect key="frame" x="0.0" y="0.0" width="400" height="300"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Q8b-0e-dLv">
+ <rect key="frame" x="-1" y="20" width="402" height="281"/>
+ <clipView key="contentView" id="J9U-Yx-o2S">
+ <rect key="frame" x="1" y="0.0" width="400" height="280"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" autosaveColumns="NO" headerView="MAZ-Iq-hBi" id="Ato-Vu-HYT">
+ <rect key="frame" x="0.0" y="0.0" width="400" height="257"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <size key="intercellSpacing" width="3" height="2"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
+ <tableColumns>
+ <tableColumn identifier="" editable="NO" width="16" minWidth="10" maxWidth="3.4028234663852886e+38" id="xtw-hQ-8C5" userLabel="State">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+ </tableHeaderCell>
+ <imageCell key="dataCell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="edU-Yw-20f"/>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <connections>
+ <binding destination="dWe-R6-sRz" name="value" keyPath="arrangedObjects.stateImage" id="2wd-1J-TZt"/>
+ </connections>
+ </tableColumn>
+ <tableColumn editable="NO" width="116" minWidth="40" maxWidth="1000" id="2hD-LN-h0L">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Name">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="oys-QZ-34I">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <connections>
+ <binding destination="dWe-R6-sRz" name="value" keyPath="arrangedObjects.context" id="NtD-s5-ZUq">
+ <dictionary key="options">
+ <string key="NSValueTransformerName">OfflinePackNameValueTransformer</string>
+ </dictionary>
+ </binding>
+ </connections>
+ </tableColumn>
+ <tableColumn editable="NO" width="50" minWidth="40" maxWidth="1000" id="pkI-c7-xoD">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Downloaded">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="WfC-qb-HsW">
+ <numberFormatter key="formatter" formatterBehavior="default10_4" numberStyle="decimal" usesGroupingSeparator="NO" groupingSize="0" minimumIntegerDigits="0" maximumIntegerDigits="42" id="sNm-Qn-ne6"/>
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <connections>
+ <binding destination="dWe-R6-sRz" name="value" keyPath="arrangedObjects.countOfResourcesCompleted" id="mu6-Jg-GiU"/>
+ </connections>
+ </tableColumn>
+ <tableColumn identifier="" editable="NO" width="50" minWidth="10" maxWidth="3.4028234663852886e+38" id="Rrd-A9-jqc">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Total">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="mHy-qJ-rOA">
+ <numberFormatter key="formatter" formatterBehavior="default10_4" numberStyle="decimal" usesGroupingSeparator="NO" groupingSize="0" minimumIntegerDigits="0" maximumIntegerDigits="42" id="kyx-ZP-OBH"/>
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <connections>
+ <binding destination="dWe-R6-sRz" name="value" keyPath="arrangedObjects.countOfResourcesExpected" id="mh2-k0-vvB"/>
+ </connections>
+ </tableColumn>
+ <tableColumn identifier="" editable="NO" width="60" minWidth="10" maxWidth="3.4028234663852886e+38" id="h7m-6l-KaS">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Size">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="701-bg-k6L">
+ <byteCountFormatter key="formatter" allowsNonnumericFormatting="NO" id="IXV-J9-sP3"/>
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <connections>
+ <binding destination="dWe-R6-sRz" name="value" keyPath="arrangedObjects.countOfBytesCompleted" id="Zsa-Na-yFN"/>
+ </connections>
+ </tableColumn>
+ </tableColumns>
+ <connections>
+ <action trigger="doubleAction" selector="chooseOfflinePack:" target="-1" id="pUN-eT-zRT"/>
+ </connections>
+ </tableView>
+ </subviews>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </clipView>
+ <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="QLr-6P-Ogs">
+ <rect key="frame" x="1" y="7" width="0.0" height="16"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </scroller>
+ <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="q0K-eE-mzL">
+ <rect key="frame" x="224" y="17" width="15" height="102"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </scroller>
+ <tableHeaderView key="headerView" id="MAZ-Iq-hBi">
+ <rect key="frame" x="0.0" y="0.0" width="400" height="23"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </tableHeaderView>
+ </scrollView>
+ <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="wzf-ce-Spm">
+ <rect key="frame" x="0.0" y="-1" width="21" height="21"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="21" id="5ST-tY-8Ph"/>
+ </constraints>
+ <buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSAddTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" imageScaling="proportionallyDown" inset="2" id="sew-F7-i5T">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <action selector="addOfflinePack:" target="-1" id="SN0-PM-HoU"/>
+ </connections>
+ </button>
+ <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7L7-hr-zId">
+ <rect key="frame" x="20" y="0.0" width="21" height="19"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="21" id="JYb-AF-8gZ"/>
+ </constraints>
+ <buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSRemoveTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" imageScaling="proportionallyDown" inset="2" id="oTF-3m-6qT">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ <string key="keyEquivalent" base64-UTF8="YES">
+CA
+</string>
+ </buttonCell>
+ <connections>
+ <action selector="delete:" target="-1" id="EGL-bf-yUD"/>
+ </connections>
+ </button>
+ </subviews>
+ <constraints>
+ <constraint firstItem="7L7-hr-zId" firstAttribute="centerY" secondItem="wzf-ce-Spm" secondAttribute="centerY" id="7TI-6w-bf1"/>
+ <constraint firstAttribute="bottom" secondItem="Q8b-0e-dLv" secondAttribute="bottom" constant="20" symbolic="YES" id="DZa-ly-bhV"/>
+ <constraint firstItem="wzf-ce-Spm" firstAttribute="top" secondItem="Q8b-0e-dLv" secondAttribute="bottom" id="LhK-5z-CQA"/>
+ <constraint firstItem="Q8b-0e-dLv" firstAttribute="leading" secondItem="8ha-hw-zOD" secondAttribute="leading" constant="-1" id="Oyo-ch-rZo"/>
+ <constraint firstAttribute="bottom" secondItem="7L7-hr-zId" secondAttribute="bottom" id="TtY-j1-T5h"/>
+ <constraint firstItem="Q8b-0e-dLv" firstAttribute="top" secondItem="8ha-hw-zOD" secondAttribute="top" constant="-1" id="WDk-Ig-Grr"/>
+ <constraint firstAttribute="trailing" secondItem="Q8b-0e-dLv" secondAttribute="trailing" constant="-1" id="hHf-rd-Wcv"/>
+ <constraint firstItem="7L7-hr-zId" firstAttribute="leading" secondItem="8ha-hw-zOD" secondAttribute="leading" constant="20" symbolic="YES" id="iKJ-ph-ACS"/>
+ <constraint firstAttribute="bottom" secondItem="wzf-ce-Spm" secondAttribute="bottom" constant="-1" id="jFV-Xi-fWr"/>
+ <constraint firstItem="wzf-ce-Spm" firstAttribute="leading" secondItem="8ha-hw-zOD" secondAttribute="leading" id="kJt-oJ-72R"/>
+ </constraints>
+ </view>
+ <point key="canvasLocation" x="720" y="317"/>
+ </window>
+ <arrayController objectClassName="MGLOfflinePack" editable="NO" avoidsEmptySelection="NO" id="dWe-R6-sRz" userLabel="Offline Packs Array Controller">
+ <declaredKeys>
+ <string>context</string>
+ <string>countOfResourcesCompleted</string>
+ <string>countOfResourcesExpected</string>
+ <string>countOfBytesCompleted</string>
+ <string>stateImage</string>
+ </declaredKeys>
+ </arrayController>
+ </objects>
+ <resources>
+ <image name="NSAddTemplate" width="11" height="11"/>
+ <image name="NSFollowLinkFreestandingTemplate" width="14" height="14"/>
+ <image name="NSRemoveTemplate" width="11" height="11"/>
+ </resources>
+</document>
diff --git a/platform/macos/app/Base.lproj/MapDocument.xib b/platform/macos/app/Base.lproj/MapDocument.xib
new file mode 100644
index 0000000000..55d82d21d0
--- /dev/null
+++ b/platform/macos/app/Base.lproj/MapDocument.xib
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="MapDocument">
+ <connections>
+ <outlet property="mapView" destination="q4d-kF-8Hi" id="7hI-dS-A5R"/>
+ <outlet property="mapViewContextMenu" destination="XbX-6a-Mgy" id="YD0-1r-5N2"/>
+ <outlet property="window" destination="cSv-fg-MAQ" id="TBu-Mu-79N"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" frameAutosaveName="MBXMapWindow" animationBehavior="default" id="cSv-fg-MAQ">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/>
+ <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
+ <rect key="contentRect" x="388" y="211" width="512" height="480"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/>
+ <view key="contentView" id="TuG-C5-zLS">
+ <rect key="frame" x="0.0" y="0.0" width="512" height="480"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <customView translatesAutoresizingMaskIntoConstraints="NO" id="q4d-kF-8Hi" customClass="MGLMapView">
+ <rect key="frame" x="0.0" y="0.0" width="512" height="480"/>
+ <connections>
+ <outlet property="delegate" destination="-2" id="dh2-0H-jFZ"/>
+ <outlet property="menu" destination="XbX-6a-Mgy" id="dSu-HR-Kq2"/>
+ </connections>
+ </customView>
+ </subviews>
+ <constraints>
+ <constraint firstAttribute="bottom" secondItem="q4d-kF-8Hi" secondAttribute="bottom" id="L2t-Be-qWL"/>
+ <constraint firstItem="q4d-kF-8Hi" firstAttribute="top" secondItem="TuG-C5-zLS" secondAttribute="top" id="T8A-o3-Bhq"/>
+ <constraint firstItem="q4d-kF-8Hi" firstAttribute="leading" secondItem="TuG-C5-zLS" secondAttribute="leading" id="fGH-YW-Qd3"/>
+ <constraint firstAttribute="trailing" secondItem="q4d-kF-8Hi" secondAttribute="trailing" id="yfG-iG-K4C"/>
+ </constraints>
+ </view>
+ <toolbar key="toolbar" implicitIdentifier="A3AC6577-4712-4628-813D-113498171A84" allowsUserCustomization="NO" displayMode="iconOnly" sizeMode="regular" id="DTc-AP-Bah">
+ <allowedToolbarItems>
+ <toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="bld-8W-Wgg"/>
+ <toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="z4l-5x-MzK"/>
+ <toolbarItem implicitItemIdentifier="2CB58C0A-7B95-4233-8DD3-F94BFE7D3061" label="Share" paletteLabel="Share" image="NSShareTemplate" id="XJT-Ho-tuZ" customClass="ValidatedToolbarItem">
+ <nil key="toolTip"/>
+ <size key="minSize" width="40" height="32"/>
+ <size key="maxSize" width="48" height="32"/>
+ <button key="view" verticalHuggingPriority="750" id="y6e-ev-rVL">
+ <rect key="frame" x="0.0" y="14" width="48" height="32"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="NSShareTemplate" imagePosition="only" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="TBK-Ra-XzZ">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ </button>
+ <connections>
+ <action selector="showShareMenu:" target="-1" id="fCB-HP-iou"/>
+ </connections>
+ </toolbarItem>
+ <toolbarItem implicitItemIdentifier="BA3542AF-D63A-4893-9CC7-8F67EF2E82B0" label="Style" paletteLabel="Style" id="u23-0z-Otl" customClass="ValidatedToolbarItem">
+ <nil key="toolTip"/>
+ <size key="minSize" width="100" height="26"/>
+ <size key="maxSize" width="120" height="26"/>
+ <popUpButton key="view" verticalHuggingPriority="750" id="Tzm-Cy-dQg">
+ <rect key="frame" x="0.0" y="14" width="120" height="26"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <popUpButtonCell key="cell" type="roundTextured" title="Streets" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="border" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="wvt-tP-O3a" id="3PJ-qK-Oh3">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="menu"/>
+ <menu key="menu" id="xf3-qk-IhF">
+ <items>
+ <menuItem title="Streets" state="on" tag="1" id="wvt-tP-O3a"/>
+ <menuItem title="Outdoors" tag="2" id="RkE-lp-fL9"/>
+ <menuItem title="Light" tag="3" id="R4X-kt-HHb"/>
+ <menuItem title="Dark" tag="4" id="jUC-5X-0Zx">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Satellite" tag="5" id="CTe-e2-o42">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Satellite Streets" tag="6" id="7ly-oA-0ND">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ </items>
+ </menu>
+ </popUpButtonCell>
+ </popUpButton>
+ <connections>
+ <action selector="setStyle:" target="-1" id="2Kw-9i-a3G"/>
+ </connections>
+ </toolbarItem>
+ </allowedToolbarItems>
+ <defaultToolbarItems>
+ <toolbarItem reference="XJT-Ho-tuZ"/>
+ <toolbarItem reference="z4l-5x-MzK"/>
+ <toolbarItem reference="u23-0z-Otl"/>
+ </defaultToolbarItems>
+ <connections>
+ <outlet property="delegate" destination="-2" id="V9D-gS-Tvu"/>
+ </connections>
+ </toolbar>
+ <connections>
+ <binding destination="-2" name="displayPatternTitle1" keyPath="mapView.centerCoordinate" id="wtz-AV-bG1">
+ <dictionary key="options">
+ <string key="NSDisplayPattern">%{title1}@</string>
+ <string key="NSValueTransformerName">LocationCoordinate2DTransformer</string>
+ </dictionary>
+ </binding>
+ <outlet property="delegate" destination="-2" id="HEo-Qf-o6o"/>
+ </connections>
+ </window>
+ <menu title="Map View" id="XbX-6a-Mgy">
+ <items>
+ <menuItem title="Drop Pin" id="qZJ-mM-bLj">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="dropPin:" target="-1" id="hxx-eC-kqU"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Remove Pin" id="Zhx-30-VmE">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="removePin:" target="-1" id="w0R-0B-7mG"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Select Features" id="za5-bY-mdf">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="selectFeatures:" target="-1" id="ikt-CZ-yZT"/>
+ </connections>
+ </menuItem>
+ </items>
+ <connections>
+ <outlet property="delegate" destination="-2" id="oHe-ZP-lyc"/>
+ </connections>
+ <point key="canvasLocation" x="820" y="254.5"/>
+ </menu>
+ </objects>
+ <resources>
+ <image name="NSShareTemplate" width="11" height="16"/>
+ </resources>
+</document>
diff --git a/platform/macos/app/Credits.rtf b/platform/macos/app/Credits.rtf
new file mode 100644
index 0000000000..6b17eb34b2
--- /dev/null
+++ b/platform/macos/app/Credits.rtf
@@ -0,0 +1,9 @@
+{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf130
+{\fonttbl\f0\fnil\fcharset0 SFUIText-Regular;}
+{\colortbl;\red255\green255\blue255;}
+\margl1440\margr1440\vieww10800\viewh8400\viewkind0
+\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0
+
+\f0\fs20 \cf0 Copyright \'a9 {\field{\*\fldinst{HYPERLINK "https://www.mapbox.com/about/maps/"}}{\fldrslt Mapbox}}.\
+\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0
+\cf0 Copyright \'a9 {\field{\*\fldinst{HYPERLINK "http://www.openstreetmap.org/about/"}}{\fldrslt OpenStreetMap contributors}}.} \ No newline at end of file
diff --git a/platform/macos/app/DroppedPinAnnotation.h b/platform/macos/app/DroppedPinAnnotation.h
new file mode 100644
index 0000000000..435a56738b
--- /dev/null
+++ b/platform/macos/app/DroppedPinAnnotation.h
@@ -0,0 +1,10 @@
+#import <Mapbox/Mapbox.h>
+
+@interface DroppedPinAnnotation : MGLPointAnnotation
+
+@property (nonatomic, readonly) NSTimeInterval elapsedShownTime;
+
+- (void)resume;
+- (void)pause;
+
+@end
diff --git a/platform/macos/app/DroppedPinAnnotation.m b/platform/macos/app/DroppedPinAnnotation.m
new file mode 100644
index 0000000000..5b19fd7401
--- /dev/null
+++ b/platform/macos/app/DroppedPinAnnotation.m
@@ -0,0 +1,68 @@
+#import "DroppedPinAnnotation.h"
+
+#import "LocationCoordinate2DTransformer.h"
+#import "TimeIntervalTransformer.h"
+
+#import <Mapbox/Mapbox.h>
+
+static MGLCoordinateFormatter *DroppedPinCoordinateFormatter;
+
+@implementation DroppedPinAnnotation {
+ NSTimer *_timer;
+ NSTimeInterval _priorShownTimeInterval;
+ NSDate *_dateShown;
+
+ NSValueTransformer *_timeIntervalTransformer;
+}
+
++ (void)initialize {
+ if (self == [DroppedPinAnnotation class]) {
+ DroppedPinCoordinateFormatter = [[MGLCoordinateFormatter alloc] init];
+ }
+}
+
+- (instancetype)init {
+ if (self = [super init]) {
+ _timeIntervalTransformer = [NSValueTransformer valueTransformerForName:
+ NSStringFromClass([TimeIntervalTransformer class])];
+ [self update:nil];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self pause];
+}
+
+- (void)setCoordinate:(CLLocationCoordinate2D)coordinate {
+ super.coordinate = coordinate;
+ [self update:nil];
+}
+
+- (NSTimeInterval)elapsedShownTime {
+ return _priorShownTimeInterval - _dateShown.timeIntervalSinceNow;
+}
+
+- (void)resume {
+ _dateShown = [NSDate date];
+ _timer = [NSTimer scheduledTimerWithTimeInterval:1
+ target:self
+ selector:@selector(update:)
+ userInfo:nil
+ repeats:YES];
+}
+
+- (void)pause {
+ [_timer invalidate];
+ _timer = nil;
+ _priorShownTimeInterval -= _dateShown.timeIntervalSinceNow;
+ _dateShown = nil;
+}
+
+- (void)update:(NSTimer *)timer {
+ NSString *coordinate = [DroppedPinCoordinateFormatter stringFromCoordinate:self.coordinate];
+ NSString *elapsedTime = [_timeIntervalTransformer transformedValue:@(self.elapsedShownTime)];
+ self.subtitle = [NSString stringWithFormat:@"%@\nSelected for %@", coordinate, elapsedTime];
+}
+
+@end
diff --git a/platform/macos/app/Info.plist b/platform/macos/app/Info.plist
new file mode 100644
index 0000000000..cc7037f589
--- /dev/null
+++ b/platform/macos/app/Info.plist
@@ -0,0 +1,56 @@
+<?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>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>mbx</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>Mapbox GL Map</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>NSDocumentClass</key>
+ <string>MapDocument</string>
+ </dict>
+ </array>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</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.1.0</string>
+ <key>CFBundleSignature</key>
+ <string>MBGL</string>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>mapboxgl</string>
+ </array>
+ </dict>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/platform/macos/app/LocationCoordinate2DTransformer.h b/platform/macos/app/LocationCoordinate2DTransformer.h
new file mode 100644
index 0000000000..162325fbad
--- /dev/null
+++ b/platform/macos/app/LocationCoordinate2DTransformer.h
@@ -0,0 +1,5 @@
+#import <Foundation/Foundation.h>
+
+@interface LocationCoordinate2DTransformer : NSValueTransformer
+
+@end
diff --git a/platform/macos/app/LocationCoordinate2DTransformer.m b/platform/macos/app/LocationCoordinate2DTransformer.m
new file mode 100644
index 0000000000..59654f1676
--- /dev/null
+++ b/platform/macos/app/LocationCoordinate2DTransformer.m
@@ -0,0 +1,31 @@
+#import "LocationCoordinate2DTransformer.h"
+
+#import <Mapbox/Mapbox.h>
+
+@implementation LocationCoordinate2DTransformer {
+ MGLCoordinateFormatter *_coordinateFormatter;
+}
+
++ (Class)transformedValueClass {
+ return [NSString class];
+}
+
++ (BOOL)allowsReverseTransformation {
+ return NO;
+}
+
+- (instancetype)init {
+ if (self = [super init]) {
+ _coordinateFormatter = [[MGLCoordinateFormatter alloc] init];
+ }
+ return self;
+}
+
+- (id)transformedValue:(id)value {
+ if (![value isKindOfClass:[NSValue class]]) {
+ return nil;
+ }
+ return [_coordinateFormatter stringForObjectValue:value].capitalizedString;
+}
+
+@end
diff --git a/platform/macos/app/MapDocument.h b/platform/macos/app/MapDocument.h
new file mode 100644
index 0000000000..86ad05e6e2
--- /dev/null
+++ b/platform/macos/app/MapDocument.h
@@ -0,0 +1,14 @@
+#import <Cocoa/Cocoa.h>
+
+@class MGLMapView;
+
+@interface MapDocument : NSDocument
+
+@property (weak) IBOutlet MGLMapView *mapView;
+
+- (IBAction)setStyle:(id)sender;
+- (IBAction)chooseCustomStyle:(id)sender;
+
+- (IBAction)reload:(id)sender;
+
+@end
diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m
new file mode 100644
index 0000000000..e9f3b99592
--- /dev/null
+++ b/platform/macos/app/MapDocument.m
@@ -0,0 +1,778 @@
+#import "MapDocument.h"
+
+#import "AppDelegate.h"
+#import "DroppedPinAnnotation.h"
+
+#import <Mapbox/Mapbox.h>
+
+static NSString * const MGLDroppedPinAnnotationImageIdentifier = @"dropped";
+
+static const CLLocationCoordinate2D WorldTourDestinations[] = {
+ { .latitude = 38.9131982, .longitude = -77.0325453144239 },
+ { .latitude = 37.7757368, .longitude = -122.4135302 },
+ { .latitude = 12.9810816, .longitude = 77.6368034 },
+ { .latitude = -13.15589555, .longitude = -74.2178961777998 },
+};
+
+NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotation>) *shapes) {
+ NSMutableArray *flattenedShapes = [NSMutableArray arrayWithCapacity:shapes.count];
+ for (id <MGLAnnotation> shape in shapes) {
+ NSArray *subshapes;
+ // Flatten multipoints but not polylines or polygons.
+ if ([shape isMemberOfClass:[MGLMultiPoint class]]) {
+ NSUInteger pointCount = [(MGLMultiPoint *)shape pointCount];
+ CLLocationCoordinate2D *coordinates = [(MGLMultiPoint *)shape coordinates];
+ NSMutableArray *pointAnnotations = [NSMutableArray arrayWithCapacity:pointCount];
+ for (NSUInteger i = 0; i < pointCount; i++) {
+ MGLPointAnnotation *pointAnnotation = [[MGLPointAnnotation alloc] init];
+ pointAnnotation.coordinate = coordinates[i];
+ [pointAnnotations addObject:pointAnnotation];
+ }
+ subshapes = pointAnnotations;
+ } else if ([shape isKindOfClass:[MGLMultiPolyline class]]) {
+ subshapes = [(MGLMultiPolyline *)shape polylines];
+ } else if ([shape isKindOfClass:[MGLMultiPolygon class]]) {
+ subshapes = [(MGLMultiPolygon *)shape polygons];
+ } else if ([shape isKindOfClass:[MGLShapeCollection class]]) {
+ subshapes = MBXFlattenedShapes([(MGLShapeCollection *)shape shapes]);
+ }
+
+ if (subshapes) {
+ [flattenedShapes addObjectsFromArray:subshapes];
+ } else {
+ [flattenedShapes addObject:shape];
+ }
+ }
+ return flattenedShapes;
+}
+
+@interface MapDocument () <NSWindowDelegate, NSSharingServicePickerDelegate, NSMenuDelegate, MGLMapViewDelegate>
+
+@property (weak) IBOutlet NSMenu *mapViewContextMenu;
+
+@end
+
+@implementation MapDocument {
+ /// Style URL inherited from an existing document at the time this document
+ /// was created.
+ NSURL *_inheritedStyleURL;
+
+ NSPoint _mouseLocationForMapViewContextMenu;
+ NSUInteger _droppedPinCounter;
+ NSNumberFormatter *_spellOutNumberFormatter;
+
+ BOOL _showsToolTipsOnDroppedPins;
+ BOOL _randomizesCursorsOnDroppedPins;
+ BOOL _isTouringWorld;
+ BOOL _isShowingPolygonAndPolylineAnnotations;
+}
+
+#pragma mark Lifecycle
+
+- (NSString *)windowNibName {
+ return @"MapDocument";
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)windowControllerWillLoadNib:(NSWindowController *)windowController {
+ NSDocument *currentDocument = [NSDocumentController sharedDocumentController].currentDocument;
+ if ([currentDocument isKindOfClass:[MapDocument class]]) {
+ _inheritedStyleURL = [(MapDocument *)currentDocument mapView].styleURL;
+ }
+}
+
+- (void)windowControllerDidLoadNib:(NSWindowController *)controller {
+ [super windowControllerDidLoadNib:controller];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(userDefaultsDidChange:)
+ name:NSUserDefaultsDidChangeNotification
+ object:nil];
+
+ _spellOutNumberFormatter = [[NSNumberFormatter alloc] init];
+
+ NSPressGestureRecognizer *pressGestureRecognizer = [[NSPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePressGesture:)];
+ [self.mapView addGestureRecognizer:pressGestureRecognizer];
+
+ [self applyPendingState];
+}
+
+- (NSWindow *)window {
+ return self.windowControllers.firstObject.window;
+}
+
+- (void)userDefaultsDidChange:(NSNotification *)notification {
+ NSUserDefaults *userDefaults = notification.object;
+ NSString *accessToken = [userDefaults stringForKey:MGLMapboxAccessTokenDefaultsKey];
+ if (![accessToken isEqualToString:[MGLAccountManager accessToken]]) {
+ [MGLAccountManager setAccessToken:accessToken];
+ [self reload:self];
+ }
+}
+
+#pragma mark NSWindowDelegate methods
+
+- (void)window:(NSWindow *)window willEncodeRestorableState:(NSCoder *)state {
+ [state encodeObject:self.mapView.styleURL forKey:@"MBXMapViewStyleURL"];
+}
+
+- (void)window:(NSWindow *)window didDecodeRestorableState:(NSCoder *)state {
+ self.mapView.styleURL = [state decodeObjectForKey:@"MBXMapViewStyleURL"];
+}
+
+#pragma mark Services
+
+- (IBAction)showShareMenu:(id)sender {
+ NSSharingServicePicker *picker = [[NSSharingServicePicker alloc] initWithItems:@[self.shareURL]];
+ picker.delegate = self;
+ [picker showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
+}
+
+- (NSURL *)shareURL {
+ NSArray *components = self.mapView.styleURL.pathComponents;
+ CLLocationCoordinate2D centerCoordinate = self.mapView.centerCoordinate;
+ return [NSURL URLWithString:
+ [NSString stringWithFormat:@"https://api.mapbox.com/styles/v1/%@/%@.html?access_token=%@#%.2f/%.5f/%.5f/%.f",
+ components[1], components[2], [MGLAccountManager accessToken],
+ self.mapView.zoomLevel, centerCoordinate.latitude, centerCoordinate.longitude, self.mapView.direction]];
+}
+
+#pragma mark View methods
+
+- (IBAction)setStyle:(id)sender {
+ NSInteger tag;
+ if ([sender isKindOfClass:[NSMenuItem class]]) {
+ tag = [sender tag];
+ } else if ([sender isKindOfClass:[NSPopUpButton class]]) {
+ tag = [sender selectedTag];
+ }
+ NSURL *styleURL;
+ switch (tag) {
+ case 1:
+ styleURL = [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion];
+ break;
+ case 2:
+ styleURL = [MGLStyle outdoorsStyleURLWithVersion:MGLStyleDefaultVersion];
+ break;
+ case 3:
+ styleURL = [MGLStyle lightStyleURLWithVersion:MGLStyleDefaultVersion];
+ break;
+ case 4:
+ styleURL = [MGLStyle darkStyleURLWithVersion:MGLStyleDefaultVersion];
+ break;
+ case 5:
+ styleURL = [MGLStyle satelliteStyleURLWithVersion:MGLStyleDefaultVersion];
+ break;
+ case 6:
+ styleURL = [MGLStyle satelliteStreetsStyleURLWithVersion:MGLStyleDefaultVersion];
+ break;
+ default:
+ NSAssert(NO, @"Cannot set style from control with tag %li", (long)tag);
+ break;
+ }
+ self.mapView.styleURL = styleURL;
+ [self.window.toolbar validateVisibleItems];
+}
+
+- (IBAction)chooseCustomStyle:(id)sender {
+ NSAlert *alert = [[NSAlert alloc] init];
+ alert.messageText = @"Apply custom style";
+ alert.informativeText = @"Enter the URL to a JSON file that conforms to the Mapbox GL style specification, such as a style designed in Mapbox Studio:";
+ NSTextField *textField = [[NSTextField alloc] initWithFrame:NSZeroRect];
+ [textField sizeToFit];
+ NSRect textFieldFrame = textField.frame;
+ textFieldFrame.size.width = 300;
+ textField.frame = textFieldFrame;
+ NSURL *savedURL = [[NSUserDefaults standardUserDefaults] URLForKey:@"MBXCustomStyleURL"];
+ if (savedURL) {
+ textField.stringValue = savedURL.absoluteString;
+ }
+ alert.accessoryView = textField;
+ [alert addButtonWithTitle:@"Apply"];
+ [alert addButtonWithTitle:@"Cancel"];
+ if ([alert runModal] == NSAlertFirstButtonReturn) {
+ self.mapView.styleURL = [NSURL URLWithString:textField.stringValue];
+ [[NSUserDefaults standardUserDefaults] setURL:self.mapView.styleURL forKey:@"MBXCustomStyleURL"];
+ [self.window.toolbar validateVisibleItems];
+ }
+}
+
+- (IBAction)zoomIn:(id)sender {
+ [self.mapView setZoomLevel:self.mapView.zoomLevel + 1 animated:YES];
+}
+
+- (IBAction)zoomOut:(id)sender {
+ [self.mapView setZoomLevel:self.mapView.zoomLevel - 1 animated:YES];
+}
+
+- (IBAction)snapToNorth:(id)sender {
+ [self.mapView setDirection:0 animated:YES];
+}
+
+- (IBAction)reload:(id)sender {
+ [self.mapView reloadStyle:sender];
+}
+
+- (void)applyPendingState {
+ if (_inheritedStyleURL) {
+ self.mapView.styleURL = _inheritedStyleURL;
+ _inheritedStyleURL = nil;
+ }
+
+ AppDelegate *appDelegate = (AppDelegate *)NSApp.delegate;
+ if (appDelegate.pendingStyleURL) {
+ self.mapView.styleURL = appDelegate.pendingStyleURL;
+ }
+ if (appDelegate.pendingCamera) {
+ if (appDelegate.pendingZoomLevel >= 0) {
+ self.mapView.zoomLevel = appDelegate.pendingZoomLevel;
+ appDelegate.pendingCamera.altitude = self.mapView.camera.altitude;
+ }
+ self.mapView.camera = appDelegate.pendingCamera;
+ appDelegate.pendingZoomLevel = -1;
+ appDelegate.pendingCamera = nil;
+ }
+ if (!MGLCoordinateBoundsIsEmpty(appDelegate.pendingVisibleCoordinateBounds)) {
+ self.mapView.visibleCoordinateBounds = appDelegate.pendingVisibleCoordinateBounds;
+ appDelegate.pendingVisibleCoordinateBounds = (MGLCoordinateBounds){ { 0, 0 }, { 0, 0 } };
+ }
+ if (appDelegate.pendingDebugMask) {
+ self.mapView.debugMask = appDelegate.pendingDebugMask;
+ }
+ if (appDelegate.pendingMinimumZoomLevel >= 0) {
+ self.mapView.zoomLevel = MAX(appDelegate.pendingMinimumZoomLevel, self.mapView.zoomLevel);
+ appDelegate.pendingMaximumZoomLevel = -1;
+ }
+ if (appDelegate.pendingMaximumZoomLevel >= 0) {
+ self.mapView.zoomLevel = MIN(appDelegate.pendingMaximumZoomLevel, self.mapView.zoomLevel);
+ appDelegate.pendingMaximumZoomLevel = -1;
+ }
+
+ // Temporarily set the display name to the default center coordinate instead
+ // of “Untitled” until the binding kicks in.
+ NSValue *coordinateValue = [NSValue valueWithMGLCoordinate:self.mapView.centerCoordinate];
+ self.displayName = [[NSValueTransformer valueTransformerForName:@"LocationCoordinate2DTransformer"]
+ transformedValue:coordinateValue];
+}
+
+#pragma mark Debug methods
+
+- (IBAction)toggleTileBoundaries:(id)sender {
+ self.mapView.debugMask ^= MGLMapDebugTileBoundariesMask;
+}
+
+- (IBAction)toggleTileInfo:(id)sender {
+ self.mapView.debugMask ^= MGLMapDebugTileInfoMask;
+}
+
+- (IBAction)toggleTileTimestamps:(id)sender {
+ self.mapView.debugMask ^= MGLMapDebugTimestampsMask;
+}
+
+- (IBAction)toggleCollisionBoxes:(id)sender {
+ self.mapView.debugMask ^= MGLMapDebugCollisionBoxesMask;
+}
+
+- (IBAction)toggleWireframes:(id)sender {
+ self.mapView.debugMask ^= MGLMapDebugWireframesMask;
+}
+
+- (IBAction)showColorBuffer:(id)sender {
+ self.mapView.debugMask &= ~MGLMapDebugStencilBufferMask;
+}
+
+- (IBAction)showStencilBuffer:(id)sender {
+ self.mapView.debugMask |= MGLMapDebugStencilBufferMask;
+}
+
+- (IBAction)toggleShowsToolTipsOnDroppedPins:(id)sender {
+ _showsToolTipsOnDroppedPins = !_showsToolTipsOnDroppedPins;
+}
+
+- (IBAction)toggleRandomizesCursorsOnDroppedPins:(id)sender {
+ _randomizesCursorsOnDroppedPins = !_randomizesCursorsOnDroppedPins;
+}
+
+- (IBAction)dropManyPins:(id)sender {
+ [self removeAllAnnotations:sender];
+
+ NSRect bounds = self.mapView.bounds;
+ NSMutableArray *annotations = [NSMutableArray array];
+ for (CGFloat x = NSMinX(bounds); x < NSMaxX(bounds); x += arc4random_uniform(50)) {
+ for (CGFloat y = NSMaxY(bounds); y >= NSMinY(bounds); y -= arc4random_uniform(100)) {
+ [annotations addObject:[self pinAtPoint:NSMakePoint(x, y)]];
+ }
+ }
+
+ [NSTimer scheduledTimerWithTimeInterval:1/60
+ target:self
+ selector:@selector(dropOneOfManyPins:)
+ userInfo:annotations
+ repeats:YES];
+}
+
+- (void)dropOneOfManyPins:(NSTimer *)timer {
+ NSMutableArray *annotations = timer.userInfo;
+ NSUInteger numberOfAnnotationsToAdd = 50;
+ if (annotations.count < numberOfAnnotationsToAdd) {
+ numberOfAnnotationsToAdd = annotations.count;
+ }
+ NSArray *annotationsToAdd = [annotations subarrayWithRange:
+ NSMakeRange(0, numberOfAnnotationsToAdd)];
+ [self.mapView addAnnotations:annotationsToAdd];
+ [annotations removeObjectsInRange:NSMakeRange(0, numberOfAnnotationsToAdd)];
+ if (!annotations.count) {
+ [timer invalidate];
+ }
+}
+
+- (IBAction)removeAllAnnotations:(id)sender {
+ [self.mapView removeAnnotations:self.mapView.annotations];
+ _isShowingPolygonAndPolylineAnnotations = NO;
+}
+
+- (IBAction)startWorldTour:(id)sender {
+ _isTouringWorld = YES;
+
+ [self removeAllAnnotations:sender];
+ NSUInteger numberOfAnnotations = sizeof(WorldTourDestinations) / sizeof(WorldTourDestinations[0]);
+ NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:numberOfAnnotations];
+ for (NSUInteger i = 0; i < numberOfAnnotations; i++) {
+ MGLPointAnnotation *annotation = [[MGLPointAnnotation alloc] init];
+ annotation.coordinate = WorldTourDestinations[i];
+ [annotations addObject:annotation];
+ }
+ [self.mapView addAnnotations:annotations];
+ [self continueWorldTourWithRemainingAnnotations:annotations];
+}
+
+- (void)continueWorldTourWithRemainingAnnotations:(NS_MUTABLE_ARRAY_OF(MGLPointAnnotation *) *)annotations {
+ MGLPointAnnotation *nextAnnotation = annotations.firstObject;
+ if (!nextAnnotation || !_isTouringWorld) {
+ _isTouringWorld = NO;
+ return;
+ }
+
+ [annotations removeObjectAtIndex:0];
+ MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:nextAnnotation.coordinate
+ fromDistance:0
+ pitch:arc4random_uniform(60)
+ heading:arc4random_uniform(360)];
+ __weak MapDocument *weakSelf = self;
+ [self.mapView flyToCamera:camera completionHandler:^{
+ MapDocument *strongSelf = weakSelf;
+ [strongSelf performSelector:@selector(continueWorldTourWithRemainingAnnotations:)
+ withObject:annotations
+ afterDelay:2];
+ }];
+}
+
+- (IBAction)stopWorldTour:(id)sender {
+ _isTouringWorld = NO;
+ // Any programmatic viewpoint change cancels outstanding animations.
+ self.mapView.camera = self.mapView.camera;
+}
+
+- (IBAction)drawPolygonAndPolyLineAnnotations:(id)sender {
+
+ if (_isShowingPolygonAndPolylineAnnotations) {
+ [self removeAllAnnotations:sender];
+ return;
+ }
+
+ _isShowingPolygonAndPolylineAnnotations = YES;
+
+ // Pacific Northwest triangle
+ CLLocationCoordinate2D triangleCoordinates[3] = {
+ CLLocationCoordinate2DMake(44, -122),
+ CLLocationCoordinate2DMake(46, -122),
+ CLLocationCoordinate2DMake(46, -121)
+ };
+ MGLPolygon *triangle = [MGLPolygon polygonWithCoordinates:triangleCoordinates count:3];
+ [self.mapView addAnnotation:triangle];
+
+ // West coast line
+ CLLocationCoordinate2D lineCoordinates[4] = {
+ CLLocationCoordinate2DMake(47.6025, -122.3327),
+ CLLocationCoordinate2DMake(45.5189, -122.6726),
+ CLLocationCoordinate2DMake(37.7790, -122.4177),
+ CLLocationCoordinate2DMake(34.0532, -118.2349)
+ };
+ MGLPolyline *line = [MGLPolyline polylineWithCoordinates:lineCoordinates count:4];
+ [self.mapView addAnnotation:line];
+}
+
+#pragma mark Offline packs
+
+- (IBAction)addOfflinePack:(id)sender {
+ NSAlert *namePrompt = [[NSAlert alloc] init];
+ namePrompt.messageText = @"Add offline pack";
+ namePrompt.informativeText = @"Choose a name for the pack:";
+ NSTextField *nameTextField = [[NSTextField alloc] initWithFrame:NSZeroRect];
+ nameTextField.placeholderString = MGLStringFromCoordinateBounds(self.mapView.visibleCoordinateBounds);
+ [nameTextField sizeToFit];
+ NSRect textFieldFrame = nameTextField.frame;
+ textFieldFrame.size.width = 300;
+ nameTextField.frame = textFieldFrame;
+ namePrompt.accessoryView = nameTextField;
+ [namePrompt addButtonWithTitle:@"Add"];
+ [namePrompt addButtonWithTitle:@"Cancel"];
+ if ([namePrompt runModal] != NSAlertFirstButtonReturn) {
+ return;
+ }
+
+ id <MGLOfflineRegion> region = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:self.mapView.styleURL bounds:self.mapView.visibleCoordinateBounds fromZoomLevel:self.mapView.zoomLevel toZoomLevel:self.mapView.maximumZoomLevel];
+ NSData *context = [[NSValueTransformer valueTransformerForName:@"OfflinePackNameValueTransformer"] reverseTransformedValue:nameTextField.stringValue];
+ [[MGLOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack * _Nullable pack, NSError * _Nullable error) {
+ if (error) {
+ [[NSAlert alertWithError:error] runModal];
+ } else {
+ [pack resume];
+ }
+ }];
+}
+
+#pragma mark Help methods
+
+- (IBAction)giveFeedback:(id)sender {
+ CLLocationCoordinate2D centerCoordinate = self.mapView.centerCoordinate;
+ NSURL *feedbackURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://www.mapbox.com/map-feedback/#/%.5f/%.5f/%.0f",
+ centerCoordinate.longitude, centerCoordinate.latitude, round(self.mapView.zoomLevel + 1)]];
+ [[NSWorkspace sharedWorkspace] openURL:feedbackURL];
+}
+
+#pragma mark Mouse events
+
+- (void)handlePressGesture:(NSPressGestureRecognizer *)gestureRecognizer {
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ NSPoint location = [gestureRecognizer locationInView:self.mapView];
+ if (!NSPointInRect([gestureRecognizer locationInView:self.mapView.compass], self.mapView.compass.bounds)
+ && !NSPointInRect([gestureRecognizer locationInView:self.mapView.zoomControls], self.mapView.zoomControls.bounds)
+ && !NSPointInRect([gestureRecognizer locationInView:self.mapView.attributionView], self.mapView.attributionView.bounds)) {
+ [self dropPinAtPoint:location];
+ }
+ }
+}
+
+- (IBAction)dropPin:(NSMenuItem *)sender {
+ [self dropPinAtPoint:_mouseLocationForMapViewContextMenu];
+}
+
+- (void)dropPinAtPoint:(NSPoint)point {
+ DroppedPinAnnotation *annotation = [self pinAtPoint:point];
+ [self.mapView addAnnotation:annotation];
+ [self.mapView selectAnnotation:annotation];
+}
+
+- (DroppedPinAnnotation *)pinAtPoint:(NSPoint)point {
+ NSArray *features = [self.mapView visibleFeaturesAtPoint:point];
+ NSString *title;
+ for (id <MGLFeature> feature in features) {
+ if (!title) {
+ title = [feature attributeForKey:@"name_en"] ?: [feature attributeForKey:@"name"];
+ }
+ }
+
+ DroppedPinAnnotation *annotation = [[DroppedPinAnnotation alloc] init];
+ annotation.coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
+ annotation.title = title ?: @"Dropped Pin";
+ _spellOutNumberFormatter.numberStyle = NSNumberFormatterSpellOutStyle;
+ if (_showsToolTipsOnDroppedPins) {
+ NSString *formattedNumber = [_spellOutNumberFormatter stringFromNumber:@(++_droppedPinCounter)];
+ annotation.toolTip = formattedNumber;
+ }
+ return annotation;
+}
+
+- (IBAction)removePin:(NSMenuItem *)sender {
+ [self removePinAtPoint:_mouseLocationForMapViewContextMenu];
+}
+
+- (void)removePinAtPoint:(NSPoint)point {
+ [self.mapView removeAnnotation:[self.mapView annotationAtPoint:point]];
+}
+
+- (IBAction)selectFeatures:(id)sender {
+ [self selectFeaturesAtPoint:_mouseLocationForMapViewContextMenu];
+}
+
+- (void)selectFeaturesAtPoint:(NSPoint)point {
+ NSArray *features = [self.mapView visibleFeaturesAtPoint:point];
+ NSArray *flattenedFeatures = MBXFlattenedShapes(features);
+ [self.mapView addAnnotations:flattenedFeatures];
+}
+
+#pragma mark User interface validation
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
+ if (menuItem.action == @selector(setStyle:)) {
+ NSURL *styleURL = self.mapView.styleURL;
+ NSCellStateValue state;
+ switch (menuItem.tag) {
+ case 1:
+ state = [styleURL isEqual:[MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion]];
+ break;
+ case 2:
+ state = [styleURL isEqual:[MGLStyle outdoorsStyleURLWithVersion:MGLStyleDefaultVersion]];
+ break;
+ case 3:
+ state = [styleURL isEqual:[MGLStyle lightStyleURLWithVersion:MGLStyleDefaultVersion]];
+ break;
+ case 4:
+ state = [styleURL isEqual:[MGLStyle darkStyleURLWithVersion:MGLStyleDefaultVersion]];
+ break;
+ case 5:
+ state = [styleURL isEqual:[MGLStyle satelliteStyleURLWithVersion:MGLStyleDefaultVersion]];
+ break;
+ case 6:
+ state = [styleURL isEqual:[MGLStyle satelliteStreetsStyleURLWithVersion:MGLStyleDefaultVersion]];
+ break;
+ default:
+ return NO;
+ }
+ menuItem.state = state;
+ return YES;
+ }
+ if (menuItem.action == @selector(chooseCustomStyle:)) {
+ menuItem.state = self.indexOfStyleInToolbarItem == NSNotFound;
+ return YES;
+ }
+ if (menuItem.action == @selector(zoomIn:)) {
+ return self.mapView.zoomLevel < self.mapView.maximumZoomLevel;
+ }
+ if (menuItem.action == @selector(zoomOut:)) {
+ return self.mapView.zoomLevel > self.mapView.minimumZoomLevel;
+ }
+ if (menuItem.action == @selector(snapToNorth:)) {
+ return self.mapView.direction != 0;
+ }
+ if (menuItem.action == @selector(reload:)) {
+ return YES;
+ }
+ if (menuItem.action == @selector(dropPin:)) {
+ id <MGLAnnotation> annotationUnderCursor = [self.mapView annotationAtPoint:_mouseLocationForMapViewContextMenu];
+ menuItem.hidden = annotationUnderCursor != nil;
+ return YES;
+ }
+ if (menuItem.action == @selector(removePin:)) {
+ id <MGLAnnotation> annotationUnderCursor = [self.mapView annotationAtPoint:_mouseLocationForMapViewContextMenu];
+ menuItem.hidden = annotationUnderCursor == nil;
+ return YES;
+ }
+ if (menuItem.action == @selector(selectFeatures:)) {
+ return YES;
+ }
+ if (menuItem.action == @selector(toggleTileBoundaries:)) {
+ BOOL isShown = self.mapView.debugMask & MGLMapDebugTileBoundariesMask;
+ menuItem.title = isShown ? @"Hide Tile Boundaries" : @"Show Tile Boundaries";
+ return YES;
+ }
+ if (menuItem.action == @selector(toggleTileInfo:)) {
+ BOOL isShown = self.mapView.debugMask & MGLMapDebugTileInfoMask;
+ menuItem.title = isShown ? @"Hide Tile Info" : @"Show Tile Info";
+ return YES;
+ }
+ if (menuItem.action == @selector(toggleTileTimestamps:)) {
+ BOOL isShown = self.mapView.debugMask & MGLMapDebugTimestampsMask;
+ menuItem.title = isShown ? @"Hide Tile Timestamps" : @"Show Tile Timestamps";
+ return YES;
+ }
+ if (menuItem.action == @selector(toggleCollisionBoxes:)) {
+ BOOL isShown = self.mapView.debugMask & MGLMapDebugCollisionBoxesMask;
+ menuItem.title = isShown ? @"Hide Collision Boxes" : @"Show Collision Boxes";
+ return YES;
+ }
+ if (menuItem.action == @selector(toggleWireframes:)) {
+ BOOL isShown = self.mapView.debugMask & MGLMapDebugWireframesMask;
+ menuItem.title = isShown ? @"Hide Wireframes" : @"Show Wireframes";
+ return YES;
+ }
+ if (menuItem.action == @selector(showColorBuffer:)) {
+ BOOL enabled = self.mapView.debugMask & MGLMapDebugStencilBufferMask;
+ menuItem.state = enabled ? NSOffState : NSOnState;
+ return YES;
+ }
+ if (menuItem.action == @selector(showStencilBuffer:)) {
+ BOOL enabled = self.mapView.debugMask & MGLMapDebugStencilBufferMask;
+ menuItem.state = enabled ? NSOnState : NSOffState;
+ return YES;
+ }
+ if (menuItem.action == @selector(toggleShowsToolTipsOnDroppedPins:)) {
+ BOOL isShown = _showsToolTipsOnDroppedPins;
+ menuItem.title = isShown ? @"Hide Tooltips on Dropped Pins" : @"Show Tooltips on Dropped Pins";
+ return YES;
+ }
+ if (menuItem.action == @selector(toggleRandomizesCursorsOnDroppedPins:)) {
+ BOOL isRandom = _randomizesCursorsOnDroppedPins;
+ menuItem.title = isRandom ? @"Use Default Cursor for Dropped Pins" : @"Use Random Cursors for Dropped Pins";
+ return _showsToolTipsOnDroppedPins;
+ }
+ if (menuItem.action == @selector(dropManyPins:)) {
+ return YES;
+ }
+ if (menuItem.action == @selector(removeAllAnnotations:)) {
+ return self.mapView.annotations.count > 0;
+ }
+ if (menuItem.action == @selector(startWorldTour:)) {
+ return !_isTouringWorld;
+ }
+ if (menuItem.action == @selector(stopWorldTour:)) {
+ return _isTouringWorld;
+ }
+ if (menuItem.action == @selector(drawPolygonAndPolyLineAnnotations:)) {
+ return !_isShowingPolygonAndPolylineAnnotations;
+ }
+ if (menuItem.action == @selector(addOfflinePack:)) {
+ NSURL *styleURL = self.mapView.styleURL;
+ return !styleURL.isFileURL;
+ }
+ if (menuItem.action == @selector(giveFeedback:)) {
+ return YES;
+ }
+ return NO;
+}
+
+- (NSUInteger)indexOfStyleInToolbarItem {
+ if (![MGLAccountManager accessToken]) {
+ return NSNotFound;
+ }
+
+ NSArray *styleURLs = @[
+ [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion],
+ [MGLStyle outdoorsStyleURLWithVersion:MGLStyleDefaultVersion],
+ [MGLStyle lightStyleURLWithVersion:MGLStyleDefaultVersion],
+ [MGLStyle darkStyleURLWithVersion:MGLStyleDefaultVersion],
+ [MGLStyle satelliteStyleURLWithVersion:MGLStyleDefaultVersion],
+ [MGLStyle satelliteStreetsStyleURLWithVersion:MGLStyleDefaultVersion],
+ ];
+ return [styleURLs indexOfObject:self.mapView.styleURL];
+}
+
+- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem {
+ if (!self.mapView) {
+ return NO;
+ }
+
+ if (toolbarItem.action == @selector(showShareMenu:)) {
+ [(NSButton *)toolbarItem.view sendActionOn:NSLeftMouseDownMask];
+ if (![MGLAccountManager accessToken]) {
+ return NO;
+ }
+ NSURL *styleURL = self.mapView.styleURL;
+ return ([styleURL.scheme isEqualToString:@"mapbox"]
+ && [styleURL.pathComponents.firstObject isEqualToString:@"styles"]);
+ }
+ if (toolbarItem.action == @selector(setStyle:)) {
+ NSPopUpButton *popUpButton = (NSPopUpButton *)toolbarItem.view;
+ NSUInteger index = self.indexOfStyleInToolbarItem;
+ if (index == NSNotFound) {
+ [popUpButton addItemWithTitle:@"Custom"];
+ index = [popUpButton numberOfItems] - 1;
+ }
+ [popUpButton selectItemAtIndex:index];
+ }
+ return NO;
+}
+
+#pragma mark NSSharingServicePickerDelegate methods
+
+- (NS_ARRAY_OF(NSSharingService *) *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NS_ARRAY_OF(NSSharingService *) *)proposedServices {
+ NSURL *shareURL = self.shareURL;
+ NSURL *browserURL = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:shareURL];
+ NSImage *browserIcon = [[NSWorkspace sharedWorkspace] iconForFile:browserURL.path];
+ NSString *browserName = [[NSFileManager defaultManager] displayNameAtPath:browserURL.path];
+ NSString *browserServiceName = [NSString stringWithFormat:@"Open in %@", browserName];
+
+ NSSharingService *browserService = [[NSSharingService alloc] initWithTitle:browserServiceName
+ image:browserIcon
+ alternateImage:nil
+ handler:^{
+ [[NSWorkspace sharedWorkspace] openURL:self.shareURL];
+ }];
+
+ NSMutableArray *sharingServices = [proposedServices mutableCopy];
+ [sharingServices insertObject:browserService atIndex:0];
+ return sharingServices;
+}
+
+#pragma mark NSMenuDelegate methods
+
+- (void)menuWillOpen:(NSMenu *)menu {
+ if (menu == self.mapViewContextMenu) {
+ _mouseLocationForMapViewContextMenu = [self.window.contentView convertPoint:self.window.mouseLocationOutsideOfEventStream
+ toView:self.mapView];
+ }
+}
+
+#pragma mark MGLMapViewDelegate methods
+
+- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation {
+ return YES;
+}
+
+- (MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation {
+ MGLAnnotationImage *annotationImage = [self.mapView dequeueReusableAnnotationImageWithIdentifier:MGLDroppedPinAnnotationImageIdentifier];
+ if (!annotationImage) {
+ NSString *imagePath = [[NSBundle bundleForClass:[MGLMapView class]]
+ pathForResource:@"default_marker" ofType:@"pdf"];
+ NSImage *image = [[NSImage alloc] initWithContentsOfFile:imagePath];
+ NSRect alignmentRect = image.alignmentRect;
+ alignmentRect.origin.y = NSMidY(alignmentRect);
+ alignmentRect.size.height /= 2;
+ image.alignmentRect = alignmentRect;
+ annotationImage = [MGLAnnotationImage annotationImageWithImage:image
+ reuseIdentifier:MGLDroppedPinAnnotationImageIdentifier];
+ }
+ if (_randomizesCursorsOnDroppedPins) {
+ NSArray *cursors = @[
+ [NSCursor IBeamCursor],
+ [NSCursor crosshairCursor],
+ [NSCursor pointingHandCursor],
+ [NSCursor disappearingItemCursor],
+ [NSCursor IBeamCursorForVerticalLayout],
+ [NSCursor operationNotAllowedCursor],
+ [NSCursor dragLinkCursor],
+ [NSCursor dragCopyCursor],
+ [NSCursor contextualMenuCursor],
+ ];
+ annotationImage.cursor = cursors[arc4random_uniform((uint32_t)cursors.count) % cursors.count];
+ } else {
+ annotationImage.cursor = nil;
+ }
+ return annotationImage;
+}
+
+- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id <MGLAnnotation>)annotation {
+ if ([annotation isKindOfClass:[DroppedPinAnnotation class]]) {
+ DroppedPinAnnotation *droppedPin = annotation;
+ [droppedPin resume];
+ }
+}
+
+- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id <MGLAnnotation>)annotation {
+ if ([annotation isKindOfClass:[DroppedPinAnnotation class]]) {
+ DroppedPinAnnotation *droppedPin = annotation;
+ [droppedPin pause];
+ }
+}
+
+- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation {
+ return 0.8;
+}
+
+@end
+
+@interface ValidatedToolbarItem : NSToolbarItem
+
+@end
+
+@implementation ValidatedToolbarItem
+
+- (void)validate {
+ [(MapDocument *)self.toolbar.delegate validateToolbarItem:self];
+}
+
+@end
diff --git a/platform/macos/app/OfflinePackNameValueTransformer.h b/platform/macos/app/OfflinePackNameValueTransformer.h
new file mode 100644
index 0000000000..11fe3ff441
--- /dev/null
+++ b/platform/macos/app/OfflinePackNameValueTransformer.h
@@ -0,0 +1,5 @@
+#import <Foundation/Foundation.h>
+
+@interface OfflinePackNameValueTransformer : NSValueTransformer
+
+@end
diff --git a/platform/macos/app/OfflinePackNameValueTransformer.m b/platform/macos/app/OfflinePackNameValueTransformer.m
new file mode 100644
index 0000000000..2825e48ed3
--- /dev/null
+++ b/platform/macos/app/OfflinePackNameValueTransformer.m
@@ -0,0 +1,33 @@
+#import "OfflinePackNameValueTransformer.h"
+
+static NSString * const MBXOfflinePackContextNameKey = @"Name";
+
+@implementation OfflinePackNameValueTransformer
+
++ (Class)transformedValueClass {
+ return [NSString class];
+}
+
++ (BOOL)allowsReverseTransformation {
+ return YES;
+}
+
+- (NSString *)transformedValue:(NSData *)context {
+ NSAssert([context isKindOfClass:[NSData class]], @"Context should be NSData.");
+
+ NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:context];
+ NSAssert([userInfo isKindOfClass:[NSDictionary class]], @"Context of offline pack isn’t a dictionary.");
+ NSString *name = userInfo[MBXOfflinePackContextNameKey];
+ NSAssert([name isKindOfClass:[NSString class]], @"Name of offline pack isn’t a string.");
+ return name;
+}
+
+- (NSData *)reverseTransformedValue:(NSString *)name {
+ NSAssert([name isKindOfClass:[NSString class]], @"Name should be a string.");
+
+ return [NSKeyedArchiver archivedDataWithRootObject:@{
+ MBXOfflinePackContextNameKey: name,
+ }];
+}
+
+@end
diff --git a/platform/macos/app/TimeIntervalTransformer.h b/platform/macos/app/TimeIntervalTransformer.h
new file mode 100644
index 0000000000..ca88ad2cd1
--- /dev/null
+++ b/platform/macos/app/TimeIntervalTransformer.h
@@ -0,0 +1,5 @@
+#import <Foundation/Foundation.h>
+
+@interface TimeIntervalTransformer : NSValueTransformer
+
+@end
diff --git a/platform/macos/app/TimeIntervalTransformer.m b/platform/macos/app/TimeIntervalTransformer.m
new file mode 100644
index 0000000000..39177dc5bc
--- /dev/null
+++ b/platform/macos/app/TimeIntervalTransformer.m
@@ -0,0 +1,53 @@
+#import "TimeIntervalTransformer.h"
+
+@implementation TimeIntervalTransformer
+
++ (Class)transformedValueClass {
+ return [NSString class];
+}
+
++ (BOOL)allowsReverseTransformation {
+ return NO;
+}
+
+NSString *NumberAndUnitString(NSInteger quantity, NSString *singular, NSString *plural) {
+ return [NSString stringWithFormat:@"%ld %@", quantity, quantity == 1 ? singular : plural];
+}
+
+- (id)transformedValue:(id)value {
+ if (![value isKindOfClass:[NSValue class]]) {
+ return nil;
+ }
+
+ NSTimeInterval timeInterval = [value doubleValue];
+ NSInteger seconds = floor(timeInterval);
+ NSInteger minutes = floor(seconds / 60);
+ seconds -= minutes * 60;
+ NSInteger hours = floor(minutes / 60);
+ minutes -= hours * 60;
+ NSInteger days = floor(hours / 24);
+ hours -= days * 24;
+ NSInteger weeks = floor(days) / 7;
+ days -= weeks * 7;
+
+ NSMutableArray *components = [NSMutableArray array];
+ if (seconds || timeInterval < 60) {
+ [components addObject:NumberAndUnitString(seconds, @"second", @"seconds")];
+ }
+ if (minutes) {
+ [components insertObject:NumberAndUnitString(minutes, @"minute", @"minutes") atIndex:0];
+ }
+ if (hours) {
+ [components insertObject:NumberAndUnitString(hours, @"hour", @"hours") atIndex:0];
+ }
+ if (days) {
+ [components insertObject:NumberAndUnitString(days, @"day", @"days") atIndex:0];
+ }
+ if (weeks) {
+ [components insertObject:NumberAndUnitString(weeks, @"week", @"weeks") atIndex:0];
+ }
+
+ return [components componentsJoinedByString:@", "];
+}
+
+@end
diff --git a/platform/macos/app/main.m b/platform/macos/app/main.m
new file mode 100644
index 0000000000..8a6799b414
--- /dev/null
+++ b/platform/macos/app/main.m
@@ -0,0 +1,5 @@
+#import <Cocoa/Cocoa.h>
+
+int main(int argc, const char * argv[]) {
+ return NSApplicationMain(argc, argv);
+}
diff --git a/platform/macos/bitrise.yml b/platform/macos/bitrise.yml
new file mode 100644
index 0000000000..ce622ef0f8
--- /dev/null
+++ b/platform/macos/bitrise.yml
@@ -0,0 +1,54 @@
+format_version: 1.1.0
+default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
+
+trigger_map:
+- pattern: "*"
+ is_pull_request_allowed: true
+ workflow: primary
+
+workflows:
+ primary:
+ steps:
+ - script:
+ title: Check for skipping CI
+ inputs:
+ - content: |-
+ #!/bin/bash
+ if [[ -n "$(echo $GIT_CLONE_COMMIT_MESSAGE_SUBJECT | sed -n '/\[skip ci\]/p')" ||
+ -n "$(echo $GIT_CLONE_COMMIT_MESSAGE_SUBJECT | sed -n '/\[ci skip\]/p')" ||
+ -n "$(echo $GIT_CLONE_COMMIT_MESSAGE_BODY | sed -n 's/\[skip ci\]/p')" ||
+ -n "$(echo $GIT_CLONE_COMMIT_MESSAGE_BODY | sed -n 's/\[ci skip\]/p')" ]]; then
+ envman add --key SKIPCI --value true
+ else
+ envman add --key SKIPCI --value false
+ fi
+ - script:
+ title: Run build script
+ run_if: '{{enveq "SKIPCI" "false"}}'
+ inputs:
+ - content: |-
+ #!/bin/bash
+ set -eu -o pipefail
+ gem install xcpretty --no-rdoc --no-ri
+ export BUILDTYPE=Debug
+ make macos
+ make test-macos
+ - is_debug: 'yes'
+ - slack:
+ title: Post to Slack
+ run_if: '{{enveq "SKIPCI" "false"}}'
+ inputs:
+ - webhook_url: "$SLACK_HOOK_URL"
+ - channel: "#gl-bots"
+ - from_username: 'Bitrise macOS'
+ - from_username_on_error: 'Bitrise macOS'
+ - message: '<${BITRISE_BUILD_URL}|Build #${BITRISE_BUILD_NUMBER}>
+ for <https://github.com/mapbox/mapbox-gl-native/compare/${BITRISE_GIT_BRANCH}|mapbox/mapbox-gl-native@${BITRISE_GIT_BRANCH}>
+ by ${GIT_CLONE_COMMIT_COMMITER_NAME}
+ passed'
+ - message_on_error: '<${BITRISE_BUILD_URL}|Build #${BITRISE_BUILD_NUMBER}>
+ for <https://github.com/mapbox/mapbox-gl-native/compare/${BITRISE_GIT_BRANCH}|mapbox/mapbox-gl-native@${BITRISE_GIT_BRANCH}>
+ by ${GIT_CLONE_COMMIT_COMMITER_NAME}
+ failed'
+ - icon_url: https://bitrise-public-content-production.s3.amazonaws.com/slack/bitrise-slack-icon-128.png
+ - icon_url_on_error: https://bitrise-public-content-production.s3.amazonaws.com/slack/bitrise-slack-error-icon-128.png
diff --git a/platform/macos/docs/doc-README.md b/platform/macos/docs/doc-README.md
new file mode 100644
index 0000000000..b037da19d4
--- /dev/null
+++ b/platform/macos/docs/doc-README.md
@@ -0,0 +1,9 @@
+# [Mapbox macOS SDK](https://github.com/mapbox/mapbox-gl-native/tree/master/platform/macos/)
+
+The Mapbox macOS SDK is an open-source framework for embedding interactive map views with scalable, customizable vector maps into Cocoa applications on macOS 10.10.0 and above using Objective-C, Swift, or Interface Builder. It takes stylesheets that conform to the [Mapbox GL Style Specification](https://www.mapbox.com/mapbox-gl-style-spec/), applies them to vector tiles that conform to the [Mapbox Vector Tile Specification](https://www.mapbox.com/developers/vector-tiles/), and renders them using OpenGL.
+
+<img alt="" src="https://raw.githubusercontent.com/mapbox/mapbox-gl-native/master/platform/macos/screenshot.png" width="645">
+
+For setup information, consult the README.md that comes with this documentation. The [Mapbox iOS SDK](https://www.mapbox.com/ios-sdk/)’s [API documentation](https://www.mapbox.com/ios-sdk/api/) and [online examples](https://www.mapbox.com/ios-sdk/examples/) apply to the Mapbox macOS SDK with few differences, mostly around unimplemented features like user location tracking. A [full changelog](https://github.com/mapbox/mapbox-gl-native/blob/master/platform/macos/CHANGELOG.md) is also available.
+
+Mapbox does not officially support the macOS SDK to the same extent as the iOS SDK; however, [bug reports and pull requests](https://github.com/mapbox/mapbox-gl-native/issues/) are certainly welcome.
diff --git a/platform/macos/docs/pod-README.md b/platform/macos/docs/pod-README.md
new file mode 100644
index 0000000000..70d98ecdb9
--- /dev/null
+++ b/platform/macos/docs/pod-README.md
@@ -0,0 +1,43 @@
+# [Mapbox macOS SDK](https://github.com/mapbox/mapbox-gl-native/tree/master/platform/macos/)
+
+The Mapbox macOS SDK is an open-source framework for embedding interactive map views with scalable, customizable vector maps into Cocoa applications on macOS 10.10.0 and above using Objective-C, Swift, or Interface Builder. It takes stylesheets that conform to the [Mapbox GL Style Specification](https://www.mapbox.com/mapbox-gl-style-spec/), applies them to vector tiles that conform to the [Mapbox Vector Tile Specification](https://www.mapbox.com/developers/vector-tiles/), and renders them using OpenGL.
+
+<img alt="" src="https://raw.githubusercontent.com/mapbox/mapbox-gl-native/master/platform/macos/screenshot.png" width="645">
+
+## Installation
+
+1. Open the project editor, select your application target, then go to the General tab. Drag Mapbox.framework from the `dynamic` folder into the “Embedded Binaries” section. (Don’t drag it into the “Linked Frameworks and Libraries” section; Xcode will add it there automatically.) In the sheet that appears, make sure “Copy items if needed” is checked, then click Finish.
+
+1. Mapbox vector tiles require a Mapbox account and API access token. In the project editor, select the application target, then go to the Info tab. Under the “Custom macOS Target Properties” section, set `MGLMapboxAccessToken` to your access token. You can obtain an access token from the [Mapbox account page](https://www.mapbox.com/studio/account/tokens/).
+
+## Usage
+
+In a storyboard or XIB, add a view to your view controller. (Drag Custom View from the Object library to the View Controller scene on the Interface Builder canvas.) In the Identity inspector, set the view’s custom class to `MGLMapView`. If you need to manipulate the map view programmatically:
+
+1. Switch to the Assistant Editor.
+1. Import the `Mapbox` module.
+1. Connect the map view to a new outlet in your view controller class. (Control-drag from the map view in Interface Builder to a valid location in your view controller implementation.) The resulting outlet declaration should look something like this:
+
+```objc
+// ViewController.m
+@import Mapbox;
+
+@interface ViewController : NSViewController
+
+@property (strong) IBOutlet MGLMapView *mapView;
+
+@end
+```
+
+```swift
+// ViewController.swift
+import Mapbox
+
+class ViewController: NSViewController {
+ @IBOutlet var mapView: MGLMapView!
+}
+```
+
+Full API documentation is included in this package, within the `documentation` folder. The [Mapbox iOS SDK](https://www.mapbox.com/ios-sdk/)’s [API documentation](https://www.mapbox.com/ios-sdk/api/) and [online examples](https://www.mapbox.com/ios-sdk/examples/) apply to the Mapbox macOS SDK with few differences, mostly around unimplemented features like user location tracking.
+
+Mapbox does not officially support the macOS SDK to the same extent as the iOS SDK; however, [bug reports and pull requests](https://github.com/mapbox/mapbox-gl-native/issues/) are certainly welcome.
diff --git a/platform/macos/jazzy.yml b/platform/macos/jazzy.yml
new file mode 100644
index 0000000000..9e160d050f
--- /dev/null
+++ b/platform/macos/jazzy.yml
@@ -0,0 +1,84 @@
+module: Mapbox
+author: Mapbox
+author_url: https://www.mapbox.com/
+github_url: https://github.com/mapbox/mapbox-gl-native
+copyright: '© 2014–2016 [Mapbox](https://www.mapbox.com/). See [license](https://github.com/mapbox/mapbox-gl-native/blob/master/LICENSE.md) for more details.'
+
+head: |
+ <link rel='shortcut icon' href='https://www.mapbox.com/img/favicon.ico' type='image/x-icon' />
+
+objc: Yes
+skip_undocumented: Yes
+hide_documentation_coverage: Yes
+umbrella_header: src/Mapbox.h
+framework_root: ../darwin/src
+
+custom_categories:
+ - name: Maps
+ children:
+ - MGLAccountManager
+ - MGLMapCamera
+ - MGLMapDebugMaskOptions
+ - MGLMapView
+ - MGLMapViewDelegate
+ - MGLStyle
+ - MGLStyleDefaultVersion
+ - MGLUserTrackingMode
+ - name: Annotations
+ children:
+ - MGLAnnotation
+ - MGLAnnotationImage
+ - MGLMultiPoint
+ - MGLMultiPolygon
+ - MGLMultiPolyline
+ - MGLPointAnnotation
+ - MGLPolygon
+ - MGLPolyline
+ - MGLOverlay
+ - MGLShape
+ - MGLShapeCollection
+ - name: Map Data
+ children:
+ - MGLFeature
+ - MGLMultiPointFeature
+ - MGLMultiPolygonFeature
+ - MGLMultiPolylineFeature
+ - MGLPointFeature
+ - MGLPolygonFeature
+ - MGLPolylineFeature
+ - MGLShapeCollectionFeature
+ - name: Offline Maps
+ children:
+ - MGLOfflineRegion
+ - MGLOfflineStorage
+ - MGLOfflinePack
+ - MGLOfflinePackAdditionCompletionHandler
+ - MGLOfflinePackErrorNotification
+ - MGLOfflinePackErrorUserInfoKey
+ - MGLOfflinePackMaximumCountUserInfoKey
+ - MGLOfflinePackMaximumMapboxTilesReachedNotification
+ - MGLOfflinePackProgress
+ - MGLOfflinePackProgressChangedNotification
+ - MGLOfflinePackProgressUserInfoKey
+ - MGLOfflinePackRemovalCompletionHandler
+ - MGLOfflinePackState
+ - MGLOfflinePackStateUserInfoKey
+ - MGLTilePyramidOfflineRegion
+ - name: Geometry
+ children:
+ - MGLClockDirectionFormatter
+ - MGLCompassDirectionFormatter
+ - MGLCoordinateBounds
+ - MGLCoordinateBoundsEqualToCoordinateBounds
+ - MGLCoordinateBoundsGetCoordinateSpan
+ - MGLCoordinateBoundsIsEmpty
+ - MGLCoordinateBoundsMake
+ - MGLCoordinateBoundsOffset
+ - MGLCoordinateFormatter
+ - MGLCoordinateSpan
+ - MGLCoordinateSpanEqualToCoordinateSpan
+ - MGLCoordinateSpanMake
+ - MGLCoordinateSpanZero
+ - MGLDegreesFromRadians
+ - MGLRadiansFromDegrees
+ - MGLStringFromCoordinateBounds
diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000..7271356f2a
--- /dev/null
+++ b/platform/macos/macos.xcodeproj/project.pbxproj
@@ -0,0 +1,1178 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 52BECB0A1CC5A26F009CD791 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52BECB091CC5A26F009CD791 /* SystemConfiguration.framework */; };
+ DA0CD58E1CF56F5800A5F5A5 /* MGLFeatureTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */; };
+ DA35A2A41CC9EB1A00E826B2 /* MGLCoordinateFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = DA35A2A31CC9EB1A00E826B2 /* MGLCoordinateFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DA35A2A61CC9EB2700E826B2 /* MGLCoordinateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2A51CC9EB2700E826B2 /* MGLCoordinateFormatter.m */; };
+ DA35A2A81CC9F41600E826B2 /* MGLCoordinateFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2A71CC9F41600E826B2 /* MGLCoordinateFormatterTests.m */; };
+ DA35A2AD1CCA091800E826B2 /* MGLCompassDirectionFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = DA35A2AB1CCA091800E826B2 /* MGLCompassDirectionFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DA35A2AE1CCA091800E826B2 /* MGLCompassDirectionFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2AC1CCA091800E826B2 /* MGLCompassDirectionFormatter.m */; };
+ DA35A2B61CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2B51CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m */; };
+ DA35A2BF1CCA9B1A00E826B2 /* MGLClockDirectionFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = DA35A2BD1CCA9B1A00E826B2 /* MGLClockDirectionFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DA35A2C01CCA9B1A00E826B2 /* MGLClockDirectionFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2BE1CCA9B1A00E826B2 /* MGLClockDirectionFormatter.m */; };
+ DA35A2C21CCA9F4A00E826B2 /* MGLClockDirectionFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2C11CCA9F4A00E826B2 /* MGLClockDirectionFormatterTests.m */; };
+ DA35A2CF1CCAAED300E826B2 /* NSValue+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DA35A2CD1CCAAED300E826B2 /* NSValue+MGLAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DA35A2D01CCAAED300E826B2 /* NSValue+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2CE1CCAAED300E826B2 /* NSValue+MGLAdditions.m */; };
+ DA839E971CC2E3400062CAFB /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA839E961CC2E3400062CAFB /* AppDelegate.m */; };
+ DA839E9A1CC2E3400062CAFB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DA839E991CC2E3400062CAFB /* main.m */; };
+ DA839E9D1CC2E3400062CAFB /* MapDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = DA839E9C1CC2E3400062CAFB /* MapDocument.m */; };
+ DA839EA01CC2E3400062CAFB /* MapDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA839E9E1CC2E3400062CAFB /* MapDocument.xib */; };
+ DA839EA21CC2E3400062CAFB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA839EA11CC2E3400062CAFB /* Assets.xcassets */; };
+ DA839EA51CC2E3400062CAFB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA839EA31CC2E3400062CAFB /* MainMenu.xib */; };
+ DA8933A51CCD287300E68420 /* MGLAnnotationCallout.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA8933A71CCD287300E68420 /* MGLAnnotationCallout.xib */; };
+ DA8933AE1CCD290700E68420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA8933AB1CCD290700E68420 /* Localizable.strings */; };
+ DA8933B51CCD2C2500E68420 /* Foundation.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA8933B31CCD2C2500E68420 /* Foundation.strings */; };
+ DA8933B81CCD2C2D00E68420 /* Foundation.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DA8933B61CCD2C2D00E68420 /* Foundation.stringsdict */; };
+ DAB6924A1CC75A31005AAB54 /* libmbgl-core.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAE6C3451CC31D1200DB3429 /* libmbgl-core.a */; };
+ DAC2ABC51CC6D343006D18C4 /* MGLAnnotationImage_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC2ABC41CC6D343006D18C4 /* MGLAnnotationImage_Private.h */; };
+ DACC22141CF3D3E200D220D9 /* MGLFeature.h in Headers */ = {isa = PBXBuildFile; fileRef = DACC22121CF3D3E200D220D9 /* MGLFeature.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DACC22151CF3D3E200D220D9 /* MGLFeature.mm in Sources */ = {isa = PBXBuildFile; fileRef = DACC22131CF3D3E200D220D9 /* MGLFeature.mm */; };
+ DACC22181CF3D4F700D220D9 /* MGLFeature_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DACC22171CF3D4F700D220D9 /* MGLFeature_Private.h */; };
+ DAD165741CF4CD7A001FF4B9 /* MGLShapeCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = DAD165721CF4CD7A001FF4B9 /* MGLShapeCollection.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAD165751CF4CD7A001FF4B9 /* MGLShapeCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD165731CF4CD7A001FF4B9 /* MGLShapeCollection.m */; };
+ DAE6C2E21CC304F900DB3429 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = DAE6C2E11CC304F900DB3429 /* Credits.rtf */; };
+ DAE6C2ED1CC3050F00DB3429 /* DroppedPinAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C2E41CC3050F00DB3429 /* DroppedPinAnnotation.m */; };
+ DAE6C2EE1CC3050F00DB3429 /* LocationCoordinate2DTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C2E61CC3050F00DB3429 /* LocationCoordinate2DTransformer.m */; };
+ DAE6C2F01CC3050F00DB3429 /* OfflinePackNameValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C2EA1CC3050F00DB3429 /* OfflinePackNameValueTransformer.m */; };
+ DAE6C2F11CC3050F00DB3429 /* TimeIntervalTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C2EC1CC3050F00DB3429 /* TimeIntervalTransformer.m */; };
+ DAE6C3321CC30DB200DB3429 /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAE6C3281CC30DB200DB3429 /* Mapbox.framework */; };
+ DAE6C33D1CC30DB200DB3429 /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAE6C3281CC30DB200DB3429 /* Mapbox.framework */; };
+ DAE6C33E1CC30DB200DB3429 /* Mapbox.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DAE6C3281CC30DB200DB3429 /* Mapbox.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ DAE6C3471CC31D1200DB3429 /* libmbgl-core.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAE6C3451CC31D1200DB3429 /* libmbgl-core.a */; };
+ DAE6C3481CC31D1200DB3429 /* libmbgl-platform-macos.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAE6C3461CC31D1200DB3429 /* libmbgl-platform-macos.a */; };
+ DAE6C35A1CC31E0400DB3429 /* MGLAccountManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C34A1CC31E0400DB3429 /* MGLAccountManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C35B1CC31E0400DB3429 /* MGLAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C34B1CC31E0400DB3429 /* MGLAnnotation.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C35C1CC31E0400DB3429 /* MGLGeometry.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C34C1CC31E0400DB3429 /* MGLGeometry.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C35D1CC31E0400DB3429 /* MGLMapCamera.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C34D1CC31E0400DB3429 /* MGLMapCamera.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C35E1CC31E0400DB3429 /* MGLMultiPoint.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C34E1CC31E0400DB3429 /* MGLMultiPoint.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C35F1CC31E0400DB3429 /* MGLOfflinePack.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C34F1CC31E0400DB3429 /* MGLOfflinePack.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3601CC31E0400DB3429 /* MGLOfflineRegion.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3501CC31E0400DB3429 /* MGLOfflineRegion.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3611CC31E0400DB3429 /* MGLOfflineStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3511CC31E0400DB3429 /* MGLOfflineStorage.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3621CC31E0400DB3429 /* MGLOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3521CC31E0400DB3429 /* MGLOverlay.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3631CC31E0400DB3429 /* MGLPointAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3531CC31E0400DB3429 /* MGLPointAnnotation.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3641CC31E0400DB3429 /* MGLPolygon.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3541CC31E0400DB3429 /* MGLPolygon.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3651CC31E0400DB3429 /* MGLPolyline.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3551CC31E0400DB3429 /* MGLPolyline.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3661CC31E0400DB3429 /* MGLShape.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3561CC31E0400DB3429 /* MGLShape.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3671CC31E0400DB3429 /* MGLStyle.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3571CC31E0400DB3429 /* MGLStyle.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3681CC31E0400DB3429 /* MGLTilePyramidOfflineRegion.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3581CC31E0400DB3429 /* MGLTilePyramidOfflineRegion.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3691CC31E0400DB3429 /* MGLTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3591CC31E0400DB3429 /* MGLTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3841CC31E2A00DB3429 /* MGLAccountManager_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C36A1CC31E2A00DB3429 /* MGLAccountManager_Private.h */; };
+ DAE6C3851CC31E2A00DB3429 /* MGLAccountManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C36B1CC31E2A00DB3429 /* MGLAccountManager.m */; };
+ DAE6C3861CC31E2A00DB3429 /* MGLGeometry_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C36C1CC31E2A00DB3429 /* MGLGeometry_Private.h */; };
+ DAE6C3871CC31E2A00DB3429 /* MGLGeometry.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C36D1CC31E2A00DB3429 /* MGLGeometry.mm */; };
+ DAE6C3881CC31E2A00DB3429 /* MGLMapCamera.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C36E1CC31E2A00DB3429 /* MGLMapCamera.mm */; };
+ DAE6C3891CC31E2A00DB3429 /* MGLMultiPoint_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C36F1CC31E2A00DB3429 /* MGLMultiPoint_Private.h */; };
+ DAE6C38A1CC31E2A00DB3429 /* MGLMultiPoint.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3701CC31E2A00DB3429 /* MGLMultiPoint.mm */; };
+ DAE6C38B1CC31E2A00DB3429 /* MGLOfflinePack_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3711CC31E2A00DB3429 /* MGLOfflinePack_Private.h */; };
+ DAE6C38C1CC31E2A00DB3429 /* MGLOfflinePack.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3721CC31E2A00DB3429 /* MGLOfflinePack.mm */; };
+ DAE6C38D1CC31E2A00DB3429 /* MGLOfflineRegion_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3731CC31E2A00DB3429 /* MGLOfflineRegion_Private.h */; };
+ DAE6C38E1CC31E2A00DB3429 /* MGLOfflineStorage_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3741CC31E2A00DB3429 /* MGLOfflineStorage_Private.h */; };
+ DAE6C38F1CC31E2A00DB3429 /* MGLOfflineStorage.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3751CC31E2A00DB3429 /* MGLOfflineStorage.mm */; };
+ DAE6C3901CC31E2A00DB3429 /* MGLPointAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3761CC31E2A00DB3429 /* MGLPointAnnotation.m */; };
+ DAE6C3911CC31E2A00DB3429 /* MGLPolygon.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3771CC31E2A00DB3429 /* MGLPolygon.mm */; };
+ DAE6C3921CC31E2A00DB3429 /* MGLPolyline.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3781CC31E2A00DB3429 /* MGLPolyline.mm */; };
+ DAE6C3931CC31E2A00DB3429 /* MGLShape.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3791CC31E2A00DB3429 /* MGLShape.m */; };
+ DAE6C3941CC31E2A00DB3429 /* MGLStyle.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C37A1CC31E2A00DB3429 /* MGLStyle.mm */; };
+ DAE6C3951CC31E2A00DB3429 /* MGLTilePyramidOfflineRegion.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C37B1CC31E2A00DB3429 /* MGLTilePyramidOfflineRegion.mm */; };
+ DAE6C3961CC31E2A00DB3429 /* MGLTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C37C1CC31E2A00DB3429 /* MGLTypes.m */; };
+ DAE6C3971CC31E2A00DB3429 /* NSBundle+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C37D1CC31E2A00DB3429 /* NSBundle+MGLAdditions.h */; };
+ DAE6C3981CC31E2A00DB3429 /* NSBundle+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C37E1CC31E2A00DB3429 /* NSBundle+MGLAdditions.m */; };
+ DAE6C3991CC31E2A00DB3429 /* NSException+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C37F1CC31E2A00DB3429 /* NSException+MGLAdditions.h */; };
+ DAE6C39A1CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3801CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.h */; };
+ DAE6C39B1CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3811CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.m */; };
+ DAE6C39C1CC31E2A00DB3429 /* NSString+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3821CC31E2A00DB3429 /* NSString+MGLAdditions.h */; };
+ DAE6C39D1CC31E2A00DB3429 /* NSString+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3831CC31E2A00DB3429 /* NSString+MGLAdditions.m */; };
+ DAE6C3A31CC31E9400DB3429 /* MGLAnnotationImage.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C39F1CC31E9400DB3429 /* MGLAnnotationImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3A41CC31E9400DB3429 /* MGLMapView.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3A01CC31E9400DB3429 /* MGLMapView.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3A51CC31E9400DB3429 /* MGLMapView+IBAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3A11CC31E9400DB3429 /* MGLMapView+IBAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3A61CC31E9400DB3429 /* MGLMapViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3A21CC31E9400DB3429 /* MGLMapViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3B11CC31EF300DB3429 /* MGLAnnotationImage.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3A71CC31EF300DB3429 /* MGLAnnotationImage.m */; };
+ DAE6C3B21CC31EF300DB3429 /* MGLAttributionButton.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3A81CC31EF300DB3429 /* MGLAttributionButton.h */; };
+ DAE6C3B31CC31EF300DB3429 /* MGLAttributionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3A91CC31EF300DB3429 /* MGLAttributionButton.m */; };
+ DAE6C3B41CC31EF300DB3429 /* MGLCompassCell.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3AA1CC31EF300DB3429 /* MGLCompassCell.h */; };
+ DAE6C3B51CC31EF300DB3429 /* MGLCompassCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3AB1CC31EF300DB3429 /* MGLCompassCell.m */; };
+ DAE6C3B61CC31EF300DB3429 /* MGLMapView_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3AC1CC31EF300DB3429 /* MGLMapView_Private.h */; };
+ DAE6C3B71CC31EF300DB3429 /* MGLMapView.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3AD1CC31EF300DB3429 /* MGLMapView.mm */; };
+ DAE6C3B81CC31EF300DB3429 /* MGLMapView+IBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3AE1CC31EF300DB3429 /* MGLMapView+IBAdditions.m */; };
+ DAE6C3B91CC31EF300DB3429 /* MGLOpenGLLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3AF1CC31EF300DB3429 /* MGLOpenGLLayer.h */; };
+ DAE6C3BA1CC31EF300DB3429 /* MGLOpenGLLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3B01CC31EF300DB3429 /* MGLOpenGLLayer.mm */; };
+ DAE6C3BE1CC31F2E00DB3429 /* default_marker.pdf in Resources */ = {isa = PBXBuildFile; fileRef = DAE6C3BB1CC31F2E00DB3429 /* default_marker.pdf */; };
+ DAE6C3BF1CC31F2E00DB3429 /* mapbox.pdf in Resources */ = {isa = PBXBuildFile; fileRef = DAE6C3BC1CC31F2E00DB3429 /* mapbox.pdf */; };
+ DAE6C3C21CC31F4500DB3429 /* Mapbox.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3C11CC31F4500DB3429 /* Mapbox.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ DAE6C3C71CC3499100DB3429 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DAE6C3C61CC3499100DB3429 /* libsqlite3.tbd */; };
+ DAE6C3D21CC34C9900DB3429 /* MGLGeometryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3C81CC34BD800DB3429 /* MGLGeometryTests.mm */; };
+ DAE6C3D31CC34C9900DB3429 /* MGLOfflinePackTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3C91CC34BD800DB3429 /* MGLOfflinePackTests.m */; };
+ DAE6C3D41CC34C9900DB3429 /* MGLOfflineRegionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3CA1CC34BD800DB3429 /* MGLOfflineRegionTests.m */; };
+ DAE6C3D51CC34C9900DB3429 /* MGLOfflineStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3CB1CC34BD800DB3429 /* MGLOfflineStorageTests.m */; };
+ DAE6C3D61CC34C9900DB3429 /* MGLStyleTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3CC1CC34BD800DB3429 /* MGLStyleTests.mm */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ DAE6C3331CC30DB200DB3429 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DA839E8A1CC2E3400062CAFB /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DAE6C3271CC30DB200DB3429;
+ remoteInfo = dynamic;
+ };
+ DAE6C33B1CC30DB200DB3429 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DA839E8A1CC2E3400062CAFB /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DAE6C3271CC30DB200DB3429;
+ remoteInfo = dynamic;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ DAE6C3221CC30B3C00DB3429 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ DAE6C33E1CC30DB200DB3429 /* Mapbox.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 52BECB091CC5A26F009CD791 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
+ DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = "<group>"; };
+ DA35A2A31CC9EB1A00E826B2 /* MGLCoordinateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCoordinateFormatter.h; sourceTree = "<group>"; };
+ DA35A2A51CC9EB2700E826B2 /* MGLCoordinateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLCoordinateFormatter.m; sourceTree = "<group>"; };
+ DA35A2A71CC9F41600E826B2 /* MGLCoordinateFormatterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLCoordinateFormatterTests.m; path = ../../darwin/test/MGLCoordinateFormatterTests.m; sourceTree = "<group>"; };
+ DA35A2AB1CCA091800E826B2 /* MGLCompassDirectionFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCompassDirectionFormatter.h; sourceTree = "<group>"; };
+ DA35A2AC1CCA091800E826B2 /* MGLCompassDirectionFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLCompassDirectionFormatter.m; sourceTree = "<group>"; };
+ DA35A2B51CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLCompassDirectionFormatterTests.m; path = ../../darwin/test/MGLCompassDirectionFormatterTests.m; sourceTree = "<group>"; };
+ DA35A2BD1CCA9B1A00E826B2 /* MGLClockDirectionFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLClockDirectionFormatter.h; sourceTree = "<group>"; };
+ DA35A2BE1CCA9B1A00E826B2 /* MGLClockDirectionFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLClockDirectionFormatter.m; sourceTree = "<group>"; };
+ DA35A2C11CCA9F4A00E826B2 /* MGLClockDirectionFormatterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLClockDirectionFormatterTests.m; path = ../../darwin/test/MGLClockDirectionFormatterTests.m; sourceTree = "<group>"; };
+ DA35A2CD1CCAAED300E826B2 /* NSValue+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSValue+MGLAdditions.h"; sourceTree = "<group>"; };
+ DA35A2CE1CCAAED300E826B2 /* NSValue+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSValue+MGLAdditions.m"; sourceTree = "<group>"; };
+ DA839E921CC2E3400062CAFB /* Mapbox GL.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Mapbox GL.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ DA839E951CC2E3400062CAFB /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+ DA839E961CC2E3400062CAFB /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+ DA839E991CC2E3400062CAFB /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ DA839E9B1CC2E3400062CAFB /* MapDocument.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MapDocument.h; sourceTree = "<group>"; };
+ DA839E9C1CC2E3400062CAFB /* MapDocument.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MapDocument.m; sourceTree = "<group>"; };
+ DA839E9F1CC2E3400062CAFB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MapDocument.xib; sourceTree = "<group>"; };
+ DA839EA11CC2E3400062CAFB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+ DA839EA41CC2E3400062CAFB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
+ DA839EA61CC2E3400062CAFB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DA8933A61CCD287300E68420 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MGLAnnotationCallout.xib; sourceTree = "<group>"; };
+ DA8933AC1CCD290700E68420 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
+ DA8933B41CCD2C2500E68420 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Foundation.strings; sourceTree = "<group>"; };
+ DA8933B71CCD2C2D00E68420 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Foundation.stringsdict; sourceTree = "<group>"; };
+ DAC2ABC41CC6D343006D18C4 /* MGLAnnotationImage_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAnnotationImage_Private.h; sourceTree = "<group>"; };
+ DACC22121CF3D3E200D220D9 /* MGLFeature.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature.h; sourceTree = "<group>"; };
+ DACC22131CF3D3E200D220D9 /* MGLFeature.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFeature.mm; sourceTree = "<group>"; };
+ DACC22171CF3D4F700D220D9 /* MGLFeature_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature_Private.h; sourceTree = "<group>"; };
+ DAD165721CF4CD7A001FF4B9 /* MGLShapeCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLShapeCollection.h; sourceTree = "<group>"; };
+ DAD165731CF4CD7A001FF4B9 /* MGLShapeCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLShapeCollection.m; sourceTree = "<group>"; };
+ DAE6C2E11CC304F900DB3429 /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = "<group>"; };
+ DAE6C2E31CC3050F00DB3429 /* DroppedPinAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DroppedPinAnnotation.h; sourceTree = "<group>"; };
+ DAE6C2E41CC3050F00DB3429 /* DroppedPinAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DroppedPinAnnotation.m; sourceTree = "<group>"; };
+ DAE6C2E51CC3050F00DB3429 /* LocationCoordinate2DTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocationCoordinate2DTransformer.h; sourceTree = "<group>"; };
+ DAE6C2E61CC3050F00DB3429 /* LocationCoordinate2DTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocationCoordinate2DTransformer.m; sourceTree = "<group>"; };
+ DAE6C2E91CC3050F00DB3429 /* OfflinePackNameValueTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OfflinePackNameValueTransformer.h; sourceTree = "<group>"; };
+ DAE6C2EA1CC3050F00DB3429 /* OfflinePackNameValueTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OfflinePackNameValueTransformer.m; sourceTree = "<group>"; };
+ DAE6C2EB1CC3050F00DB3429 /* TimeIntervalTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimeIntervalTransformer.h; sourceTree = "<group>"; };
+ DAE6C2EC1CC3050F00DB3429 /* TimeIntervalTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimeIntervalTransformer.m; sourceTree = "<group>"; };
+ DAE6C3281CC30DB200DB3429 /* Mapbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Mapbox.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ DAE6C32C1CC30DB200DB3429 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DAE6C3311CC30DB200DB3429 /* test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = test.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ DAE6C33A1CC30DB200DB3429 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DAE6C3451CC31D1200DB3429 /* libmbgl-core.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libmbgl-core.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ DAE6C3461CC31D1200DB3429 /* libmbgl-platform-macos.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libmbgl-platform-macos.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ DAE6C34A1CC31E0400DB3429 /* MGLAccountManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAccountManager.h; sourceTree = "<group>"; };
+ DAE6C34B1CC31E0400DB3429 /* MGLAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAnnotation.h; sourceTree = "<group>"; };
+ DAE6C34C1CC31E0400DB3429 /* MGLGeometry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLGeometry.h; sourceTree = "<group>"; };
+ DAE6C34D1CC31E0400DB3429 /* MGLMapCamera.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapCamera.h; sourceTree = "<group>"; };
+ DAE6C34E1CC31E0400DB3429 /* MGLMultiPoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMultiPoint.h; sourceTree = "<group>"; };
+ DAE6C34F1CC31E0400DB3429 /* MGLOfflinePack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLOfflinePack.h; sourceTree = "<group>"; };
+ DAE6C3501CC31E0400DB3429 /* MGLOfflineRegion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLOfflineRegion.h; sourceTree = "<group>"; };
+ DAE6C3511CC31E0400DB3429 /* MGLOfflineStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLOfflineStorage.h; sourceTree = "<group>"; };
+ DAE6C3521CC31E0400DB3429 /* MGLOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLOverlay.h; sourceTree = "<group>"; };
+ DAE6C3531CC31E0400DB3429 /* MGLPointAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLPointAnnotation.h; sourceTree = "<group>"; };
+ DAE6C3541CC31E0400DB3429 /* MGLPolygon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLPolygon.h; sourceTree = "<group>"; };
+ DAE6C3551CC31E0400DB3429 /* MGLPolyline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLPolyline.h; sourceTree = "<group>"; };
+ DAE6C3561CC31E0400DB3429 /* MGLShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLShape.h; sourceTree = "<group>"; };
+ DAE6C3571CC31E0400DB3429 /* MGLStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLStyle.h; sourceTree = "<group>"; };
+ DAE6C3581CC31E0400DB3429 /* MGLTilePyramidOfflineRegion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLTilePyramidOfflineRegion.h; sourceTree = "<group>"; };
+ DAE6C3591CC31E0400DB3429 /* MGLTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLTypes.h; sourceTree = "<group>"; };
+ DAE6C36A1CC31E2A00DB3429 /* MGLAccountManager_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAccountManager_Private.h; sourceTree = "<group>"; };
+ DAE6C36B1CC31E2A00DB3429 /* MGLAccountManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLAccountManager.m; sourceTree = "<group>"; };
+ DAE6C36C1CC31E2A00DB3429 /* MGLGeometry_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLGeometry_Private.h; sourceTree = "<group>"; };
+ DAE6C36D1CC31E2A00DB3429 /* MGLGeometry.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLGeometry.mm; sourceTree = "<group>"; };
+ DAE6C36E1CC31E2A00DB3429 /* MGLMapCamera.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapCamera.mm; sourceTree = "<group>"; };
+ DAE6C36F1CC31E2A00DB3429 /* MGLMultiPoint_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMultiPoint_Private.h; sourceTree = "<group>"; };
+ DAE6C3701CC31E2A00DB3429 /* MGLMultiPoint.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMultiPoint.mm; sourceTree = "<group>"; };
+ DAE6C3711CC31E2A00DB3429 /* MGLOfflinePack_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLOfflinePack_Private.h; sourceTree = "<group>"; };
+ DAE6C3721CC31E2A00DB3429 /* MGLOfflinePack.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLOfflinePack.mm; sourceTree = "<group>"; };
+ DAE6C3731CC31E2A00DB3429 /* MGLOfflineRegion_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLOfflineRegion_Private.h; sourceTree = "<group>"; };
+ DAE6C3741CC31E2A00DB3429 /* MGLOfflineStorage_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLOfflineStorage_Private.h; sourceTree = "<group>"; };
+ DAE6C3751CC31E2A00DB3429 /* MGLOfflineStorage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLOfflineStorage.mm; sourceTree = "<group>"; };
+ DAE6C3761CC31E2A00DB3429 /* MGLPointAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLPointAnnotation.m; sourceTree = "<group>"; };
+ DAE6C3771CC31E2A00DB3429 /* MGLPolygon.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLPolygon.mm; sourceTree = "<group>"; };
+ DAE6C3781CC31E2A00DB3429 /* MGLPolyline.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLPolyline.mm; sourceTree = "<group>"; };
+ DAE6C3791CC31E2A00DB3429 /* MGLShape.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLShape.m; sourceTree = "<group>"; };
+ DAE6C37A1CC31E2A00DB3429 /* MGLStyle.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLStyle.mm; sourceTree = "<group>"; };
+ DAE6C37B1CC31E2A00DB3429 /* MGLTilePyramidOfflineRegion.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLTilePyramidOfflineRegion.mm; sourceTree = "<group>"; };
+ DAE6C37C1CC31E2A00DB3429 /* MGLTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLTypes.m; sourceTree = "<group>"; };
+ DAE6C37D1CC31E2A00DB3429 /* NSBundle+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+MGLAdditions.h"; sourceTree = "<group>"; };
+ DAE6C37E1CC31E2A00DB3429 /* NSBundle+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+MGLAdditions.m"; sourceTree = "<group>"; };
+ DAE6C37F1CC31E2A00DB3429 /* NSException+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSException+MGLAdditions.h"; sourceTree = "<group>"; };
+ DAE6C3801CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSProcessInfo+MGLAdditions.h"; sourceTree = "<group>"; };
+ DAE6C3811CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSProcessInfo+MGLAdditions.m"; sourceTree = "<group>"; };
+ DAE6C3821CC31E2A00DB3429 /* NSString+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+MGLAdditions.h"; sourceTree = "<group>"; };
+ DAE6C3831CC31E2A00DB3429 /* NSString+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MGLAdditions.m"; sourceTree = "<group>"; };
+ DAE6C39F1CC31E9400DB3429 /* MGLAnnotationImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAnnotationImage.h; sourceTree = "<group>"; };
+ DAE6C3A01CC31E9400DB3429 /* MGLMapView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapView.h; sourceTree = "<group>"; };
+ DAE6C3A11CC31E9400DB3429 /* MGLMapView+IBAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLMapView+IBAdditions.h"; sourceTree = "<group>"; };
+ DAE6C3A21CC31E9400DB3429 /* MGLMapViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapViewDelegate.h; sourceTree = "<group>"; };
+ DAE6C3A71CC31EF300DB3429 /* MGLAnnotationImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLAnnotationImage.m; sourceTree = "<group>"; };
+ DAE6C3A81CC31EF300DB3429 /* MGLAttributionButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionButton.h; sourceTree = "<group>"; };
+ DAE6C3A91CC31EF300DB3429 /* MGLAttributionButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLAttributionButton.m; sourceTree = "<group>"; };
+ DAE6C3AA1CC31EF300DB3429 /* MGLCompassCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCompassCell.h; sourceTree = "<group>"; };
+ DAE6C3AB1CC31EF300DB3429 /* MGLCompassCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLCompassCell.m; sourceTree = "<group>"; };
+ DAE6C3AC1CC31EF300DB3429 /* MGLMapView_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapView_Private.h; sourceTree = "<group>"; };
+ DAE6C3AD1CC31EF300DB3429 /* MGLMapView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapView.mm; sourceTree = "<group>"; };
+ DAE6C3AE1CC31EF300DB3429 /* MGLMapView+IBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLMapView+IBAdditions.m"; sourceTree = "<group>"; };
+ DAE6C3AF1CC31EF300DB3429 /* MGLOpenGLLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLOpenGLLayer.h; sourceTree = "<group>"; };
+ DAE6C3B01CC31EF300DB3429 /* MGLOpenGLLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLOpenGLLayer.mm; sourceTree = "<group>"; };
+ DAE6C3BB1CC31F2E00DB3429 /* default_marker.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = default_marker.pdf; sourceTree = "<group>"; };
+ DAE6C3BC1CC31F2E00DB3429 /* mapbox.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = mapbox.pdf; sourceTree = "<group>"; };
+ DAE6C3C11CC31F4500DB3429 /* Mapbox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Mapbox.h; path = src/Mapbox.h; sourceTree = SOURCE_ROOT; };
+ DAE6C3C51CC31F9100DB3429 /* mbgl.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = mbgl.xcconfig; path = ../../build/macos/mbgl.xcconfig; sourceTree = "<group>"; };
+ DAE6C3C61CC3499100DB3429 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
+ DAE6C3C81CC34BD800DB3429 /* MGLGeometryTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLGeometryTests.mm; path = ../../darwin/test/MGLGeometryTests.mm; sourceTree = "<group>"; };
+ DAE6C3C91CC34BD800DB3429 /* MGLOfflinePackTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLOfflinePackTests.m; path = ../../darwin/test/MGLOfflinePackTests.m; sourceTree = "<group>"; };
+ DAE6C3CA1CC34BD800DB3429 /* MGLOfflineRegionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLOfflineRegionTests.m; path = ../../darwin/test/MGLOfflineRegionTests.m; sourceTree = "<group>"; };
+ DAE6C3CB1CC34BD800DB3429 /* MGLOfflineStorageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLOfflineStorageTests.m; path = ../../darwin/test/MGLOfflineStorageTests.m; sourceTree = "<group>"; };
+ DAE6C3CC1CC34BD800DB3429 /* MGLStyleTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLStyleTests.mm; path = ../../darwin/test/MGLStyleTests.mm; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ DA839E8F1CC2E3400062CAFB /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DAE6C33D1CC30DB200DB3429 /* Mapbox.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DAE6C3241CC30DB200DB3429 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 52BECB0A1CC5A26F009CD791 /* SystemConfiguration.framework in Frameworks */,
+ DAE6C3471CC31D1200DB3429 /* libmbgl-core.a in Frameworks */,
+ DAE6C3481CC31D1200DB3429 /* libmbgl-platform-macos.a in Frameworks */,
+ DAE6C3C71CC3499100DB3429 /* libsqlite3.tbd in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DAE6C32E1CC30DB200DB3429 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DAB6924A1CC75A31005AAB54 /* libmbgl-core.a in Frameworks */,
+ DAE6C3321CC30DB200DB3429 /* Mapbox.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ DA839E891CC2E3400062CAFB = {
+ isa = PBXGroup;
+ children = (
+ DA839E941CC2E3400062CAFB /* Demo App */,
+ DAE6C3291CC30DB200DB3429 /* SDK */,
+ DAE6C3371CC30DB200DB3429 /* SDK Tests */,
+ DAE6C31E1CC308BC00DB3429 /* Frameworks */,
+ DAE6C3C41CC31F7800DB3429 /* Configuration */,
+ DA839E931CC2E3400062CAFB /* Products */,
+ );
+ sourceTree = "<group>";
+ };
+ DA839E931CC2E3400062CAFB /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ DA839E921CC2E3400062CAFB /* Mapbox GL.app */,
+ DAE6C3281CC30DB200DB3429 /* Mapbox.framework */,
+ DAE6C3311CC30DB200DB3429 /* test.xctest */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ DA839E941CC2E3400062CAFB /* Demo App */ = {
+ isa = PBXGroup;
+ children = (
+ DA839E951CC2E3400062CAFB /* AppDelegate.h */,
+ DA839E961CC2E3400062CAFB /* AppDelegate.m */,
+ DAE6C2E31CC3050F00DB3429 /* DroppedPinAnnotation.h */,
+ DAE6C2E41CC3050F00DB3429 /* DroppedPinAnnotation.m */,
+ DAE6C2E51CC3050F00DB3429 /* LocationCoordinate2DTransformer.h */,
+ DAE6C2E61CC3050F00DB3429 /* LocationCoordinate2DTransformer.m */,
+ DA839E9B1CC2E3400062CAFB /* MapDocument.h */,
+ DA839E9C1CC2E3400062CAFB /* MapDocument.m */,
+ DA839E9E1CC2E3400062CAFB /* MapDocument.xib */,
+ DAE6C2E91CC3050F00DB3429 /* OfflinePackNameValueTransformer.h */,
+ DAE6C2EA1CC3050F00DB3429 /* OfflinePackNameValueTransformer.m */,
+ DAE6C2EB1CC3050F00DB3429 /* TimeIntervalTransformer.h */,
+ DAE6C2EC1CC3050F00DB3429 /* TimeIntervalTransformer.m */,
+ DA839EA11CC2E3400062CAFB /* Assets.xcassets */,
+ DA839EA31CC2E3400062CAFB /* MainMenu.xib */,
+ DAE6C2E11CC304F900DB3429 /* Credits.rtf */,
+ DA839EA61CC2E3400062CAFB /* Info.plist */,
+ DA839E981CC2E3400062CAFB /* Supporting Files */,
+ );
+ name = "Demo App";
+ path = app;
+ sourceTree = "<group>";
+ };
+ DA839E981CC2E3400062CAFB /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ DA839E991CC2E3400062CAFB /* main.m */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+ DA8933A81CCD28D100E68420 /* Kit Resources */ = {
+ isa = PBXGroup;
+ children = (
+ DA8933AB1CCD290700E68420 /* Localizable.strings */,
+ DAE6C3BB1CC31F2E00DB3429 /* default_marker.pdf */,
+ DAE6C3BC1CC31F2E00DB3429 /* mapbox.pdf */,
+ DA8933A71CCD287300E68420 /* MGLAnnotationCallout.xib */,
+ );
+ name = "Kit Resources";
+ sourceTree = "<group>";
+ };
+ DA8933B21CCD2C0700E68420 /* Foundation Resources */ = {
+ isa = PBXGroup;
+ children = (
+ DA8933B31CCD2C2500E68420 /* Foundation.strings */,
+ DA8933B61CCD2C2D00E68420 /* Foundation.stringsdict */,
+ );
+ name = "Foundation Resources";
+ path = ../../darwin/resources;
+ sourceTree = "<group>";
+ };
+ DAD1657C1CF4CE6B001FF4B9 /* Formatters */ = {
+ isa = PBXGroup;
+ children = (
+ DA35A2BD1CCA9B1A00E826B2 /* MGLClockDirectionFormatter.h */,
+ DA35A2BE1CCA9B1A00E826B2 /* MGLClockDirectionFormatter.m */,
+ DA35A2AB1CCA091800E826B2 /* MGLCompassDirectionFormatter.h */,
+ DA35A2AC1CCA091800E826B2 /* MGLCompassDirectionFormatter.m */,
+ DA35A2A31CC9EB1A00E826B2 /* MGLCoordinateFormatter.h */,
+ DA35A2A51CC9EB2700E826B2 /* MGLCoordinateFormatter.m */,
+ );
+ name = Formatters;
+ sourceTree = "<group>";
+ };
+ DAD1657D1CF4CECB001FF4B9 /* Geometry */ = {
+ isa = PBXGroup;
+ children = (
+ DAE6C34B1CC31E0400DB3429 /* MGLAnnotation.h */,
+ DACC22121CF3D3E200D220D9 /* MGLFeature.h */,
+ DACC22171CF3D4F700D220D9 /* MGLFeature_Private.h */,
+ DACC22131CF3D3E200D220D9 /* MGLFeature.mm */,
+ DAE6C34C1CC31E0400DB3429 /* MGLGeometry.h */,
+ DAE6C36C1CC31E2A00DB3429 /* MGLGeometry_Private.h */,
+ DAE6C36D1CC31E2A00DB3429 /* MGLGeometry.mm */,
+ DAE6C34E1CC31E0400DB3429 /* MGLMultiPoint.h */,
+ DAE6C36F1CC31E2A00DB3429 /* MGLMultiPoint_Private.h */,
+ DAE6C3701CC31E2A00DB3429 /* MGLMultiPoint.mm */,
+ DAE6C3521CC31E0400DB3429 /* MGLOverlay.h */,
+ DAE6C3531CC31E0400DB3429 /* MGLPointAnnotation.h */,
+ DAE6C3761CC31E2A00DB3429 /* MGLPointAnnotation.m */,
+ DAE6C3541CC31E0400DB3429 /* MGLPolygon.h */,
+ DAE6C3771CC31E2A00DB3429 /* MGLPolygon.mm */,
+ DAE6C3551CC31E0400DB3429 /* MGLPolyline.h */,
+ DAE6C3781CC31E2A00DB3429 /* MGLPolyline.mm */,
+ DAE6C3561CC31E0400DB3429 /* MGLShape.h */,
+ DAE6C3791CC31E2A00DB3429 /* MGLShape.m */,
+ DAD165721CF4CD7A001FF4B9 /* MGLShapeCollection.h */,
+ DAD165731CF4CD7A001FF4B9 /* MGLShapeCollection.m */,
+ );
+ name = Geometry;
+ sourceTree = "<group>";
+ };
+ DAD1657E1CF4CF04001FF4B9 /* Offline Maps */ = {
+ isa = PBXGroup;
+ children = (
+ DAE6C34F1CC31E0400DB3429 /* MGLOfflinePack.h */,
+ DAE6C3711CC31E2A00DB3429 /* MGLOfflinePack_Private.h */,
+ DAE6C3721CC31E2A00DB3429 /* MGLOfflinePack.mm */,
+ DAE6C3501CC31E0400DB3429 /* MGLOfflineRegion.h */,
+ DAE6C3731CC31E2A00DB3429 /* MGLOfflineRegion_Private.h */,
+ DAE6C3511CC31E0400DB3429 /* MGLOfflineStorage.h */,
+ DAE6C3741CC31E2A00DB3429 /* MGLOfflineStorage_Private.h */,
+ DAE6C3751CC31E2A00DB3429 /* MGLOfflineStorage.mm */,
+ DAE6C3581CC31E0400DB3429 /* MGLTilePyramidOfflineRegion.h */,
+ DAE6C37B1CC31E2A00DB3429 /* MGLTilePyramidOfflineRegion.mm */,
+ );
+ name = "Offline Maps";
+ sourceTree = "<group>";
+ };
+ DAD1657F1CF4CF50001FF4B9 /* Categories */ = {
+ isa = PBXGroup;
+ children = (
+ DAE6C37D1CC31E2A00DB3429 /* NSBundle+MGLAdditions.h */,
+ DAE6C37E1CC31E2A00DB3429 /* NSBundle+MGLAdditions.m */,
+ DAE6C37F1CC31E2A00DB3429 /* NSException+MGLAdditions.h */,
+ DAE6C3801CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.h */,
+ DAE6C3811CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.m */,
+ DAE6C3821CC31E2A00DB3429 /* NSString+MGLAdditions.h */,
+ DAE6C3831CC31E2A00DB3429 /* NSString+MGLAdditions.m */,
+ DA35A2CD1CCAAED300E826B2 /* NSValue+MGLAdditions.h */,
+ DA35A2CE1CCAAED300E826B2 /* NSValue+MGLAdditions.m */,
+ );
+ name = Categories;
+ sourceTree = "<group>";
+ };
+ DAE6C31E1CC308BC00DB3429 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 52BECB091CC5A26F009CD791 /* SystemConfiguration.framework */,
+ DAE6C3451CC31D1200DB3429 /* libmbgl-core.a */,
+ DAE6C3461CC31D1200DB3429 /* libmbgl-platform-macos.a */,
+ DAE6C3C61CC3499100DB3429 /* libsqlite3.tbd */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ DAE6C3291CC30DB200DB3429 /* SDK */ = {
+ isa = PBXGroup;
+ children = (
+ DAE6C3C11CC31F4500DB3429 /* Mapbox.h */,
+ DAE6C3491CC31DF500DB3429 /* Foundation */,
+ DA8933B21CCD2C0700E68420 /* Foundation Resources */,
+ DAE6C39E1CC31E7C00DB3429 /* Kit */,
+ DA8933A81CCD28D100E68420 /* Kit Resources */,
+ DAE6C32C1CC30DB200DB3429 /* Info.plist */,
+ );
+ name = SDK;
+ path = sdk;
+ sourceTree = "<group>";
+ };
+ DAE6C3371CC30DB200DB3429 /* SDK Tests */ = {
+ isa = PBXGroup;
+ children = (
+ DA35A2C11CCA9F4A00E826B2 /* MGLClockDirectionFormatterTests.m */,
+ DA35A2B51CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m */,
+ DA35A2A71CC9F41600E826B2 /* MGLCoordinateFormatterTests.m */,
+ DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */,
+ DAE6C3C81CC34BD800DB3429 /* MGLGeometryTests.mm */,
+ DAE6C3C91CC34BD800DB3429 /* MGLOfflinePackTests.m */,
+ DAE6C3CA1CC34BD800DB3429 /* MGLOfflineRegionTests.m */,
+ DAE6C3CB1CC34BD800DB3429 /* MGLOfflineStorageTests.m */,
+ DAE6C3CC1CC34BD800DB3429 /* MGLStyleTests.mm */,
+ DAE6C33A1CC30DB200DB3429 /* Info.plist */,
+ );
+ name = "SDK Tests";
+ path = test;
+ sourceTree = "<group>";
+ };
+ DAE6C3491CC31DF500DB3429 /* Foundation */ = {
+ isa = PBXGroup;
+ children = (
+ DAD1657F1CF4CF50001FF4B9 /* Categories */,
+ DAD1657C1CF4CE6B001FF4B9 /* Formatters */,
+ DAD1657D1CF4CECB001FF4B9 /* Geometry */,
+ DAD1657E1CF4CF04001FF4B9 /* Offline Maps */,
+ DAE6C34A1CC31E0400DB3429 /* MGLAccountManager.h */,
+ DAE6C36A1CC31E2A00DB3429 /* MGLAccountManager_Private.h */,
+ DAE6C36B1CC31E2A00DB3429 /* MGLAccountManager.m */,
+ DAE6C34D1CC31E0400DB3429 /* MGLMapCamera.h */,
+ DAE6C36E1CC31E2A00DB3429 /* MGLMapCamera.mm */,
+ DAE6C3571CC31E0400DB3429 /* MGLStyle.h */,
+ DAE6C37A1CC31E2A00DB3429 /* MGLStyle.mm */,
+ DAE6C3591CC31E0400DB3429 /* MGLTypes.h */,
+ DAE6C37C1CC31E2A00DB3429 /* MGLTypes.m */,
+ );
+ name = Foundation;
+ path = ../darwin/src;
+ sourceTree = SOURCE_ROOT;
+ };
+ DAE6C39E1CC31E7C00DB3429 /* Kit */ = {
+ isa = PBXGroup;
+ children = (
+ DAE6C39F1CC31E9400DB3429 /* MGLAnnotationImage.h */,
+ DAC2ABC41CC6D343006D18C4 /* MGLAnnotationImage_Private.h */,
+ DAE6C3A71CC31EF300DB3429 /* MGLAnnotationImage.m */,
+ DAE6C3A81CC31EF300DB3429 /* MGLAttributionButton.h */,
+ DAE6C3A91CC31EF300DB3429 /* MGLAttributionButton.m */,
+ DAE6C3AA1CC31EF300DB3429 /* MGLCompassCell.h */,
+ DAE6C3AB1CC31EF300DB3429 /* MGLCompassCell.m */,
+ DAE6C3A01CC31E9400DB3429 /* MGLMapView.h */,
+ DAE6C3AC1CC31EF300DB3429 /* MGLMapView_Private.h */,
+ DAE6C3AD1CC31EF300DB3429 /* MGLMapView.mm */,
+ DAE6C3A11CC31E9400DB3429 /* MGLMapView+IBAdditions.h */,
+ DAE6C3AE1CC31EF300DB3429 /* MGLMapView+IBAdditions.m */,
+ DAE6C3A21CC31E9400DB3429 /* MGLMapViewDelegate.h */,
+ DAE6C3AF1CC31EF300DB3429 /* MGLOpenGLLayer.h */,
+ DAE6C3B01CC31EF300DB3429 /* MGLOpenGLLayer.mm */,
+ );
+ name = Kit;
+ path = src;
+ sourceTree = SOURCE_ROOT;
+ };
+ DAE6C3C41CC31F7800DB3429 /* Configuration */ = {
+ isa = PBXGroup;
+ children = (
+ DAE6C3C51CC31F9100DB3429 /* mbgl.xcconfig */,
+ );
+ name = Configuration;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ DAE6C3251CC30DB200DB3429 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DAE6C38D1CC31E2A00DB3429 /* MGLOfflineRegion_Private.h in Headers */,
+ DAE6C35B1CC31E0400DB3429 /* MGLAnnotation.h in Headers */,
+ DAE6C3B61CC31EF300DB3429 /* MGLMapView_Private.h in Headers */,
+ DAE6C3B21CC31EF300DB3429 /* MGLAttributionButton.h in Headers */,
+ DAE6C3A31CC31E9400DB3429 /* MGLAnnotationImage.h in Headers */,
+ DAE6C3A41CC31E9400DB3429 /* MGLMapView.h in Headers */,
+ DAE6C3611CC31E0400DB3429 /* MGLOfflineStorage.h in Headers */,
+ DAE6C35E1CC31E0400DB3429 /* MGLMultiPoint.h in Headers */,
+ DAE6C3971CC31E2A00DB3429 /* NSBundle+MGLAdditions.h in Headers */,
+ DAD165741CF4CD7A001FF4B9 /* MGLShapeCollection.h in Headers */,
+ DAE6C3631CC31E0400DB3429 /* MGLPointAnnotation.h in Headers */,
+ DAC2ABC51CC6D343006D18C4 /* MGLAnnotationImage_Private.h in Headers */,
+ DAE6C35F1CC31E0400DB3429 /* MGLOfflinePack.h in Headers */,
+ DAE6C39C1CC31E2A00DB3429 /* NSString+MGLAdditions.h in Headers */,
+ DAE6C3861CC31E2A00DB3429 /* MGLGeometry_Private.h in Headers */,
+ DAE6C3841CC31E2A00DB3429 /* MGLAccountManager_Private.h in Headers */,
+ DAE6C3691CC31E0400DB3429 /* MGLTypes.h in Headers */,
+ DAE6C3991CC31E2A00DB3429 /* NSException+MGLAdditions.h in Headers */,
+ DAE6C3661CC31E0400DB3429 /* MGLShape.h in Headers */,
+ DAE6C3C21CC31F4500DB3429 /* Mapbox.h in Headers */,
+ DAE6C3641CC31E0400DB3429 /* MGLPolygon.h in Headers */,
+ DA35A2BF1CCA9B1A00E826B2 /* MGLClockDirectionFormatter.h in Headers */,
+ DA35A2A41CC9EB1A00E826B2 /* MGLCoordinateFormatter.h in Headers */,
+ DAE6C3621CC31E0400DB3429 /* MGLOverlay.h in Headers */,
+ DAE6C3651CC31E0400DB3429 /* MGLPolyline.h in Headers */,
+ DAE6C39A1CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.h in Headers */,
+ DAE6C38E1CC31E2A00DB3429 /* MGLOfflineStorage_Private.h in Headers */,
+ DAE6C3601CC31E0400DB3429 /* MGLOfflineRegion.h in Headers */,
+ DAE6C3681CC31E0400DB3429 /* MGLTilePyramidOfflineRegion.h in Headers */,
+ DA35A2CF1CCAAED300E826B2 /* NSValue+MGLAdditions.h in Headers */,
+ DAE6C3A61CC31E9400DB3429 /* MGLMapViewDelegate.h in Headers */,
+ DAE6C38B1CC31E2A00DB3429 /* MGLOfflinePack_Private.h in Headers */,
+ DACC22141CF3D3E200D220D9 /* MGLFeature.h in Headers */,
+ DAE6C35C1CC31E0400DB3429 /* MGLGeometry.h in Headers */,
+ DAE6C35A1CC31E0400DB3429 /* MGLAccountManager.h in Headers */,
+ DAE6C35D1CC31E0400DB3429 /* MGLMapCamera.h in Headers */,
+ DAE6C3B41CC31EF300DB3429 /* MGLCompassCell.h in Headers */,
+ DAE6C3B91CC31EF300DB3429 /* MGLOpenGLLayer.h in Headers */,
+ DAE6C3891CC31E2A00DB3429 /* MGLMultiPoint_Private.h in Headers */,
+ DAE6C3A51CC31E9400DB3429 /* MGLMapView+IBAdditions.h in Headers */,
+ DA35A2AD1CCA091800E826B2 /* MGLCompassDirectionFormatter.h in Headers */,
+ DACC22181CF3D4F700D220D9 /* MGLFeature_Private.h in Headers */,
+ DAE6C3671CC31E0400DB3429 /* MGLStyle.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXLegacyTarget section */
+ DAAA17961CE13BAE00731EFE /* docs */ = {
+ isa = PBXLegacyTarget;
+ buildArgumentsString = "xdocument OUTPUT=build/macos/pkg/documentation";
+ buildConfigurationList = DAAA17991CE13BAE00731EFE /* Build configuration list for PBXLegacyTarget "docs" */;
+ buildPhases = (
+ );
+ buildToolPath = /usr/bin/make;
+ buildWorkingDirectory = ../../;
+ dependencies = (
+ );
+ name = docs;
+ passBuildSettingsInEnvironment = 1;
+ productName = docs;
+ };
+/* End PBXLegacyTarget section */
+
+/* Begin PBXNativeTarget section */
+ DA839E911CC2E3400062CAFB /* macosapp */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DA839EA91CC2E3400062CAFB /* Build configuration list for PBXNativeTarget "macosapp" */;
+ buildPhases = (
+ DA839E8E1CC2E3400062CAFB /* Sources */,
+ DA839E8F1CC2E3400062CAFB /* Frameworks */,
+ DA839E901CC2E3400062CAFB /* Resources */,
+ DAE6C3221CC30B3C00DB3429 /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DAE6C33C1CC30DB200DB3429 /* PBXTargetDependency */,
+ );
+ name = macosapp;
+ productName = macosapp;
+ productReference = DA839E921CC2E3400062CAFB /* Mapbox GL.app */;
+ productType = "com.apple.product-type.application";
+ };
+ DAE6C3271CC30DB200DB3429 /* dynamic */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DAE6C3431CC30DB200DB3429 /* Build configuration list for PBXNativeTarget "dynamic" */;
+ buildPhases = (
+ DAE6C3231CC30DB200DB3429 /* Sources */,
+ DAE6C3241CC30DB200DB3429 /* Frameworks */,
+ DAE6C3251CC30DB200DB3429 /* Headers */,
+ DAE6C3261CC30DB200DB3429 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = dynamic;
+ productName = dynamic;
+ productReference = DAE6C3281CC30DB200DB3429 /* Mapbox.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ DAE6C3301CC30DB200DB3429 /* test */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DAE6C3441CC30DB200DB3429 /* Build configuration list for PBXNativeTarget "test" */;
+ buildPhases = (
+ DAE6C32D1CC30DB200DB3429 /* Sources */,
+ DAE6C32E1CC30DB200DB3429 /* Frameworks */,
+ DAE6C32F1CC30DB200DB3429 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DAE6C3341CC30DB200DB3429 /* PBXTargetDependency */,
+ );
+ name = test;
+ productName = dynamicTests;
+ productReference = DAE6C3311CC30DB200DB3429 /* test.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ DA839E8A1CC2E3400062CAFB /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ CLASSPREFIX = MBX;
+ LastUpgradeCheck = 0730;
+ ORGANIZATIONNAME = Mapbox;
+ TargetAttributes = {
+ DA839E911CC2E3400062CAFB = {
+ CreatedOnToolsVersion = 7.3;
+ };
+ DAAA17961CE13BAE00731EFE = {
+ CreatedOnToolsVersion = 7.3.1;
+ };
+ DAE6C3271CC30DB200DB3429 = {
+ CreatedOnToolsVersion = 7.3;
+ };
+ DAE6C3301CC30DB200DB3429 = {
+ CreatedOnToolsVersion = 7.3;
+ };
+ };
+ };
+ buildConfigurationList = DA839E8D1CC2E3400062CAFB /* Build configuration list for PBXProject "macos" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = DA839E891CC2E3400062CAFB;
+ productRefGroup = DA839E931CC2E3400062CAFB /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ DA839E911CC2E3400062CAFB /* macosapp */,
+ DAE6C3271CC30DB200DB3429 /* dynamic */,
+ DAE6C3301CC30DB200DB3429 /* test */,
+ DAAA17961CE13BAE00731EFE /* docs */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ DA839E901CC2E3400062CAFB /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DA839EA21CC2E3400062CAFB /* Assets.xcassets in Resources */,
+ DA839EA01CC2E3400062CAFB /* MapDocument.xib in Resources */,
+ DA839EA51CC2E3400062CAFB /* MainMenu.xib in Resources */,
+ DAE6C2E21CC304F900DB3429 /* Credits.rtf in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DAE6C3261CC30DB200DB3429 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DA8933AE1CCD290700E68420 /* Localizable.strings in Resources */,
+ DAE6C3BE1CC31F2E00DB3429 /* default_marker.pdf in Resources */,
+ DAE6C3BF1CC31F2E00DB3429 /* mapbox.pdf in Resources */,
+ DA8933A51CCD287300E68420 /* MGLAnnotationCallout.xib in Resources */,
+ DA8933B51CCD2C2500E68420 /* Foundation.strings in Resources */,
+ DA8933B81CCD2C2D00E68420 /* Foundation.stringsdict in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DAE6C32F1CC30DB200DB3429 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ DA839E8E1CC2E3400062CAFB /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DA839E9D1CC2E3400062CAFB /* MapDocument.m in Sources */,
+ DAE6C2ED1CC3050F00DB3429 /* DroppedPinAnnotation.m in Sources */,
+ DAE6C2EE1CC3050F00DB3429 /* LocationCoordinate2DTransformer.m in Sources */,
+ DAE6C2F11CC3050F00DB3429 /* TimeIntervalTransformer.m in Sources */,
+ DA839E9A1CC2E3400062CAFB /* main.m in Sources */,
+ DA839E971CC2E3400062CAFB /* AppDelegate.m in Sources */,
+ DAE6C2F01CC3050F00DB3429 /* OfflinePackNameValueTransformer.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DAE6C3231CC30DB200DB3429 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DAE6C3901CC31E2A00DB3429 /* MGLPointAnnotation.m in Sources */,
+ DAE6C3981CC31E2A00DB3429 /* NSBundle+MGLAdditions.m in Sources */,
+ DAE6C3B71CC31EF300DB3429 /* MGLMapView.mm in Sources */,
+ DAE6C38C1CC31E2A00DB3429 /* MGLOfflinePack.mm in Sources */,
+ DAE6C3B11CC31EF300DB3429 /* MGLAnnotationImage.m in Sources */,
+ DACC22151CF3D3E200D220D9 /* MGLFeature.mm in Sources */,
+ DAE6C3B31CC31EF300DB3429 /* MGLAttributionButton.m in Sources */,
+ DAE6C3931CC31E2A00DB3429 /* MGLShape.m in Sources */,
+ DAE6C39D1CC31E2A00DB3429 /* NSString+MGLAdditions.m in Sources */,
+ DAE6C3941CC31E2A00DB3429 /* MGLStyle.mm in Sources */,
+ DAE6C3871CC31E2A00DB3429 /* MGLGeometry.mm in Sources */,
+ DAE6C3B81CC31EF300DB3429 /* MGLMapView+IBAdditions.m in Sources */,
+ DA35A2D01CCAAED300E826B2 /* NSValue+MGLAdditions.m in Sources */,
+ DA35A2C01CCA9B1A00E826B2 /* MGLClockDirectionFormatter.m in Sources */,
+ DAE6C3BA1CC31EF300DB3429 /* MGLOpenGLLayer.mm in Sources */,
+ DAE6C38A1CC31E2A00DB3429 /* MGLMultiPoint.mm in Sources */,
+ DAE6C3961CC31E2A00DB3429 /* MGLTypes.m in Sources */,
+ DA35A2A61CC9EB2700E826B2 /* MGLCoordinateFormatter.m in Sources */,
+ DAE6C3881CC31E2A00DB3429 /* MGLMapCamera.mm in Sources */,
+ DAE6C3911CC31E2A00DB3429 /* MGLPolygon.mm in Sources */,
+ DAE6C39B1CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.m in Sources */,
+ DAE6C38F1CC31E2A00DB3429 /* MGLOfflineStorage.mm in Sources */,
+ DAE6C3951CC31E2A00DB3429 /* MGLTilePyramidOfflineRegion.mm in Sources */,
+ DAE6C3851CC31E2A00DB3429 /* MGLAccountManager.m in Sources */,
+ DAE6C3921CC31E2A00DB3429 /* MGLPolyline.mm in Sources */,
+ DAE6C3B51CC31EF300DB3429 /* MGLCompassCell.m in Sources */,
+ DAD165751CF4CD7A001FF4B9 /* MGLShapeCollection.m in Sources */,
+ DA35A2AE1CCA091800E826B2 /* MGLCompassDirectionFormatter.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DAE6C32D1CC30DB200DB3429 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DA35A2C21CCA9F4A00E826B2 /* MGLClockDirectionFormatterTests.m in Sources */,
+ DAE6C3D41CC34C9900DB3429 /* MGLOfflineRegionTests.m in Sources */,
+ DAE6C3D61CC34C9900DB3429 /* MGLStyleTests.mm in Sources */,
+ DA35A2B61CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m in Sources */,
+ DAE6C3D21CC34C9900DB3429 /* MGLGeometryTests.mm in Sources */,
+ DAE6C3D51CC34C9900DB3429 /* MGLOfflineStorageTests.m in Sources */,
+ DAE6C3D31CC34C9900DB3429 /* MGLOfflinePackTests.m in Sources */,
+ DA35A2A81CC9F41600E826B2 /* MGLCoordinateFormatterTests.m in Sources */,
+ DA0CD58E1CF56F5800A5F5A5 /* MGLFeatureTests.mm in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ DAE6C3341CC30DB200DB3429 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DAE6C3271CC30DB200DB3429 /* dynamic */;
+ targetProxy = DAE6C3331CC30DB200DB3429 /* PBXContainerItemProxy */;
+ };
+ DAE6C33C1CC30DB200DB3429 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DAE6C3271CC30DB200DB3429 /* dynamic */;
+ targetProxy = DAE6C33B1CC30DB200DB3429 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ DA839E9E1CC2E3400062CAFB /* MapDocument.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DA839E9F1CC2E3400062CAFB /* Base */,
+ );
+ name = MapDocument.xib;
+ sourceTree = "<group>";
+ };
+ DA839EA31CC2E3400062CAFB /* MainMenu.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DA839EA41CC2E3400062CAFB /* Base */,
+ );
+ name = MainMenu.xib;
+ sourceTree = "<group>";
+ };
+ DA8933A71CCD287300E68420 /* MGLAnnotationCallout.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DA8933A61CCD287300E68420 /* Base */,
+ );
+ name = MGLAnnotationCallout.xib;
+ sourceTree = "<group>";
+ };
+ DA8933AB1CCD290700E68420 /* Localizable.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DA8933AC1CCD290700E68420 /* Base */,
+ );
+ name = Localizable.strings;
+ sourceTree = "<group>";
+ };
+ DA8933B31CCD2C2500E68420 /* Foundation.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DA8933B41CCD2C2500E68420 /* Base */,
+ );
+ name = Foundation.strings;
+ sourceTree = "<group>";
+ };
+ DA8933B61CCD2C2D00E68420 /* Foundation.stringsdict */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DA8933B71CCD2C2D00E68420 /* en */,
+ );
+ name = Foundation.stringsdict;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ DA839EA71CC2E3400062CAFB /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ 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_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ 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;
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = macosx;
+ };
+ name = Debug;
+ };
+ DA839EA81CC2E3400062CAFB /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ 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_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ 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;
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = macosx;
+ };
+ name = Release;
+ };
+ DA839EAA1CC2E3400062CAFB /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = "$(SRCROOT)/app/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.MapboxGL;
+ PRODUCT_NAME = "Mapbox GL";
+ };
+ name = Debug;
+ };
+ DA839EAB1CC2E3400062CAFB /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = "$(SRCROOT)/app/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.MapboxGL;
+ PRODUCT_NAME = "Mapbox GL";
+ };
+ name = Release;
+ };
+ DAAA17971CE13BAE00731EFE /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ DEBUGGING_SYMBOLS = YES;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ DAAA17981CE13BAE00731EFE /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
+ DAE6C33F1CC30DB200DB3429 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = DAE6C3C51CC31F9100DB3429 /* mbgl.xcconfig */;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ FRAMEWORK_VERSION = A;
+ HEADER_SEARCH_PATHS = (
+ ../default,
+ ../../include,
+ ../../src,
+ );
+ INFOPLIST_FILE = "$(SRCROOT)/sdk/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
+ OTHER_CPLUSPLUSFLAGS = (
+ "$(OTHER_CFLAGS)",
+ "$(zlib_cflags)",
+ "$(rapidjson_cflags)",
+ "$(variant_cflags)",
+ "$(geometry_cflags)",
+ );
+ OTHER_LDFLAGS = (
+ "$(zlib_ldflags)",
+ "$(opengl_ldflags)",
+ "$(geojsonvt_static_libs)",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.MapboxGL;
+ PRODUCT_NAME = Mapbox;
+ SKIP_INSTALL = YES;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ DAE6C3401CC30DB200DB3429 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = DAE6C3C51CC31F9100DB3429 /* mbgl.xcconfig */;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ FRAMEWORK_VERSION = A;
+ HEADER_SEARCH_PATHS = (
+ ../default,
+ ../../include,
+ ../../src,
+ );
+ INFOPLIST_FILE = "$(SRCROOT)/sdk/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
+ OTHER_CPLUSPLUSFLAGS = (
+ "$(OTHER_CFLAGS)",
+ "$(zlib_cflags)",
+ "$(rapidjson_cflags)",
+ "$(variant_cflags)",
+ "$(geometry_cflags)",
+ );
+ OTHER_LDFLAGS = (
+ "$(zlib_ldflags)",
+ "$(opengl_ldflags)",
+ "$(geojsonvt_static_libs)",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.MapboxGL;
+ PRODUCT_NAME = Mapbox;
+ SKIP_INSTALL = YES;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ DAE6C3411CC30DB200DB3429 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = DAE6C3C51CC31F9100DB3429 /* mbgl.xcconfig */;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ HEADER_SEARCH_PATHS = ../../include;
+ INFOPLIST_FILE = test/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+ OTHER_CPLUSPLUSFLAGS = (
+ "$(OTHER_CFLAGS)",
+ "$(variant_cflags)",
+ "$(geometry_cflags)",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.test;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ DAE6C3421CC30DB200DB3429 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = DAE6C3C51CC31F9100DB3429 /* mbgl.xcconfig */;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ HEADER_SEARCH_PATHS = ../../include;
+ INFOPLIST_FILE = test/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+ OTHER_CPLUSPLUSFLAGS = (
+ "$(OTHER_CFLAGS)",
+ "$(variant_cflags)",
+ "$(geometry_cflags)",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.test;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ DA839E8D1CC2E3400062CAFB /* Build configuration list for PBXProject "macos" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DA839EA71CC2E3400062CAFB /* Debug */,
+ DA839EA81CC2E3400062CAFB /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DA839EA91CC2E3400062CAFB /* Build configuration list for PBXNativeTarget "macosapp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DA839EAA1CC2E3400062CAFB /* Debug */,
+ DA839EAB1CC2E3400062CAFB /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DAAA17991CE13BAE00731EFE /* Build configuration list for PBXLegacyTarget "docs" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DAAA17971CE13BAE00731EFE /* Debug */,
+ DAAA17981CE13BAE00731EFE /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DAE6C3431CC30DB200DB3429 /* Build configuration list for PBXNativeTarget "dynamic" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DAE6C33F1CC30DB200DB3429 /* Debug */,
+ DAE6C3401CC30DB200DB3429 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DAE6C3441CC30DB200DB3429 /* Build configuration list for PBXNativeTarget "test" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DAE6C3411CC30DB200DB3429 /* Debug */,
+ DAE6C3421CC30DB200DB3429 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = DA839E8A1CC2E3400062CAFB /* Project object */;
+}
diff --git a/platform/macos/macos.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/platform/macos/macos.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000000..f2c779de46
--- /dev/null
+++ b/platform/macos/macos.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "self:/Users/mxn/hub/mapbox-gl-native/platform/macos/macos.xcodeproj">
+ </FileRef>
+</Workspace>
diff --git a/platform/macos/macos.xcodeproj/xcshareddata/xcschemes/CI.xcscheme b/platform/macos/macos.xcodeproj/xcshareddata/xcschemes/CI.xcscheme
new file mode 100644
index 0000000000..476532377d
--- /dev/null
+++ b/platform/macos/macos.xcodeproj/xcshareddata/xcschemes/CI.xcscheme
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0730"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "4E8A9455A3A23B7FD2A8FC52"
+ BuildableName = "All"
+ BlueprintName = "All"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DA839E911CC2E3400062CAFB"
+ BuildableName = "Mapbox GL.app"
+ BlueprintName = "macosapp"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "NO"
+ buildForProfiling = "NO"
+ buildForArchiving = "NO"
+ buildForAnalyzing = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DAE6C3301CC30DB200DB3429"
+ BuildableName = "test.xctest"
+ BlueprintName = "test"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DAE6C3301CC30DB200DB3429"
+ BuildableName = "test.xctest"
+ BlueprintName = "test"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DA839E911CC2E3400062CAFB"
+ BuildableName = "Mapbox GL.app"
+ BlueprintName = "macosapp"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DA839E911CC2E3400062CAFB"
+ BuildableName = "Mapbox GL.app"
+ BlueprintName = "macosapp"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DA839E911CC2E3400062CAFB"
+ BuildableName = "Mapbox GL.app"
+ BlueprintName = "macosapp"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/platform/macos/macos.xcodeproj/xcshareddata/xcschemes/dynamic.xcscheme b/platform/macos/macos.xcodeproj/xcshareddata/xcschemes/dynamic.xcscheme
new file mode 100644
index 0000000000..1d1c9de8c5
--- /dev/null
+++ b/platform/macos/macos.xcodeproj/xcshareddata/xcschemes/dynamic.xcscheme
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0730"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DAE6C3271CC30DB200DB3429"
+ BuildableName = "Mapbox.framework"
+ BlueprintName = "dynamic"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "NO"
+ buildForProfiling = "NO"
+ buildForArchiving = "NO"
+ buildForAnalyzing = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DAE6C3301CC30DB200DB3429"
+ BuildableName = "test.xctest"
+ BlueprintName = "test"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DAE6C3301CC30DB200DB3429"
+ BuildableName = "test.xctest"
+ BlueprintName = "test"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DAE6C3271CC30DB200DB3429"
+ BuildableName = "Mapbox.framework"
+ BlueprintName = "dynamic"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DAE6C3271CC30DB200DB3429"
+ BuildableName = "Mapbox.framework"
+ BlueprintName = "dynamic"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DAE6C3271CC30DB200DB3429"
+ BuildableName = "Mapbox.framework"
+ BlueprintName = "dynamic"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/platform/macos/macos.xcodeproj/xcshareddata/xcschemes/macosapp.xcscheme b/platform/macos/macos.xcodeproj/xcshareddata/xcschemes/macosapp.xcscheme
new file mode 100644
index 0000000000..3e3baa8325
--- /dev/null
+++ b/platform/macos/macos.xcodeproj/xcshareddata/xcschemes/macosapp.xcscheme
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0730"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DA839E911CC2E3400062CAFB"
+ BuildableName = "Mapbox GL.app"
+ BlueprintName = "macosapp"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DAE6C3301CC30DB200DB3429"
+ BuildableName = "test.xctest"
+ BlueprintName = "test"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DA839E911CC2E3400062CAFB"
+ BuildableName = "Mapbox GL.app"
+ BlueprintName = "macosapp"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DA839E911CC2E3400062CAFB"
+ BuildableName = "Mapbox GL.app"
+ BlueprintName = "macosapp"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DA839E911CC2E3400062CAFB"
+ BuildableName = "Mapbox GL.app"
+ BlueprintName = "macosapp"
+ ReferencedContainer = "container:macos.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/platform/macos/macos.xcworkspace/contents.xcworkspacedata b/platform/macos/macos.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000000..67a33490e9
--- /dev/null
+++ b/platform/macos/macos.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "container:macos.xcodeproj">
+ </FileRef>
+ <FileRef
+ location = "group:../../build/macos/platform/macos/platform.xcodeproj">
+ </FileRef>
+</Workspace>
diff --git a/platform/macos/macos.xcworkspace/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist b/platform/macos/macos.xcworkspace/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist
new file mode 100644
index 0000000000..cb6ecad738
--- /dev/null
+++ b/platform/macos/macos.xcworkspace/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Bucket
+ type = "3"
+ version = "2.0">
+ <Breakpoints>
+ <BreakpointProxy
+ BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint">
+ <BreakpointContent
+ shouldBeEnabled = "No"
+ ignoreCount = "0"
+ continueAfterRunningActions = "No"
+ scope = "1"
+ stopOnStyle = "0">
+ <Actions>
+ <BreakpointActionProxy
+ ActionExtensionID = "Xcode.BreakpointAction.Sound">
+ <ActionContent
+ soundName = "Basso">
+ </ActionContent>
+ </BreakpointActionProxy>
+ </Actions>
+ </BreakpointContent>
+ </BreakpointProxy>
+ </Breakpoints>
+</Bucket>
diff --git a/platform/macos/macos.xcworkspace/xcshareddata/xcschemes/mbgl-offline.xcscheme b/platform/macos/macos.xcworkspace/xcshareddata/xcschemes/mbgl-offline.xcscheme
new file mode 100644
index 0000000000..90ba9db153
--- /dev/null
+++ b/platform/macos/macos.xcworkspace/xcshareddata/xcschemes/mbgl-offline.xcscheme
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0720"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "960D5BDFBD73C605ACCF58C2"
+ BuildableName = "mbgl-offline"
+ BlueprintName = "mbgl-offline"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "960D5BDFBD73C605ACCF58C2"
+ BuildableName = "mbgl-offline"
+ BlueprintName = "mbgl-offline"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "YES"
+ customWorkingDirectory = "$(PROJECT_DIR)/../.."
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "960D5BDFBD73C605ACCF58C2"
+ BuildableName = "mbgl-offline"
+ BlueprintName = "mbgl-offline"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "960D5BDFBD73C605ACCF58C2"
+ BuildableName = "mbgl-offline"
+ BlueprintName = "mbgl-offline"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/platform/macos/macos.xcworkspace/xcshareddata/xcschemes/mbgl-render.xcscheme b/platform/macos/macos.xcworkspace/xcshareddata/xcschemes/mbgl-render.xcscheme
new file mode 100644
index 0000000000..c176861999
--- /dev/null
+++ b/platform/macos/macos.xcworkspace/xcshareddata/xcschemes/mbgl-render.xcscheme
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0720"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "D27AE71B9B193AD277AD4CFE"
+ BuildableName = "mbgl-render"
+ BlueprintName = "mbgl-render"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "D27AE71B9B193AD277AD4CFE"
+ BuildableName = "mbgl-render"
+ BlueprintName = "mbgl-render"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "YES"
+ customWorkingDirectory = "$(PROJECT_DIR)/../.."
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "D27AE71B9B193AD277AD4CFE"
+ BuildableName = "mbgl-render"
+ BlueprintName = "mbgl-render"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "D27AE71B9B193AD277AD4CFE"
+ BuildableName = "mbgl-render"
+ BlueprintName = "mbgl-render"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/platform/macos/macos.xcworkspace/xcshareddata/xcschemes/test.xcscheme b/platform/macos/macos.xcworkspace/xcshareddata/xcschemes/test.xcscheme
new file mode 100644
index 0000000000..20394eb258
--- /dev/null
+++ b/platform/macos/macos.xcworkspace/xcshareddata/xcschemes/test.xcscheme
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0720"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DC37A7F85AC5E5958672B4F8"
+ BuildableName = "test"
+ BlueprintName = "test"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DC37A7F85AC5E5958672B4F8"
+ BuildableName = "test"
+ BlueprintName = "test"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "YES"
+ customWorkingDirectory = "$(PROJECT_DIR)/../.."
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DC37A7F85AC5E5958672B4F8"
+ BuildableName = "test"
+ BlueprintName = "test"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DC37A7F85AC5E5958672B4F8"
+ BuildableName = "test"
+ BlueprintName = "test"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/platform/macos/platform.gyp b/platform/macos/platform.gyp
new file mode 100644
index 0000000000..68ab4ce467
--- /dev/null
+++ b/platform/macos/platform.gyp
@@ -0,0 +1,104 @@
+{
+ 'variables': {
+ 'loop_lib': 'darwin',
+ 'headless_lib': 'cgl',
+ 'coverage': 0,
+ },
+ 'includes': [
+ '../../build/macos/config.gypi',
+ '../../mbgl.gypi',
+ '../../test/test.gypi',
+ '../../benchmark/benchmark.gypi',
+ '../../bin/glfw.gypi',
+ '../../bin/render.gypi',
+ '../../bin/offline.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'test',
+ 'type': 'executable',
+
+ 'dependencies': [
+ 'test-lib',
+ 'platform-lib',
+ ],
+
+ 'sources': [
+ '../../test/src/main.cpp',
+ ],
+ },
+ {
+ 'target_name': 'benchmark',
+ 'type': 'executable',
+
+ 'dependencies': [
+ 'benchmark-lib',
+ 'platform-lib',
+ ],
+
+ 'sources': [
+ '../../benchmark/src/main.cpp',
+ ],
+ },
+ {
+ 'target_name': 'platform-lib',
+ 'product_name': 'mbgl-platform-macos',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'hard_dependency': 1,
+ 'dependencies': [
+ 'core',
+ ],
+
+ 'include_dirs': [
+ 'include',
+ '../darwin/include',
+ '../default',
+ '../../include',
+ '../../src', # TODO: eliminate
+ '<(SHARED_INTERMEDIATE_DIR)/include',
+ ],
+
+ 'sources': [
+ '../default/asset_file_source.cpp',
+ '../default/default_file_source.cpp',
+ '../default/online_file_source.cpp',
+ '../default/mbgl/storage/offline.hpp',
+ '../default/mbgl/storage/offline.cpp',
+ '../default/mbgl/storage/offline_database.hpp',
+ '../default/mbgl/storage/offline_database.cpp',
+ '../default/mbgl/storage/offline_download.hpp',
+ '../default/mbgl/storage/offline_download.cpp',
+ '../default/sqlite3.hpp',
+ '../default/sqlite3.cpp',
+ '../darwin/src/http_file_source.mm',
+ '../darwin/src/log_nslog.mm',
+ '../darwin/src/string_nsstring.mm',
+ '../darwin/src/image.mm',
+ '../darwin/src/nsthread.mm',
+ '../darwin/src/reachability.m',
+ ],
+
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS': [
+ '<@(sqlite_cflags)',
+ '<@(zlib_cflags)',
+ '<@(rapidjson_cflags)',
+ ],
+ 'CLANG_ENABLE_OBJC_ARC': 'YES',
+ 'CLANG_ENABLE_MODULES': 'YES',
+ },
+
+ 'link_settings': {
+ 'libraries': [
+ '<@(sqlite_static_libs)',
+ '<@(zlib_static_libs)',
+ '$(SDKROOT)/System/Library/Frameworks/Cocoa.framework',
+ ],
+ 'xcode_settings': {
+ 'OTHER_LDFLAGS': [ '<@(zlib_ldflags)' ],
+ },
+ },
+ },
+ ],
+}
diff --git a/platform/macos/screenshot.png b/platform/macos/screenshot.png
new file mode 100644
index 0000000000..3bf0c46ab0
--- /dev/null
+++ b/platform/macos/screenshot.png
Binary files differ
diff --git a/platform/macos/scripts/configure.sh b/platform/macos/scripts/configure.sh
new file mode 100644
index 0000000000..2952ec2535
--- /dev/null
+++ b/platform/macos/scripts/configure.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+UNIQUE_RESOURCE_VERSION=dev
+PROTOZERO_VERSION=1.3.0
+BOOST_VERSION=1.60.0
+BOOST_LIBPROGRAM_OPTIONS_VERSION=1.60.0
+GLFW_VERSION=3.1.2
+SQLITE_VERSION=3.9.1
+ZLIB_VERSION=system
+NUNICODE_VERSION=1.6
+GEOMETRY_VERSION=0.5.0
+GEOJSONVT_VERSION=4.1.2
+VARIANT_VERSION=1.1.0
+RAPIDJSON_VERSION=1.0.2
+GTEST_VERSION=1.7.0
+PIXELMATCH_VERSION=0.9.0
+EARCUT_VERSION=0.11
+BENCHMARK_VERSION=1.0.0
diff --git a/platform/macos/scripts/document.sh b/platform/macos/scripts/document.sh
new file mode 100755
index 0000000000..e62a1e1668
--- /dev/null
+++ b/platform/macos/scripts/document.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+
+set -e
+set -o pipefail
+set -u
+
+if [ -z `which jazzy` ]; then
+ echo "Installing jazzy…"
+ gem install jazzy
+ if [ -z `which jazzy` ]; then
+ echo "Unable to install jazzy. See https://github.com/mapbox/mapbox-gl-native/blob/master/platform/macos/INSTALL.md"
+ exit 1
+ fi
+fi
+
+OUTPUT=${OUTPUT:-documentation}
+
+BRANCH=$( git describe --tags --match=macos-v*.*.* --abbrev=0 )
+SHORT_VERSION=$( echo ${BRANCH} | sed 's/^macos-v//' )
+RELEASE_VERSION=$( echo ${SHORT_VERSION} | sed -e 's/^macos-v//' -e 's/-.*//' )
+
+SWIFT_VERSION=$(xcrun swift -version | head -n 1 | sed -e 's/^Apple Swift version //' -e 's/ .*$//')
+
+rm -rf /tmp/mbgl
+mkdir -p /tmp/mbgl/
+README=/tmp/mbgl/README.md
+cp platform/macos/docs/doc-README.md "${README}"
+# http://stackoverflow.com/a/4858011/4585461
+echo "## Changes in version ${RELEASE_VERSION}" >> "${README}"
+sed -n -e '/^## /{' -e ':a' -e 'n' -e '/^##/q' -e 'p' -e 'ba' -e '}' platform/macos/CHANGELOG.md >> "${README}"
+
+rm -rf ${OUTPUT}
+mkdir -p ${OUTPUT}
+
+jazzy \
+ --config platform/macos/jazzy.yml \
+ --sdk macosx \
+ --swift-version $SWIFT_VERSION \
+ --github-file-prefix https://github.com/mapbox/mapbox-gl-native/tree/${BRANCH} \
+ --module-version ${SHORT_VERSION} \
+ --readme ${README} \
+ --output ${OUTPUT}
+# https://github.com/realm/jazzy/issues/411
+find ${OUTPUT} -name *.html -exec \
+ perl -pi -e 's/Mapbox\s+(Docs|Reference)/Mapbox macOS SDK $1/' {} \;
diff --git a/platform/macos/scripts/macostest.xcscheme b/platform/macos/scripts/macostest.xcscheme
new file mode 100644
index 0000000000..ba6f6a6f4b
--- /dev/null
+++ b/platform/macos/scripts/macostest.xcscheme
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0720"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6EE19CDFBCE7BD04FE561812"
+ BuildableName = "macostest.xctest"
+ BlueprintName = "macostest"
+ ReferencedContainer = "container:../../build/macos/platform/macos/platform.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/platform/macos/scripts/package.sh b/platform/macos/scripts/package.sh
new file mode 100755
index 0000000000..a6952e8ab2
--- /dev/null
+++ b/platform/macos/scripts/package.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+
+set -e
+set -o pipefail
+set -u
+
+NAME=Mapbox
+OUTPUT=build/macos/pkg
+DERIVED_DATA=build/macos
+PRODUCTS=${DERIVED_DATA}/Build/Products
+
+BUILDTYPE=${BUILDTYPE:-Release}
+GCC_GENERATE_DEBUGGING_SYMBOLS=${SYMBOLS:-YES}
+
+function step { >&2 echo -e "\033[1m\033[36m* $@\033[0m"; }
+function finish { >&2 echo -en "\033[0m"; }
+trap finish EXIT
+
+rm -rf ${OUTPUT}
+
+HASH=`git log | head -1 | awk '{ print $2 }' | cut -c 1-10` && true
+PROJ_VERSION=$(git rev-list --count HEAD)
+SEM_VERSION=$( git describe --tags --match=macos-v*.*.* --abbrev=0 | sed 's/^macos-v//' )
+SHORT_VERSION=${SEM_VERSION%-*}
+
+step "Building targets (build ${PROJ_VERSION}, version ${SEM_VERSION})…"
+xcodebuild \
+ GCC_GENERATE_DEBUGGING_SYMBOLS=${GCC_GENERATE_DEBUGGING_SYMBOLS} \
+ CURRENT_PROJECT_VERSION=${PROJ_VERSION} \
+ CURRENT_SHORT_VERSION=${SHORT_VERSION} \
+ CURRENT_SEMANTIC_VERSION=${SEM_VERSION} \
+ CURRENT_COMMIT_HASH=${HASH} \
+ -derivedDataPath ${DERIVED_DATA} \
+ -workspace ./platform/macos/macos.xcworkspace \
+ -scheme dynamic \
+ -configuration ${BUILDTYPE} \
+ -jobs ${JOBS} | xcpretty
+
+step "Copying dynamic framework into place"
+mkdir -p "${OUTPUT}/${NAME}.framework"
+cp -r ${PRODUCTS}/${BUILDTYPE}/${NAME}.framework/* "${OUTPUT}/${NAME}.framework"
+if [[ -e ${PRODUCTS}/${BUILDTYPE}/${NAME}.framework.dSYM ]]; then
+ cp -r ${PRODUCTS}/${BUILDTYPE}/${NAME}.framework.dSYM "${OUTPUT}"
+fi
+
+if [[ "${GCC_GENERATE_DEBUGGING_SYMBOLS}" == false ]]; then
+ step "Stripping binaries…"
+ strip -Sx "${OUTPUT}/${NAME}.framework/${NAME}"
+fi
+
+step "Copying library resources…"
+cp -pv LICENSE.md "${OUTPUT}"
+cp -pv platform/macos/docs/pod-README.md "${OUTPUT}/README.md"
+sed -n -e '/^## /,$p' platform/macos/CHANGELOG.md > "${OUTPUT}/CHANGELOG.md"
+
+step "Generating API documentation…"
+make xdocument OUTPUT="${OUTPUT}/documentation"
diff --git a/platform/macos/sdk/Base.lproj/Localizable.strings b/platform/macos/sdk/Base.lproj/Localizable.strings
new file mode 100644
index 0000000000..818c82b2ec
--- /dev/null
+++ b/platform/macos/sdk/Base.lproj/Localizable.strings
@@ -0,0 +1,30 @@
+/* Linked part of copyright notice */
+"COPYRIGHT_MAPBOX" = "Mapbox";
+
+/* Copyright notice link */
+"COPYRIGHT_MAPBOX_LINK" = "https://www.mapbox.com/about/maps/";
+
+/* Linked part of copyright notice */
+"COPYRIGHT_OSM" = "OpenStreetMap";
+
+/* Copyright notice link */
+"COPYRIGHT_OSM_LINK" = "http://www.openstreetmap.org/about/";
+
+/* Copyright notice prefix */
+"COPYRIGHT_PREFIX" = "© ";
+
+/* Accessibility title */
+"MAP_A11Y_TITLE" = "Mapbox";
+
+/* Label of Zoom In button */
+"ZOOM_IN_LABEL" = "+";
+
+/* Tooltip of Zoom In button */
+"ZOOM_IN_TOOLTIP" = "Zoom In";
+
+/* Label of Zoom Out button; U+2212 MINUS SIGN */
+"ZOOM_OUT_LABEL" = "−";
+
+/* Tooltip of Zoom Out button */
+"ZOOM_OUT_TOOLTIP" = "Zoom Out";
+
diff --git a/platform/macos/sdk/Base.lproj/MGLAnnotationCallout.xib b/platform/macos/sdk/Base.lproj/MGLAnnotationCallout.xib
new file mode 100644
index 0000000000..c8e29bc29e
--- /dev/null
+++ b/platform/macos/sdk/Base.lproj/MGLAnnotationCallout.xib
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="NSViewController">
+ <connections>
+ <outlet property="view" destination="c22-O7-iKe" id="QAM-0O-WIj"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <customView id="c22-O7-iKe">
+ <rect key="frame" x="0.0" y="0.0" width="270" height="50"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <subviews>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k5x-ao-Pz3">
+ <rect key="frame" x="18" y="25" width="234" height="17"/>
+ <textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" title="Title" id="nVE-Zi-KcG">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <attributedString key="userComments">
+ <fragment content="Placeholder for the annotation’s title">
+ <attributes>
+ <font key="NSFont" metaFont="smallSystem"/>
+ <paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
+ </attributes>
+ </fragment>
+ </attributedString>
+ <connections>
+ <binding destination="-2" name="value" keyPath="representedObject.title" id="3nD-YS-gzq"/>
+ </connections>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e9C-Ve-ccw">
+ <rect key="frame" x="18" y="8" width="234" height="14"/>
+ <textFieldCell key="cell" controlSize="small" selectable="YES" sendsActionOnEndEditing="YES" title="Subtitle" id="eKw-tQ-dw8">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <attributedString key="userComments">
+ <fragment content="Placeholder for the annotation’s subtitle">
+ <attributes>
+ <font key="NSFont" metaFont="smallSystem"/>
+ <paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
+ </attributes>
+ </fragment>
+ </attributedString>
+ <connections>
+ <binding destination="-2" name="value" keyPath="representedObject.subtitle" id="RQf-48-DyH"/>
+ </connections>
+ </textField>
+ </subviews>
+ <constraints>
+ <constraint firstItem="e9C-Ve-ccw" firstAttribute="leading" secondItem="k5x-ao-Pz3" secondAttribute="leading" id="ApT-ew-CYb"/>
+ <constraint firstAttribute="bottom" secondItem="e9C-Ve-ccw" secondAttribute="bottom" constant="8" id="CWV-Dd-8oi"/>
+ <constraint firstItem="k5x-ao-Pz3" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" constant="20" id="UUL-GB-Jtv"/>
+ <constraint firstItem="e9C-Ve-ccw" firstAttribute="top" secondItem="k5x-ao-Pz3" secondAttribute="bottom" constant="3" id="Urc-wn-m8X"/>
+ <constraint firstItem="e9C-Ve-ccw" firstAttribute="trailing" secondItem="k5x-ao-Pz3" secondAttribute="trailing" id="gss-6G-9GF"/>
+ <constraint firstAttribute="trailing" secondItem="k5x-ao-Pz3" secondAttribute="trailing" constant="20" id="xCZ-s9-HaP"/>
+ <constraint firstItem="k5x-ao-Pz3" firstAttribute="top" secondItem="c22-O7-iKe" secondAttribute="top" constant="8" id="xcm-oY-jjy"/>
+ </constraints>
+ <point key="canvasLocation" x="257" y="355"/>
+ </customView>
+ </objects>
+</document>
diff --git a/platform/macos/sdk/Info.plist b/platform/macos/sdk/Info.plist
new file mode 100644
index 0000000000..3b2b38a58a
--- /dev/null
+++ b/platform/macos/sdk/Info.plist
@@ -0,0 +1,24 @@
+<?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>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>FMWK</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>$(CURRENT_PROJECT_VERSION)</string>
+</dict>
+</plist>
diff --git a/platform/macos/sdk/default_marker.pdf b/platform/macos/sdk/default_marker.pdf
new file mode 100644
index 0000000000..4e2e332301
--- /dev/null
+++ b/platform/macos/sdk/default_marker.pdf
Binary files differ
diff --git a/platform/macos/sdk/mapbox.pdf b/platform/macos/sdk/mapbox.pdf
new file mode 100644
index 0000000000..c08a0e3135
--- /dev/null
+++ b/platform/macos/sdk/mapbox.pdf
Binary files differ
diff --git a/platform/macos/src/MGLAnnotationImage.h b/platform/macos/src/MGLAnnotationImage.h
new file mode 100644
index 0000000000..ad44993ee1
--- /dev/null
+++ b/platform/macos/src/MGLAnnotationImage.h
@@ -0,0 +1,64 @@
+#import <AppKit/AppKit.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ The `MGLAnnotationImage` class is responsible for presenting point-based
+ annotations visually on an `MGLMapView` instance. Annotation image objects pair
+ `NSImage` objects with annotation-related metadata. They may be recycled later
+ and put into a reuse queue that is maintained by the map view.
+ */
+@interface MGLAnnotationImage : NSObject
+
+#pragma mark Initializing and Preparing the Image Object
+
+/**
+ Initializes and returns a new annotation image object.
+
+ @param image The image to display for the annotation.
+ @param reuseIdentifier The string that identifies this annotation image in the
+ reuse queue.
+ @return The initialized annotation image object or `nil` if there was a problem
+ initializing the object.
+ */
++ (instancetype)annotationImageWithImage:(NSImage *)image reuseIdentifier:(NSString *)reuseIdentifier;
+
+#pragma mark Getting and Setting Attributes
+
+/** The image to display for the annotation. */
+@property (nonatomic, readonly) NSImage *image;
+
+/**
+ The string that identifies this annotation image in the reuse queue.
+ (read-only)
+
+ You specify the reuse identifier when you create the image object. You use this
+ type later to retrieve an annotation image object that was created previously
+ but which is currently unused because its annotation is not on-screen.
+
+ If you define distinctly different types of annotations (with distinctly
+ different annotation images to go with them), you can differentiate between the
+ annotation types by specifying different reuse identifiers for each one.
+ */
+@property (nonatomic, readonly) NSString *reuseIdentifier;
+
+/**
+ A Boolean value indicating whether the annotation is selectable.
+
+ The default value of this property is `YES`. If the value of this property is
+ `NO`, the annotation image ignores click events and cannot be selected.
+ */
+@property (nonatomic, getter=isSelectable) BOOL selectable;
+
+/**
+ The cursor that appears above any annotation using this annotation image.
+
+ By default, this property is set to `nil`, representing the current cursor.
+ */
+@property (nonatomic, nullable) NSCursor *cursor;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/macos/src/MGLAnnotationImage.m b/platform/macos/src/MGLAnnotationImage.m
new file mode 100644
index 0000000000..1b545651d2
--- /dev/null
+++ b/platform/macos/src/MGLAnnotationImage.m
@@ -0,0 +1,26 @@
+#import "MGLAnnotationImage_Private.h"
+
+@interface MGLAnnotationImage ()
+
+@property (nonatomic) NSImage *image;
+@property (nonatomic) NSString *reuseIdentifier;
+@property (nonatomic, strong, nullable) NSString *styleIconIdentifier;
+
+@end
+
+@implementation MGLAnnotationImage
+
++ (instancetype)annotationImageWithImage:(NSImage *)image reuseIdentifier:(NSString *)reuseIdentifier {
+ return [[self alloc] initWithImage:image reuseIdentifier:reuseIdentifier];
+}
+
+- (instancetype)initWithImage:(NSImage *)image reuseIdentifier:(NSString *)reuseIdentifier {
+ if (self = [super init]) {
+ _image = image;
+ _reuseIdentifier = [reuseIdentifier copy];
+ _selectable = YES;
+ }
+ return self;
+}
+
+@end
diff --git a/platform/macos/src/MGLAnnotationImage_Private.h b/platform/macos/src/MGLAnnotationImage_Private.h
new file mode 100644
index 0000000000..21963a86a0
--- /dev/null
+++ b/platform/macos/src/MGLAnnotationImage_Private.h
@@ -0,0 +1,8 @@
+#import <Mapbox/Mapbox.h>
+
+@interface MGLAnnotationImage (Private)
+
+/// Unique identifier of the sprite image used by the style to represent the receiver’s `image`.
+@property (nonatomic, strong, nullable) NSString *styleIconIdentifier;
+
+@end
diff --git a/platform/macos/src/MGLAttributionButton.h b/platform/macos/src/MGLAttributionButton.h
new file mode 100644
index 0000000000..9ff3137849
--- /dev/null
+++ b/platform/macos/src/MGLAttributionButton.h
@@ -0,0 +1,15 @@
+#import <Cocoa/Cocoa.h>
+
+/// Button that looks like a hyperlink and opens a URL.
+@interface MGLAttributionButton : NSButton
+
+/// Returns an `MGLAttributionButton` instance with the given title and URL.
+- (instancetype)initWithTitle:(NSString *)title URL:(NSURL *)url;
+
+/// The URL to open and display as a tooltip.
+@property (nonatomic) NSURL *URL;
+
+/// Opens the URL.
+- (IBAction)openURL:(id)sender;
+
+@end
diff --git a/platform/macos/src/MGLAttributionButton.m b/platform/macos/src/MGLAttributionButton.m
new file mode 100644
index 0000000000..e21b860794
--- /dev/null
+++ b/platform/macos/src/MGLAttributionButton.m
@@ -0,0 +1,50 @@
+#import "MGLAttributionButton.h"
+
+#import "NSBundle+MGLAdditions.h"
+
+@implementation MGLAttributionButton {
+ NSTrackingRectTag _trackingAreaTag;
+}
+
+- (instancetype)initWithTitle:(NSString *)title URL:(NSURL *)url {
+ if (self = [super initWithFrame:NSZeroRect]) {
+ self.bordered = NO;
+ self.bezelStyle = NSRegularSquareBezelStyle;
+
+ // Start with a copyright symbol. The whole string will be mini.
+ NSMutableAttributedString *attributedTitle = [[NSMutableAttributedString alloc] initWithString:NSLocalizedStringWithDefaultValue(@"COPYRIGHT_PREFIX", nil, nil, @"© ", @"Copyright notice prefix") attributes:@{
+ NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]],
+ }];
+ // Append the specified title, underlining it like a hyperlink.
+ [attributedTitle appendAttributedString:
+ [[NSAttributedString alloc] initWithString:title
+ attributes:@{
+ NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]],
+ NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
+ }]];
+ self.attributedTitle = attributedTitle;
+ [self sizeToFit];
+
+ _URL = url;
+ self.toolTip = _URL.absoluteString;
+
+ self.target = self;
+ self.action = @selector(openURL:);
+ }
+ return self;
+}
+
+- (BOOL)wantsLayer {
+ return YES;
+}
+
+- (void)resetCursorRects {
+ // The whole button gets a pointing hand cursor, just like a hyperlink.
+ [self addCursorRect:self.bounds cursor:[NSCursor pointingHandCursor]];
+}
+
+- (IBAction)openURL:(__unused id)sender {
+ [[NSWorkspace sharedWorkspace] openURL:self.URL];
+}
+
+@end
diff --git a/platform/macos/src/MGLCompassCell.h b/platform/macos/src/MGLCompassCell.h
new file mode 100644
index 0000000000..5ed70dcb06
--- /dev/null
+++ b/platform/macos/src/MGLCompassCell.h
@@ -0,0 +1,5 @@
+#import <Cocoa/Cocoa.h>
+
+/// Circular slider with an arrow pointing north.
+@interface MGLCompassCell : NSSliderCell
+@end
diff --git a/platform/macos/src/MGLCompassCell.m b/platform/macos/src/MGLCompassCell.m
new file mode 100644
index 0000000000..b3a4ad4544
--- /dev/null
+++ b/platform/macos/src/MGLCompassCell.m
@@ -0,0 +1,34 @@
+#import "MGLCompassCell.h"
+
+@implementation MGLCompassCell
+
+- (instancetype)init {
+ if (self = [super init]) {
+ self.sliderType = NSCircularSlider;
+ // A tick mark for each cardinal direction.
+ self.numberOfTickMarks = 4;
+ // This slider goes backwards!
+ self.minValue = -360;
+ self.maxValue = 0;
+ }
+ return self;
+}
+
+- (void)drawKnob:(NSRect)knobRect {
+ // Draw a red triangle pointing whichever way the slider is facing.
+ NSBezierPath *trianglePath = [NSBezierPath bezierPath];
+ [trianglePath moveToPoint:NSMakePoint(NSMinX(knobRect), NSMaxY(knobRect))];
+ [trianglePath lineToPoint:NSMakePoint(NSMaxX(knobRect), NSMaxY(knobRect))];
+ [trianglePath lineToPoint:NSMakePoint(NSMidX(knobRect), NSMinY(knobRect))];
+ [trianglePath closePath];
+ NSAffineTransform *transform = [NSAffineTransform transform];
+ [transform translateXBy:NSMidX(knobRect) yBy:NSMidY(knobRect)];
+ [transform scaleBy:0.8];
+ [transform rotateByDegrees:self.doubleValue];
+ [transform translateXBy:-NSMidX(knobRect) yBy:-NSMidY(knobRect)];
+ [trianglePath transformUsingAffineTransform:transform];
+ [[NSColor redColor] setFill];
+ [trianglePath fill];
+}
+
+@end
diff --git a/platform/macos/src/MGLMapView+IBAdditions.h b/platform/macos/src/MGLMapView+IBAdditions.h
new file mode 100644
index 0000000000..81f4506a57
--- /dev/null
+++ b/platform/macos/src/MGLMapView+IBAdditions.h
@@ -0,0 +1,68 @@
+#import <Foundation/Foundation.h>
+
+#import "MGLMapView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MGLMapView (IBAdditions)
+
+#if TARGET_INTERFACE_BUILDER
+
+// Core properties that can be manipulated in the Attributes inspector in
+// Interface Builder. These redeclarations merely add the IBInspectable keyword.
+// They appear here to ensure that they appear above the convenience properties;
+// inspectables declared in MGLMapView.h are always sorted before those in
+// MGLMapView+IBAdditions.h, due to ASCII sort order.
+
+// We want this property to look like a URL bar in the Attributes inspector, but
+// just calling it styleURL would violate Cocoa naming conventions and conflict
+// with the existing NSURL property. Fortunately, IB strips out the two
+// underscores for display.
+
+/** URL of the style currently displayed in the receiver.
+
+ The URL may be a full HTTP or HTTPS URL, a Mapbox URL indicating the style’s
+ map ID (`mapbox://styles/<user>/<style>`), or a path to a local file
+ relative to the application’s resource path. Leave this field blank for the
+ default style. */
+@property (nonatomic, nullable) IBInspectable NSString *styleURL__;
+
+// Convenience properties related to the initial viewport. These properties
+// are not meant to be used outside of Interface Builder. latitude and longitude
+// are backed by properties of type CLLocationDegrees, but these declarations
+// must use the type double because Interface Builder is unaware that
+// CLLocationDegrees is a typedef for double.
+
+/** The initial center latitude. */
+@property (nonatomic) IBInspectable double latitude;
+
+/** The initial center longitude. */
+@property (nonatomic) IBInspectable double longitude;
+
+@property (nonatomic) IBInspectable double zoomLevel;
+
+// Renamed properties. Interface Builder derives the display name of each
+// inspectable from the runtime name, but runtime names don’t always make sense
+// in UI.
+
+/** A Boolean value that determines whether the user may zoom the map, changing
+ its zoom level. */
+@property (nonatomic) IBInspectable BOOL allowsZooming;
+
+/** A Boolean value that determines whether the user may scroll around the map,
+ changing its center coordinate. */
+@property (nonatomic) IBInspectable BOOL allowsScrolling;
+
+/** A Boolean value that determines whether the user may rotate the map,
+ changing its direction. */
+@property (nonatomic) IBInspectable BOOL allowsRotating;
+
+/** A Boolean value that determines whether the user may tilt the map, changing
+ its pitch. */
+@property (nonatomic) IBInspectable BOOL allowsTilting;
+
+#endif
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/macos/src/MGLMapView+IBAdditions.m b/platform/macos/src/MGLMapView+IBAdditions.m
new file mode 100644
index 0000000000..eada47ef90
--- /dev/null
+++ b/platform/macos/src/MGLMapView+IBAdditions.m
@@ -0,0 +1,118 @@
+#import "MGLMapView+IBAdditions.h"
+
+#import "MGLMapView_Private.h"
+
+@implementation MGLMapView (IBAdditions)
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingStyleURL__ {
+ return [NSSet setWithObject:@"styleURL"];
+}
+
+- (nullable NSString *)styleURL__ {
+ return self.styleURL.absoluteString;
+}
+
+- (void)setStyleURL__:(nullable NSString *)URLString {
+ URLString = [URLString stringByTrimmingCharactersInSet:
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ NSURL *url = URLString.length ? [NSURL URLWithString:URLString] : nil;
+ if (URLString.length && !url) {
+ [NSException raise:@"Invalid style URL"
+ format:@"“%@” is not a valid style URL.", URLString];
+ }
+ self.styleURL = url;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLatitude {
+ return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil];
+}
+
+- (double)latitude {
+ return self.centerCoordinate.latitude;
+}
+
+- (void)setLatitude:(double)latitude {
+ if (!isnan(self.pendingLongitude)) {
+ // With both components present, set the real center coordinate and
+ // forget the pending parts.
+ self.centerCoordinate = CLLocationCoordinate2DMake(latitude, self.pendingLongitude);
+ self.pendingLatitude = NAN;
+ self.pendingLongitude = NAN;
+ } else {
+ // Not enough info to make a valid center coordinate yet. Stash this
+ // latitude away until the longitude is set too.
+ self.pendingLatitude = latitude;
+ }
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLongitude {
+ return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil];
+}
+
+- (double)longitude {
+ return self.centerCoordinate.longitude;
+}
+
+- (void)setLongitude:(double)longitude {
+ if (!isnan(self.pendingLatitude)) {
+ // With both components present, set the real center coordinate and
+ // forget the pending parts.
+ self.centerCoordinate = CLLocationCoordinate2DMake(self.pendingLatitude, longitude);
+ self.pendingLatitude = NAN;
+ self.pendingLongitude = NAN;
+ } else {
+ // Not enough info to make a valid center coordinate yet. Stash this
+ // longitude away until the latitude is set too.
+ self.pendingLongitude = longitude;
+ }
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingAllowsZooming {
+ return [NSSet setWithObject:@"zoomEnabled"];
+}
+
+- (BOOL)allowsZooming {
+ return self.zoomEnabled;
+}
+
+- (void)setAllowsZooming:(BOOL)allowsZooming {
+ self.zoomEnabled = allowsZooming;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingAllowsScrolling {
+ return [NSSet setWithObject:@"scrollEnabled"];
+}
+
+- (BOOL)allowsScrolling {
+ return self.scrollEnabled;
+}
+
+- (void)setAllowsScrolling:(BOOL)allowsScrolling {
+ self.scrollEnabled = allowsScrolling;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingAllowsRotating {
+ return [NSSet setWithObject:@"rotateEnabled"];
+}
+
+- (BOOL)allowsRotating {
+ return self.rotateEnabled;
+}
+
+- (void)setAllowsRotating:(BOOL)allowsRotating {
+ self.rotateEnabled = allowsRotating;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingAllowsTilting {
+ return [NSSet setWithObject:@"pitchEnabled"];
+}
+
+- (BOOL)allowsTilting {
+ return self.pitchEnabled;
+}
+
+- (void)setAllowsTilting:(BOOL)allowsTilting {
+ self.pitchEnabled = allowsTilting;
+}
+
+@end
diff --git a/platform/macos/src/MGLMapView.h b/platform/macos/src/MGLMapView.h
new file mode 100644
index 0000000000..7b3efd293b
--- /dev/null
+++ b/platform/macos/src/MGLMapView.h
@@ -0,0 +1,903 @@
+#import <Cocoa/Cocoa.h>
+#import <CoreLocation/CoreLocation.h>
+
+#import "MGLGeometry.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** Options for enabling debugging features in an MGLMapView instance. */
+typedef NS_OPTIONS(NSUInteger, MGLMapDebugMaskOptions) {
+ /** Edges of tile boundaries are shown as thick, red lines to help diagnose
+ tile clipping issues. */
+ MGLMapDebugTileBoundariesMask = 1 << 1,
+
+ /** Each tile shows its tile coordinate (x/y/z) in the upper-left corner. */
+ MGLMapDebugTileInfoMask = 1 << 2,
+
+ /** Each tile shows a timestamp indicating when it was loaded. */
+ MGLMapDebugTimestampsMask = 1 << 3,
+
+ /** Edges of glyphs and symbols are shown as faint, green lines to help
+ diagnose collision and label placement issues. */
+ MGLMapDebugCollisionBoxesMask = 1 << 4,
+
+ /** Line widths, backgrounds, and fill colors are ignored to create a
+ wireframe effect. */
+ MGLMapDebugWireframesMask = 1 << 5,
+
+ /** The stencil buffer is shown instead of the color buffer. */
+ MGLMapDebugStencilBufferMask = 1 << 6,
+};
+
+@class MGLAnnotationImage;
+@class MGLMapCamera;
+
+@protocol MGLAnnotation;
+@protocol MGLMapViewDelegate;
+@protocol MGLOverlay;
+@protocol MGLFeature;
+
+/**
+ An interactive, customizable map view with an interface similar to the one
+ provided by Apple’s MapKit.
+
+ Using `MGLMapView`, you can embed the map inside a view, allow users to
+ manipulate it with standard gestures, animate the map between different
+ viewpoints, and present information in the form of annotations and overlays.
+
+ The map view loads scalable vector tiles that conform to the
+ <a href="https://github.com/mapbox/vector-tile-spec">Mapbox Vector Tile Specification</a>.
+ It styles them with a style that conforms to the
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/">Mapbox GL style specification</a>.
+ Such styles can be designed in
+ <a href="https://www.mapbox.com/studio/">Mapbox Studio</a> and hosted on
+ mapbox.com.
+
+ A collection of Mapbox-hosted styles is available through the `MGLStyle` class.
+ These basic styles use
+ <a href="https://www.mapbox.com/developers/vector-tiles/mapbox-streets">Mapbox Streets</a>
+ or <a href="https://www.mapbox.com/satellite/">Mapbox Satellite</a> data
+ sources, but you can specify a custom style that makes use of your own data.
+
+ Mapbox-hosted vector tiles and styles require an API access token, which you
+ can obtain from the
+ <a href="https://www.mapbox.com/studio/account/tokens/">Mapbox account page</a>.
+ Access tokens associate requests to Mapbox’s vector tile and style APIs with
+ your Mapbox account. They also deter other developers from using your styles
+ without your permission.
+
+ @note You are responsible for getting permission to use the map data and for
+ ensuring that your use adheres to the relevant terms of use.
+ */
+IB_DESIGNABLE
+@interface MGLMapView : NSView
+
+#pragma mark Creating Instances
+
+/**
+ Initializes and returns a newly allocated map view with the specified frame and
+ the default style.
+
+ @param frame The frame for the view, measured in points.
+ @return An initialized map view.
+ */
+- (instancetype)initWithFrame:(NSRect)frame;
+
+/**
+ Initializes and returns a newly allocated map view with the specified frame and
+ style URL.
+
+ @param frame The frame for the view, measured in points.
+ @param styleURL URL of the map style to display. The URL may be a full HTTP or
+ HTTPS URL, a Mapbox URL indicating the style’s map ID
+ (`mapbox://styles/<user>/<style>`), or a path to a local file relative to
+ the application’s resource path. Specify `nil` for the default style.
+ @return An initialized map view.
+ */
+- (instancetype)initWithFrame:(NSRect)frame styleURL:(nullable NSURL *)styleURL;
+
+#pragma mark Accessing the Delegate
+
+/**
+ The receiver’s delegate.
+
+ A map view sends messages to its delegate to notify it of changes to its
+ contents or the viewpoint. The delegate also provides information about
+ annotations displayed on the map, such as the styles to apply to individual
+ annotations.
+ */
+@property (nonatomic, weak, nullable) IBOutlet id <MGLMapViewDelegate> delegate;
+
+#pragma mark Configuring the Map’s Appearance
+
+/**
+ URL of the style currently displayed in the receiver.
+
+ The URL may be a full HTTP or HTTPS URL, a Mapbox URL indicating the style’s
+ map ID (`mapbox://styles/<user>/<style>`), or a path to a local file relative
+ to the application’s resource path.
+
+ If you set this property to `nil`, the receiver will use the default style and
+ this property will automatically be set to that style’s URL.
+ */
+@property (nonatomic, null_resettable) NSURL *styleURL;
+
+/**
+ Reloads the style.
+
+ You do not normally need to call this method. The map view automatically
+ responds to changes in network connectivity by reloading the style. You may
+ need to call this method if you change the access token after a style has
+ loaded but before loading a style associated with a different Mapbox account.
+ */
+- (IBAction)reloadStyle:(id)sender;
+
+/**
+ A control for zooming in and out, positioned in the lower-right corner.
+ */
+@property (nonatomic, readonly) NSSegmentedControl *zoomControls;
+
+/**
+ A control indicating the map’s direction and allowing the user to manipulate
+ the direction, positioned above the zoom controls in the lower-right corner.
+ */
+@property (nonatomic, readonly) NSSlider *compass;
+
+/**
+ The Mapbox logo, positioned in the lower-left corner.
+
+ @note The Mapbox terms of service, which governs the use of Mapbox-hosted
+ vector tiles and styles,
+ <a href="https://www.mapbox.com/help/mapbox-logo/">requires</a> most Mapbox
+ customers to display the Mapbox logo. If this applies to you, do not hide
+ this view or change its contents.
+ */
+@property (nonatomic, readonly) NSImageView *logoView;
+
+/**
+ A view showing legally required copyright notices, positioned along the bottom
+ of the map view, to the left of the Mapbox logo.
+
+ @note The Mapbox terms of service, which governs the use of Mapbox-hosted
+ vector tiles and styles,
+ <a href="https://www.mapbox.com/help/attribution/">requires</a> these
+ copyright notices to accompany any map that features Mapbox-designed styles,
+ OpenStreetMap data, or other Mapbox data such as satellite or terrain data.
+ If that applies to this map view, do not hide this view or remove any
+ notices from it.
+ */
+@property (nonatomic, readonly) NSView *attributionView;
+
+#pragma mark Manipulating the Viewpoint
+
+/**
+ The geographic coordinate at the center of the map view.
+
+ Changing the value of 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.
+
+ Changing the center coordinate centers the map on the new coordinate without
+ changing the current zoom level.
+
+ @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.
+ */
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated;
+
+/**
+ The zoom level of the receiver.
+
+ In addition to affecting the visual size and detail of features on the map, the
+ zoom level affects the size of the vector tiles that are loaded. At zoom level
+ 0, each tile covers the entire world map; at zoom level 1, it covers ¼ of the
+ world; at zoom level 2, <sup>1</sup>⁄<sub>16</sub> 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;
+
+/**
+ The minimum zoom level at which the map can be shown.
+
+ Depending on the map view’s aspect ratio, the map view may be prevented from
+ reaching the minimum zoom level, in order to keep the map from repeating within
+ the current viewport.
+
+ If the value of this property is greater than that of the `maximumZoomLevel`
+ property, the behavior is undefined.
+
+ The default value of this property is 0.
+ */
+@property (nonatomic) double minimumZoomLevel;
+
+/**
+ The maximum zoom level the map can be shown at.
+
+ If the value of this property is smaller than that of the `minimumZoomLevel`
+ property, the behavior is undefined.
+
+ The default value of this property is 20.
+ */
+@property (nonatomic) double maximumZoomLevel;
+
+/**
+ Changes the zoom level of the map and optionally animates the change.
+
+ Changing the zoom level scales the map without changing the current center
+ coordinate.
+
+ @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.
+ */
+- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated;
+
+/**
+ The heading of the map, measured in degrees clockwise from 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.
+
+ Changing the value of this property updates the map view immediately. If you
+ want to animate the change, use the `-setDirection:animated:` method instead.
+ */
+@property (nonatomic) CLLocationDirection direction;
+
+/**
+ Changes the heading of the map and optionally animates the change.
+
+ Changing the heading rotates the map without changing the current center
+ coordinate or zoom level.
+
+ @param direction The heading of the map, measured in degrees clockwise from
+ 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.
+ */
+- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated;
+
+/**
+ A camera representing the current viewpoint of the map.
+ */
+@property (nonatomic, copy) MGLMapCamera *camera;
+
+/**
+ Moves the viewpoint to a different location with respect to the map with an
+ optional transition animation.
+
+ @param camera The new viewpoint.
+ @param animated Specify `YES` if you want the map view to animate the change to
+ the new viewpoint or `NO` if you want the map to display the new viewpoint
+ immediately.
+ */
+- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated;
+
+/**
+ Moves the viewpoint to a different location with respect to the map with an
+ optional transition duration and timing function.
+
+ @param camera The new viewpoint.
+ @param duration The amount of time, measured in seconds, that the transition
+ animation should take. Specify `0` to jump to the new viewpoint
+ instantaneously.
+ @param function A timing function used for the animation. Set this parameter to
+ `nil` for a transition that matches most system animations. If the duration
+ is `0`, this parameter is ignored.
+ @param completion The block to execute after the animation finishes.
+ */
+- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion;
+
+/**
+ Moves the viewpoint to a different location using a transition animation that
+ evokes powered flight and a default duration based on the length of the flight
+ path.
+
+ The transition animation seamlessly incorporates zooming and panning to help
+ the user find his or her bearings even after traversing a great distance.
+
+ @param camera The new viewpoint.
+ @param completion The block to execute after the animation finishes.
+ */
+- (void)flyToCamera:(MGLMapCamera *)camera completionHandler:(nullable void (^)(void))completion;
+
+/**
+ Moves the viewpoint to a different location using a transition animation that
+ evokes powered flight and an optional transition duration.
+
+ The transition animation seamlessly incorporates zooming and panning to help
+ the user find his or her bearings even after traversing a great distance.
+
+ @param camera The new viewpoint.
+ @param duration The amount of time, measured in seconds, that the transition
+ animation should take. Specify `0` to jump to the new viewpoint
+ instantaneously. Specify a negative value to use the default duration, which
+ is based on the length of the flight path.
+ @param completion The block to execute after the animation finishes.
+ */
+- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration completionHandler:(nullable void (^)(void))completion;
+
+/**
+ Moves the viewpoint to a different location using a transition animation that
+ evokes powered flight and an optional transition duration and peak altitude.
+
+ The transition animation seamlessly incorporates zooming and panning to help
+ the user find his or her bearings even after traversing a great distance.
+
+ @param camera The new viewpoint.
+ @param duration The amount of time, measured in seconds, that the transition
+ animation should take. Specify `0` to jump to the new viewpoint
+ instantaneously. Specify a negative value to use the default duration, which
+ is based on the length of the flight path.
+ @param peakAltitude The altitude, measured in meters, at the midpoint of the
+ animation. The value of this parameter is ignored if it is negative or if
+ the animation transition resulting from a similar call to
+ `-setCamera:animated:` would have a midpoint at a higher altitude.
+ @param completion The block to execute after the animation finishes.
+ */
+- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion;
+
+/**
+ The geographic coordinate bounds visible in the receiver’s viewport.
+
+ Changing the value of this property updates the receiver immediately. If you
+ want to animate the change, use the `-setVisibleCoordinateBounds:animated:`
+ method instead.
+ */
+@property (nonatomic) MGLCoordinateBounds visibleCoordinateBounds;
+
+/**
+ Changes the receiver’s viewport to fit the given coordinate bounds, optionally
+ animating the change.
+
+ @param bounds The bounds that the viewport will show in its entirety.
+ @param animated Specify `YES` to animate the change by smoothly scrolling and
+ zooming or `NO` to immediately display the given bounds.
+ */
+- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds animated:(BOOL)animated;
+
+/**
+ Changes the receiver’s viewport to fit the given coordinate bounds and
+ optionally some additional padding on each side.
+
+ @param bounds The bounds that the viewport will show in its entirety.
+ @param insets The minimum padding (in screen points) that will be visible
+ around the given coordinate bounds.
+ @param animated Specify `YES` to animate the change by smoothly scrolling and
+ zooming or `NO` to immediately display the given bounds.
+ */
+- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated;
+
+/**
+ Returns the camera that best fits the given coordinate bounds.
+
+ @param bounds The coordinate bounds to fit to the receiver’s viewport.
+ @return A camera object centered on the same location as the coordinate bounds
+ with zoom level as high (close to the ground) as possible while still
+ including the entire coordinate bounds. The camera object uses the current
+ direction and pitch.
+ */
+- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds;
+
+/**
+ Returns the camera that best fits the given coordinate bounds, optionally with
+ some additional padding on each side.
+
+ @param bounds The coordinate bounds to fit to the receiver’s viewport.
+ @param insets The minimum padding (in screen points) that would be visible
+ around the returned camera object if it were set as the receiver’s camera.
+ @return A camera object centered on the same location as the coordinate bounds
+ with zoom level as high (close to the ground) as possible while still
+ including the entire coordinate bounds. The camera object uses the current
+ direction and pitch.
+ */
+- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets;
+
+/**
+ A Boolean value indicating whether the receiver automatically adjusts its
+ content insets.
+
+ When the value of this property is `YES`, the map view automatically updates
+ its `contentInsets` property to account for any overlapping title bar or
+ toolbar. To overlap with the title bar or toolbar, the containing window’s
+ style mask must have `NSFullSizeContentViewWindowMask` set, and the title bar
+ must not be transparent.
+
+ The default value of this property is `YES`.
+ */
+@property (nonatomic, assign) BOOL automaticallyAdjustsContentInsets;
+
+/**
+ The distance from the edges of the map view’s frame to the edges of the map
+ view’s logical viewport.
+
+ When the value of this property is equal to `NSEdgeInsetsZero`, viewport
+ properties such as `centerCoordinate` assume a viewport that matches the map
+ view’s frame. Otherwise, those properties are inset, excluding part of the
+ frame from the viewport. For instance, if the only the top edge is inset, the
+ map center is effectively shifted downward.
+
+ When the value of the `automaticallyAdjustsContentInsets` property is `YES`,
+ the value of this property may be overridden at any time.
+
+ Changing the value of this property updates the map view immediately. If you
+ want to animate the change, use the `-setContentInsets:animated:` method
+ instead.
+ */
+@property (nonatomic, assign) NSEdgeInsets contentInsets;
+
+/**
+ Sets the distance from the edges of the map view’s frame to the edges of the
+ map view’s logical viewport, with an optional transition animation.
+
+ When the value of this property is equal to `NSEdgeInsetsZero`, viewport
+ properties such as `centerCoordinate` assume a viewport that matches the map
+ view’s frame. Otherwise, those properties are inset, excluding part of the
+ frame from the viewport. For instance, if the only the top edge is inset, the
+ map center is effectively shifted downward.
+
+ When the value of the `automaticallyAdjustsContentInsets` property is `YES`,
+ the value of this property may be overridden at any time.
+
+ @param contentInsets The new values to inset the content by.
+ @param animated Specify `YES` if you want the map view to animate the change to
+ the content insets or `NO` if you want the map to inset the content
+ immediately.
+ */
+- (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated;
+
+#pragma mark Configuring How the User Interacts with the Map
+
+/**
+ A Boolean value that determines whether the user may zoom the map in and out,
+ changing the zoom level.
+
+ When this property is set to `YES`, the default, the user may zoom the map in
+ and out by pinching two fingers, by using a scroll wheel on a traditional
+ mouse, or by dragging the mouse cursor up and down while holding down the Shift
+ key. When the receiver has focus, the user may also zoom by pressing the up and
+ down arrow keys while holding down the Option key.
+
+ 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.
+ */
+@property (nonatomic, getter=isZoomEnabled) BOOL zoomEnabled;
+
+/**
+ A Boolean value that determines whether the user may scroll around the map,
+ changing the center coordinate.
+
+ When this property is set to `YES`, the default, the user may scroll the map by
+ swiping with two fingers or dragging the mouse cursor. When the receiver has
+ focus, the user may also scroll around the map by pressing the arrow keys.
+
+ 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.
+ */
+@property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
+
+/**
+ A Boolean value that determines whether the user may rotate the map, changing
+ the direction.
+
+ When this property is set to `YES`, the default, the user may rotate the map by
+ moving two fingers in a circular motion or by dragging the mouse cursor left
+ and right while holding down the Option key. When the receiver has focus, the
+ user may also zoom by pressing the left and right arrow keys while holding down
+ the Option key.
+
+ 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.
+ */
+@property (nonatomic, getter=isRotateEnabled) BOOL rotateEnabled;
+
+/**
+ A Boolean value that determines whether the user may tilt of the map, changing
+ the pitch.
+
+ When this property is set to `YES`, the default, the user may rotate the map by
+ dragging the mouse cursor up and down while holding down the Option key.
+
+ This property controls only user interactions with the map. If you set the
+ value of this property to `NO`, you may still change the pitch of the map
+ programmatically.
+ */
+@property (nonatomic, getter=isPitchEnabled) BOOL pitchEnabled;
+
+#pragma mark Annotating the Map
+
+/**
+ The complete list of annotations associated with the receiver. (read-only)
+
+ The objects in this array must adopt the `MGLAnnotation` protocol. If no
+ annotations are associated with the map view, the value of this property is
+ `nil`.
+ */
+@property (nonatomic, readonly, nullable) NS_ARRAY_OF(id <MGLAnnotation>) *annotations;
+
+/**
+ Adds an annotation to the map view.
+
+ @note `MGLMultiPolyline`, `MGLMultiPolygon`, and `MGLShapeCollection` objects
+ cannot be added to the map view at this time. Nor can `MGLMultiPoint`
+ objects that are not instances of `MGLPolyline` or `MGLPolygon`. Any
+ multipoint, multipolyline, multipolygon, or shape collection object that is
+ specified is silently ignored.
+
+ @param annotation The annotation object to add to the receiver. This object
+ must conform to the `MGLAnnotation` protocol. The map view retains the
+ annotation object.
+ */
+- (void)addAnnotation:(id <MGLAnnotation>)annotation;
+
+/**
+ Adds an array of annotations to the map view.
+
+ @note `MGLMultiPolyline`, `MGLMultiPolygon`, and `MGLShapeCollection` objects
+ cannot be added to the map view at this time. Nor can `MGLMultiPoint`
+ objects that are not instances of `MGLPolyline` or `MGLPolygon`. Any
+ multipoint, multipolyline, multipolygon, or shape collection objects that
+ are specified are silently ignored.
+
+ @param annotations An array of annotation objects. Each object in the array
+ must conform to the `MGLAnnotation` protocol. The map view retains each
+ individual annotation object.
+ */
+- (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations;
+
+/**
+ Removes an annotation from the map view, deselecting it if it is selected.
+
+ Removing an annotation object dissociates it from the map view entirely,
+ preventing it from being displayed on the map. Thus you would typically call
+ this method only when you want to hide or delete a given annotation.
+
+ @param annotation The annotation object to remove. This object must conform to
+ the `MGLAnnotation` protocol.
+ */
+- (void)removeAnnotation:(id <MGLAnnotation>)annotation;
+
+/**
+ Removes an array of annotations from the map view, deselecting any selected
+ annotations in the array.
+
+ Removing annotation objects dissociates them from the map view entirely,
+ preventing them from being displayed on the map. Thus you would typically call
+ this method only when you want to hide or delete the given annotations.
+
+ @param annotations The array of annotation objects to remove. Objects in the
+ array must conform to the `MGLAnnotation` protocol.
+ */
+- (void)removeAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations;
+
+/**
+ Returns a reusable annotation image object associated with its identifier.
+
+ For performance reasons, you should generally reuse `MGLAnnotationImage`
+ objects for identical-looking annotations in your map views. Dequeueing saves
+ time and memory during performance-critical operations such as scrolling.
+
+ @param identifier A string identifying the annotation image to be reused. This
+ string is the same one you specify when initially returning the annotation
+ image object using the `-mapView:imageForAnnotation:` method.
+ @return An annotation image object with the given identifier, or `nil` if no
+ such object exists in the reuse queue.
+ */
+- (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier;
+
+#pragma mark Managing Annotation Selections
+
+/**
+ The currently selected annotations.
+
+ Assigning a new array to this property selects only the first annotation in the
+ array.
+ */
+@property (nonatomic, copy) NS_ARRAY_OF(id <MGLAnnotation>) *selectedAnnotations;
+
+/**
+ Selects an annotation and displays a callout popover for it.
+
+ If the given annotation is not visible within the current viewport, this method
+ has no effect.
+
+ @param annotation The annotation object to select.
+ */
+- (void)selectAnnotation:(id <MGLAnnotation>)annotation;
+
+/**
+ Deselects an annotation and hides its callout popover.
+
+ @param annotation The annotation object to deselect.
+ */
+- (void)deselectAnnotation:(nullable id <MGLAnnotation>)annotation;
+
+/**
+ A common view controller for managing a callout popover’s content view.
+
+ Like any instance of `NSPopover`, an annotation callout manages its contents
+ with a view controller. The annotation object is the view controller’s
+ represented object. This means that you can bind controls in the view
+ controller’s content view to KVO-compliant properties of the annotation object,
+ such as `title` and `subtitle`.
+
+ This property defines a common view controller that is used for every
+ annotation’s callout view. If you set this property to `nil`, a default view
+ controller will be used that manages a simple title label and subtitle label.
+ If you need distinct view controllers for different annotations, the map view’s
+ delegate should implement `-mapView:calloutViewControllerForAnnotation:`
+ instead.
+ */
+@property (nonatomic, strong, null_resettable) IBOutlet NSViewController *calloutViewController;
+
+#pragma mark Finding Annotations
+
+/**
+ Returns a point annotation located at the given point.
+
+ @param point A point in the view’s coordinate system.
+ @return A point annotation whose annotation image coincides with the point. If
+ multiple point annotations coincide with the point, the return value is the
+ annotation that would be selected if the user clicks at this point.
+ */
+- (id <MGLAnnotation>)annotationAtPoint:(NSPoint)point;
+
+#pragma mark Overlaying the Map
+
+/**
+ Adds a single overlay to the map.
+
+ To remove an overlay from a map, use the `-removeOverlay:` method.
+
+ @param overlay The overlay object to add. This object must conform to the
+ `MGLOverlay` protocol.
+ */
+- (void)addOverlay:(id <MGLOverlay>)overlay;
+
+/**
+ Adds an array of overlays to the map.
+
+ To remove multiple overlays from a map, use the `-removeOverlays:` method.
+
+ @param overlays An array of objects, each of which must conform to the
+ `MGLOverlay` protocol.
+ */
+- (void)addOverlays:(NS_ARRAY_OF(id <MGLOverlay>) *)overlays;
+
+/**
+ Removes a single overlay from the map.
+
+ If the specified overlay is not currently associated with the map view, this
+ method does nothing.
+
+ @param overlay The overlay object to remove.
+ */
+- (void)removeOverlay:(id <MGLOverlay>)overlay;
+
+/**
+ Removes an array of overlays from the map.
+
+ If a given overlay object is not associated with the map view, it is ignored.
+
+ @param overlays An array of objects, each of which conforms to the `MGLOverlay`
+ protocol.
+ */
+- (void)removeOverlays:(NS_ARRAY_OF(id <MGLOverlay>) *)overlays;
+
+#pragma mark Accessing the Underlying Map Data
+
+/**
+ Returns an array of rendered map features that intersect with a given point.
+
+ This method may return features from any of the map’s style layers. To restrict
+ the search to a particular layer or layers, use the
+ `-visibleFeaturesAtPoint:inStyleLayersWithIdentifiers:` method. For more
+ information about searching for map features, see that method’s documentation.
+
+ @param point A point expressed in the map view’s coordinate system.
+ @return An array of objects conforming to the `MGLFeature` protocol that
+ represent features in the sources used by the current style.
+ */
+- (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesAtPoint:(NSPoint)point NS_SWIFT_NAME(visibleFeatures(_:));
+
+/**
+ Returns an array of rendered map features that intersect with a given point,
+ restricted to the given style layers.
+
+ Each object in the returned array represents a feature rendered by the
+ current style and provides access to attributes specified by the relevant
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile sources</a>.
+ The returned array includes features specified in vector and GeoJSON tile
+ sources but does not include anything from raster, image, or video sources.
+
+ Only visible features are returned. For example, suppose the current style uses
+ the
+ <a href="https://www.mapbox.com/vector-tiles/mapbox-streets/">Mapbox Streets source</a>,
+ but none of the specified style layers includes features that have the `maki`
+ property set to `bus`. If you pass a point corresponding to the location of a
+ bus stop into this method, the bus stop feature does not appear in the
+ resulting array. On the other hand, if the style does include bus stops, an
+ `MGLFeature` object representing that bus stop is returned and its
+ `attributes` dictionary has the `maki` key set to `bus` (along with other
+ attributes). The dictionary contains only the attributes provided by the
+ tile source; it does not include computed attribute values or rules about how
+ the feature is rendered by the current style.
+
+ The returned array is sorted by z-order, starting with the topmost rendered
+ feature and ending with the bottommost rendered feature. A feature that is
+ rendered multiple times due to wrapping across the antimeridian at low zoom
+ levels is included only once, subject to the caveat that follows.
+
+ Features come from tiled vector data or GeoJSON data that is converted to tiles
+ internally, so feature geometries are clipped at tile boundaries and features
+ may appear duplicated across tiles. For example, suppose the specified point
+ lies along a road that spans the screen. The resulting array includes those
+ parts of the road that lie within the map tile that contain the specified
+ point, even if the road extends into other tiles.
+
+ To find out the layer names in a particular style, view the style in
+ <a href="https://www.mapbox.com/studio/">Mapbox Studio</a>.
+
+ @param point A point expressed in the map view’s coordinate system.
+ @param styleLayerIdentifiers A set of strings that correspond to the names of
+ layers defined in the current style. Only the features contained in these
+ layers are included in the returned array.
+ @return An array of objects conforming to the `MGLFeature` protocol that
+ represent features in the sources used by the current style.
+ */
+- (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesAtPoint:(NSPoint)point inStyleLayersWithIdentifiers:(nullable NS_SET_OF(NSString *) *)styleLayerIdentifiers NS_SWIFT_NAME(visibleFeatures(_:styleLayerIdentifiers:));
+
+/**
+ Returns an array of rendered map features that intersect with the given
+ rectangle.
+
+ This method may return features from any of the map’s style layers. To restrict
+ the search to a particular layer or layers, use the
+ `-visibleFeaturesAtPoint:inStyleLayersWithIdentifiers:` method. For more
+ information about searching for map features, see that method’s documentation.
+
+ @param rect A rectangle expressed in the map view’s coordinate system.
+ @return An array of objects conforming to the `MGLFeature` protocol that
+ represent features in the sources used by the current style.
+ */
+- (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesInRect:(NSRect)rect NS_SWIFT_NAME(visibleFeatures(_:));
+
+/**
+ Returns an array of rendered map features that intersect with the given
+ rectangle, restricted to the given style layers.
+
+ Each object in the returned array represents a feature rendered by the
+ current style and provides access to attributes specified by the relevant
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile sources</a>.
+ The returned array includes features specified in vector and GeoJSON tile
+ sources but does not include anything from raster, image, or video sources.
+
+ Only visible features are returned. For example, suppose the current style uses
+ the
+ <a href="https://www.mapbox.com/vector-tiles/mapbox-streets/">Mapbox Streets source</a>,
+ but none of the specified style layers includes features that have the `maki`
+ property set to `bus`. If you pass a rectangle containing the location of a bus
+ stop into this method, the bus stop feature does not appear in the resulting
+ array. On the other hand, if the style does include bus stops, an `MGLFeature`
+ object representing that bus stop is returned and its `attributes` dictionary
+ has the `maki` key set to `bus` (along with other attributes). The dictionary
+ contains only the attributes provided by the tile source; it does not include
+ computed attribute values or rules about how the feature is rendered by the
+ current style.
+
+ The returned array is sorted by z-order, starting with the topmost rendered
+ feature and ending with the bottommost rendered feature. A feature that is
+ rendered multiple times due to wrapping across the antimeridian at low zoom
+ levels is included only once, subject to the caveat that follows.
+
+ Features come from tiled vector data or GeoJSON data that is converted to tiles
+ internally, so feature geometries are clipped at tile boundaries and features
+ may appear duplicated across tiles. For example, suppose the specified
+ rectangle intersects with a road that spans the screen. The resulting array
+ includes those parts of the road that lie within the map tiles covering the
+ specified rectangle, even if the road extends into other tiles. The portion of
+ the road within each map tile is included individually.
+
+ To find out the layer names in a particular style, view the style in
+ <a href="https://www.mapbox.com/studio/">Mapbox Studio</a>.
+
+ @param rect A rectangle expressed in the map view’s coordinate system.
+ @param styleLayerIdentifiers A set of strings that correspond to the names of
+ layers defined in the current style. Only the features contained in these
+ layers are included in the returned array.
+ @return An array of objects conforming to the `MGLFeature` protocol that
+ represent features in the sources used by the current style.
+ */
+- (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesInRect:(NSRect)rect inStyleLayersWithIdentifiers:(nullable NS_SET_OF(NSString *) *)styleLayerIdentifiers NS_SWIFT_NAME(visibleFeatures(_:styleLayerIdentifiers:));
+
+#pragma mark Converting Geographic Coordinates
+
+/**
+ Converts a geographic coordinate to a point in the given view’s coordinate
+ system.
+
+ @param coordinate The geographic coordinate to convert.
+ @param view The view in whose coordinate system the returned point should be
+ expressed. If this parameter is `nil`, the returned point is expressed in
+ the window’s coordinate system. If `view` is not `nil`, it must belong to
+ the same window as the map view.
+ @return The point (in the appropriate view or window coordinate system)
+ corresponding to the given geographic coordinate.
+ */
+- (NSPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable NSView *)view;
+
+/**
+ Converts a point in the given view’s coordinate system to a geographic
+ coordinate.
+
+ @param point The point to convert.
+ @param view The view in whose coordinate system the point is expressed.
+ @return The geographic coordinate at the given point.
+ */
+- (CLLocationCoordinate2D)convertPoint:(NSPoint)point toCoordinateFromView:(nullable NSView *)view;
+
+/**
+ Converts a geographic bounding box to a rectangle in the given view’s
+ coordinate system.
+
+ @param bounds The geographic bounding box to convert.
+ @param view The view in whose coordinate system the returned rectangle should
+ be expressed. If this parameter is `nil`, the returned rectangle is
+ expressed in the window’s coordinate system. If `view` is not `nil`, it must
+ belong to the same window as the map view.
+ */
+- (NSRect)convertCoordinateBounds:(MGLCoordinateBounds)bounds toRectToView:(nullable NSView *)view;
+
+/**
+ Converts a rectangle in the given view’s coordinate system to a geographic
+ bounding box.
+
+ @param rect The rectangle to convert.
+ @param view The view in whose coordinate system the rectangle is expressed.
+ @return The geographic bounding box coextensive with the given rectangle.
+ */
+- (MGLCoordinateBounds)convertRect:(NSRect)rect toCoordinateBoundsFromView:(nullable NSView *)view;
+
+/**
+ Returns the distance spanned by one point in the map view’s coordinate system
+ at the given latitude and current zoom level.
+
+ The distance between points decreases as the latitude approaches the poles.
+ This relationship parallels the relationship between longitudinal coordinates
+ at different latitudes.
+
+ @param latitude The latitude of the geographic coordinate represented by the
+ point.
+ @return The distance in meters spanned by a single point.
+ */
+- (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude;
+
+#pragma mark Debugging the Map
+
+/**
+ The options that determine which debugging aids are shown on the map.
+
+ These options are all disabled by default and should remain disabled in
+ released software for performance and aesthetic reasons.
+ */
+@property (nonatomic) MGLMapDebugMaskOptions debugMask;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm
new file mode 100644
index 0000000000..2f985b85d8
--- /dev/null
+++ b/platform/macos/src/MGLMapView.mm
@@ -0,0 +1,2499 @@
+#import "MGLMapView_Private.h"
+#import "MGLAnnotationImage_Private.h"
+#import "MGLAttributionButton.h"
+#import "MGLCompassCell.h"
+#import "MGLOpenGLLayer.h"
+#import "MGLStyle.h"
+
+#import "MGLFeature_Private.h"
+#import "MGLGeometry_Private.h"
+#import "MGLMultiPoint_Private.h"
+#import "MGLOfflineStorage_Private.h"
+
+#import "MGLAccountManager.h"
+#import "MGLMapCamera.h"
+#import "MGLPolygon.h"
+#import "MGLPolyline.h"
+#import "MGLAnnotationImage.h"
+#import "MGLMapViewDelegate.h"
+
+#import <mbgl/mbgl.hpp>
+#import <mbgl/annotation/annotation.hpp>
+#import <mbgl/map/camera.hpp>
+#import <mbgl/platform/darwin/reachability.h>
+#import <mbgl/gl/gl.hpp>
+#import <mbgl/sprite/sprite_image.hpp>
+#import <mbgl/storage/default_file_source.hpp>
+#import <mbgl/storage/network_status.hpp>
+#import <mbgl/math/wrap.hpp>
+#import <mbgl/util/constants.hpp>
+#import <mbgl/util/chrono.hpp>
+
+#import <map>
+#import <unordered_set>
+
+#import "NSBundle+MGLAdditions.h"
+#import "NSProcessInfo+MGLAdditions.h"
+#import "NSException+MGLAdditions.h"
+#import "NSString+MGLAdditions.h"
+
+#import <QuartzCore/QuartzCore.h>
+
+class MGLMapViewImpl;
+class MGLAnnotationContext;
+
+/// Distance from the edge of the view to ornament views (logo, attribution, etc.).
+const CGFloat MGLOrnamentPadding = 12;
+
+/// Alpha value of the ornament views (logo, attribution, etc.).
+const CGFloat MGLOrnamentOpacity = 0.9;
+
+/// Default duration for programmatic animations.
+const NSTimeInterval MGLAnimationDuration = 0.3;
+
+/// Distance in points that a single press of the panning keyboard shortcut pans the map by.
+const CGFloat MGLKeyPanningIncrement = 150;
+
+/// Degrees that a single press of the rotation keyboard shortcut rotates the map by.
+const CLLocationDegrees MGLKeyRotationIncrement = 25;
+
+/// Key for the user default that, when true, causes the map view to zoom in and out on scroll wheel events.
+NSString * const MGLScrollWheelZoomsMapViewDefaultKey = @"MGLScrollWheelZoomsMapView";
+
+/// Reuse identifier and file name of the default point annotation image.
+static NSString * const MGLDefaultStyleMarkerSymbolName = @"default_marker";
+
+/// Prefix that denotes a sprite installed by MGLMapView, to avoid collisions
+/// with style-defined sprites.
+static NSString * const MGLAnnotationSpritePrefix = @"com.mapbox.sprites.";
+
+/// Slop area around the hit testing point, allowing for imprecise annotation selection.
+const CGFloat MGLAnnotationImagePaddingForHitTest = 4;
+
+/// Distance from the callout’s anchor point to the annotation it points to.
+const CGFloat MGLAnnotationImagePaddingForCallout = 4;
+
+/// Copyright notices displayed in the attribution view.
+struct MGLAttribution {
+ /// Attribution button label text. A copyright symbol is prepended to this string.
+ NSString *title;
+ /// URL to open when the attribution button is clicked.
+ NSString *urlString;
+} MGLAttributions[] = {
+ {
+ .title = NSLocalizedStringWithDefaultValue(@"COPYRIGHT_MAPBOX", nil, nil, @"Mapbox", @"Linked part of copyright notice"),
+ .urlString = NSLocalizedStringWithDefaultValue(@"COPYRIGHT_MAPBOX_LINK", nil, nil, @"https://www.mapbox.com/about/maps/", @"Copyright notice link"),
+ },
+ {
+ .title = NSLocalizedStringWithDefaultValue(@"COPYRIGHT_OSM", nil, nil, @"OpenStreetMap", @"Linked part of copyright notice"),
+ .urlString = NSLocalizedStringWithDefaultValue(@"COPYRIGHT_OSM_LINK", nil, nil, @"http://www.openstreetmap.org/about/", @"Copyright notice link"),
+ },
+};
+
+/// Unique identifier representing a single annotation in mbgl.
+typedef uint32_t MGLAnnotationTag;
+
+/// An indication that the requested annotation was not found or is nonexistent.
+enum { MGLAnnotationTagNotFound = UINT32_MAX };
+
+/// Mapping from an annotation tag to metadata about that annotation, including
+/// the annotation itself.
+typedef std::map<MGLAnnotationTag, MGLAnnotationContext> MGLAnnotationContextMap;
+
+/// Returns an NSImage for the default marker image.
+NSImage *MGLDefaultMarkerImage() {
+ NSString *path = [[NSBundle mgl_frameworkBundle] pathForResource:MGLDefaultStyleMarkerSymbolName
+ ofType:@"pdf"];
+ return [[NSImage alloc] initWithContentsOfFile:path];
+}
+
+/// Converts from a duration in seconds to a duration object usable in mbgl.
+mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) {
+ return std::chrono::duration_cast<mbgl::Duration>(std::chrono::duration<NSTimeInterval>(duration));
+}
+
+/// Converts a media timing function into a unit bezier object usable in mbgl.
+mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function) {
+ if (!function) {
+ function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
+ }
+ float p1[2], p2[2];
+ [function getControlPointAtIndex:0 values:p1];
+ [function getControlPointAtIndex:1 values:p2];
+ return { p1[0], p1[1], p2[0], p2[1] };
+}
+
+/// Converts the given color into an mbgl::Color in calibrated RGB space.
+mbgl::Color MGLColorObjectFromNSColor(NSColor *color) {
+ if (!color) {
+ return {{ 0, 0, 0, 0 }};
+ }
+ CGFloat r, g, b, a;
+ [[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&r green:&g blue:&b alpha:&a];
+ return {{ (float)r, (float)g, (float)b, (float)a }};
+}
+
+/// Lightweight container for metadata about an annotation, including the annotation itself.
+class MGLAnnotationContext {
+public:
+ id <MGLAnnotation> annotation;
+ /// The annotation’s image’s reuse identifier.
+ NSString *imageReuseIdentifier;
+};
+
+@interface MGLMapView () <NSPopoverDelegate, MGLMultiPointDelegate>
+
+@property (nonatomic, readwrite) NSSegmentedControl *zoomControls;
+@property (nonatomic, readwrite) NSSlider *compass;
+@property (nonatomic, readwrite) NSImageView *logoView;
+@property (nonatomic, readwrite) NSView *attributionView;
+
+/// Mapping from reusable identifiers to annotation images.
+@property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImagesByIdentifier;
+/// Currently shown popover representing the selected annotation.
+@property (nonatomic) NSPopover *calloutForSelectedAnnotation;
+
+@property (nonatomic, readwrite, getter=isDormant) BOOL dormant;
+
+@end
+
+@implementation MGLMapView {
+ /// Cross-platform map view controller.
+ mbgl::Map *_mbglMap;
+ MGLMapViewImpl *_mbglView;
+
+ NSPanGestureRecognizer *_panGestureRecognizer;
+ NSMagnificationGestureRecognizer *_magnificationGestureRecognizer;
+ NSRotationGestureRecognizer *_rotationGestureRecognizer;
+ double _scaleAtBeginningOfGesture;
+ CLLocationDirection _directionAtBeginningOfGesture;
+ CGFloat _pitchAtBeginningOfGesture;
+ BOOL _didHideCursorDuringGesture;
+
+ MGLAnnotationContextMap _annotationContextsByAnnotationTag;
+ MGLAnnotationTag _selectedAnnotationTag;
+ MGLAnnotationTag _lastSelectedAnnotationTag;
+ /// Size of the rectangle formed by unioning the maximum slop area around every annotation image.
+ NSSize _unionedAnnotationImageSize;
+ std::vector<MGLAnnotationTag> _annotationsNearbyLastClick;
+ /// True if any annotations that have tooltips have been installed.
+ BOOL _wantsToolTipRects;
+ /// True if any annotation images that have custom cursors have been installed.
+ BOOL _wantsCursorRects;
+
+ // Cached checks for delegate method implementations that may be called from
+ // MGLMultiPointDelegate methods.
+
+ BOOL _delegateHasAlphasForShapeAnnotations;
+ BOOL _delegateHasStrokeColorsForShapeAnnotations;
+ BOOL _delegateHasFillColorsForShapeAnnotations;
+ BOOL _delegateHasLineWidthsForShapeAnnotations;
+
+ /// True if the current process is the Interface Builder designable
+ /// renderer. When drawing the designable, the map is paused, so any call to
+ /// it may hang the process.
+ BOOL _isTargetingInterfaceBuilder;
+ CLLocationDegrees _pendingLatitude;
+ CLLocationDegrees _pendingLongitude;
+
+ /// True if the view is currently printing itself.
+ BOOL _isPrinting;
+}
+
+#pragma mark Lifecycle
+
++ (void)initialize {
+ if (self == [MGLMapView class]) {
+ [[NSUserDefaults standardUserDefaults] registerDefaults:@{
+ MGLScrollWheelZoomsMapViewDefaultKey: @NO,
+ }];
+ }
+}
+
+- (instancetype)initWithFrame:(NSRect)frameRect {
+ if (self = [super initWithFrame:frameRect]) {
+ [self commonInit];
+ self.styleURL = nil;
+ }
+ return self;
+}
+
+- (instancetype)initWithFrame:(NSRect)frame styleURL:(nullable NSURL *)styleURL {
+ if (self = [super initWithFrame:frame]) {
+ [self commonInit];
+ self.styleURL = styleURL;
+ }
+ return self;
+}
+
+- (instancetype)initWithCoder:(nonnull NSCoder *)decoder {
+ if (self = [super initWithCoder:decoder]) {
+ [self commonInit];
+ }
+ return self;
+}
+
+- (void)awakeFromNib {
+ [super awakeFromNib];
+
+ self.styleURL = nil;
+}
+
++ (NSArray *)restorableStateKeyPaths {
+ return @[@"camera", @"debugMask"];
+}
+
+- (void)commonInit {
+ _isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent;
+
+ // Set up cross-platform controllers and resources.
+ _mbglView = new MGLMapViewImpl(self, [NSScreen mainScreen].backingScaleFactor);
+
+ // Delete the pre-offline ambient cache at
+ // ~/Library/Caches/com.mapbox.sdk.ios/cache.db.
+ NSURL *cachesDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
+ inDomain:NSUserDomainMask
+ appropriateForURL:nil
+ create:NO
+ error:nil];
+ cachesDirectoryURL = [cachesDirectoryURL URLByAppendingPathComponent:
+ [NSBundle mgl_frameworkBundle].bundleIdentifier];
+ NSURL *legacyCacheURL = [cachesDirectoryURL URLByAppendingPathComponent:@"cache.db"];
+ [[NSFileManager defaultManager] removeItemAtURL:legacyCacheURL error:NULL];
+
+ mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource;
+ _mbglMap = new mbgl::Map(*_mbglView, *mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None, mbgl::ViewportMode::Default);
+ [self validateTileCacheSize];
+
+ // Install the OpenGL layer. Interface Builder’s synchronous drawing means
+ // we can’t display a map, so don’t even bother to have a map layer.
+ self.layer = _isTargetingInterfaceBuilder ? [CALayer layer] : [MGLOpenGLLayer layer];
+
+ // Notify map object when network reachability status changes.
+ MGLReachability *reachability = [MGLReachability reachabilityForInternetConnection];
+ reachability.reachableBlock = ^(MGLReachability *) {
+ mbgl::NetworkStatus::Reachable();
+ };
+ [reachability startNotifier];
+
+ // Install ornaments and gesture recognizers.
+ [self installZoomControls];
+ [self installCompass];
+ [self installLogoView];
+ [self installAttributionView];
+ [self installGestureRecognizers];
+
+ // Set up annotation management and selection state.
+ _annotationImagesByIdentifier = [NSMutableDictionary dictionary];
+ _annotationContextsByAnnotationTag = {};
+ _selectedAnnotationTag = MGLAnnotationTagNotFound;
+ _lastSelectedAnnotationTag = MGLAnnotationTagNotFound;
+ _annotationsNearbyLastClick = {};
+
+ // Jump to Null Island initially.
+ self.automaticallyAdjustsContentInsets = YES;
+ mbgl::CameraOptions options;
+ options.center = mbgl::LatLng(0, 0);
+ options.padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets);
+ options.zoom = _mbglMap->getMinZoom();
+ _mbglMap->jumpTo(options);
+ _pendingLatitude = NAN;
+ _pendingLongitude = NAN;
+}
+
+/// Adds zoom controls to the lower-right corner.
+- (void)installZoomControls {
+ _zoomControls = [[NSSegmentedControl alloc] initWithFrame:NSZeroRect];
+ _zoomControls.wantsLayer = YES;
+ _zoomControls.layer.opacity = MGLOrnamentOpacity;
+ [(NSSegmentedCell *)_zoomControls.cell setTrackingMode:NSSegmentSwitchTrackingMomentary];
+ _zoomControls.continuous = YES;
+ _zoomControls.segmentCount = 2;
+ [_zoomControls setLabel:NSLocalizedStringWithDefaultValue(@"ZOOM_OUT_LABEL", nil, nil, @"−", @"Label of Zoom Out button; U+2212 MINUS SIGN") forSegment:0];
+ [(NSSegmentedCell *)_zoomControls.cell setTag:0 forSegment:0];
+ [(NSSegmentedCell *)_zoomControls.cell setToolTip:NSLocalizedStringWithDefaultValue(@"ZOOM_OUT_TOOLTIP", nil, nil, @"Zoom Out", @"Tooltip of Zoom Out button") forSegment:0];
+ [_zoomControls setLabel:NSLocalizedStringWithDefaultValue(@"ZOOM_IN_LABEL", nil, nil, @"+", @"Label of Zoom In button") forSegment:1];
+ [(NSSegmentedCell *)_zoomControls.cell setTag:1 forSegment:1];
+ [(NSSegmentedCell *)_zoomControls.cell setToolTip:NSLocalizedStringWithDefaultValue(@"ZOOM_IN_TOOLTIP", nil, nil, @"Zoom In", @"Tooltip of Zoom In button") forSegment:1];
+ _zoomControls.target = self;
+ _zoomControls.action = @selector(zoomInOrOut:);
+ _zoomControls.controlSize = NSRegularControlSize;
+ [_zoomControls sizeToFit];
+ _zoomControls.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:_zoomControls];
+}
+
+/// Adds a rudimentary compass control to the lower-right corner.
+- (void)installCompass {
+ _compass = [[NSSlider alloc] initWithFrame:NSZeroRect];
+ _compass.wantsLayer = YES;
+ _compass.layer.opacity = MGLOrnamentOpacity;
+ _compass.cell = [[MGLCompassCell alloc] init];
+ _compass.continuous = YES;
+ _compass.target = self;
+ _compass.action = @selector(rotate:);
+ [_compass sizeToFit];
+ _compass.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:_compass];
+}
+
+/// Adds a Mapbox logo to the lower-left corner.
+- (void)installLogoView {
+ _logoView = [[NSImageView alloc] initWithFrame:NSZeroRect];
+ _logoView.wantsLayer = YES;
+ NSImage *logoImage = [[NSImage alloc] initWithContentsOfFile:
+ [[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]];
+ // Account for the image’s built-in padding when aligning other controls to the logo.
+ logoImage.alignmentRect = NSInsetRect(logoImage.alignmentRect, 3, 3);
+ _logoView.image = logoImage;
+ _logoView.translatesAutoresizingMaskIntoConstraints = NO;
+ _logoView.accessibilityTitle = NSLocalizedStringWithDefaultValue(@"MAP_A11Y_TITLE", nil, nil, @"Mapbox", @"Accessibility title");
+ [self addSubview:_logoView];
+}
+
+/// Adds legally required map attribution to the lower-left corner.
+- (void)installAttributionView {
+ _attributionView = [[NSView alloc] initWithFrame:NSZeroRect];
+ _attributionView.wantsLayer = YES;
+
+ // Make the background and foreground translucent to be unobtrusive.
+ _attributionView.layer.opacity = 0.6;
+
+ // Blur the background to prevent text underneath the view from running into
+ // the text in the view, rendering it illegible.
+ CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
+ [attributionBlurFilter setDefaults];
+
+ // Brighten the background. This is similar to applying a translucent white
+ // background on the view, but the effect is a bit more subtle and works
+ // well with the blur above.
+ CIFilter *attributionColorFilter = [CIFilter filterWithName:@"CIColorControls"];
+ [attributionColorFilter setDefaults];
+ [attributionColorFilter setValue:@(0.1) forKey:kCIInputBrightnessKey];
+
+ // Apply the background effects and a standard button corner radius.
+ _attributionView.backgroundFilters = @[attributionColorFilter, attributionBlurFilter];
+ _attributionView.layer.cornerRadius = 4;
+
+ _attributionView.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:_attributionView];
+ [self updateAttributionView];
+}
+
+/// Adds gesture recognizers for manipulating the viewport and selecting annotations.
+- (void)installGestureRecognizers {
+ _scrollEnabled = YES;
+ _zoomEnabled = YES;
+ _rotateEnabled = YES;
+ _pitchEnabled = YES;
+
+ _panGestureRecognizer = [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
+ _panGestureRecognizer.delaysKeyEvents = YES;
+ [self addGestureRecognizer:_panGestureRecognizer];
+
+ NSClickGestureRecognizer *clickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleClickGesture:)];
+ clickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO;
+ [self addGestureRecognizer:clickGestureRecognizer];
+
+ NSClickGestureRecognizer *doubleClickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleClickGesture:)];
+ doubleClickGestureRecognizer.numberOfClicksRequired = 2;
+ doubleClickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO;
+ [self addGestureRecognizer:doubleClickGestureRecognizer];
+
+ _magnificationGestureRecognizer = [[NSMagnificationGestureRecognizer alloc] initWithTarget:self action:@selector(handleMagnificationGesture:)];
+ [self addGestureRecognizer:_magnificationGestureRecognizer];
+
+ _rotationGestureRecognizer = [[NSRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotationGesture:)];
+ [self addGestureRecognizer:_rotationGestureRecognizer];
+}
+
+/// Updates the attribution view to reflect the sources used. For now, this is
+/// hard-coded to the standard Mapbox and OpenStreetMap attribution.
+- (void)updateAttributionView {
+ self.attributionView.subviews = @[];
+
+ for (NSUInteger i = 0; i < sizeof(MGLAttributions) / sizeof(MGLAttributions[0]); i++) {
+ // For each attribution, add a borderless button that responds to clicks
+ // and feels like a hyperlink.
+ NSURL *url = [NSURL URLWithString:MGLAttributions[i].urlString];
+ NSButton *button = [[MGLAttributionButton alloc] initWithTitle:MGLAttributions[i].title URL:url];
+ button.controlSize = NSMiniControlSize;
+ button.translatesAutoresizingMaskIntoConstraints = NO;
+
+ // Set the new button flush with the buttom of the container and to the
+ // right of the previous button, with standard spacing. If there is no
+ // previous button, align to the container instead.
+ NSView *previousView = self.attributionView.subviews.lastObject;
+ [self.attributionView addSubview:button];
+ [_attributionView addConstraint:
+ [NSLayoutConstraint constraintWithItem:button
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:_attributionView
+ attribute:NSLayoutAttributeBottom
+ multiplier:1
+ constant:0]];
+ [_attributionView addConstraint:
+ [NSLayoutConstraint constraintWithItem:button
+ attribute:NSLayoutAttributeLeading
+ relatedBy:NSLayoutRelationEqual
+ toItem:previousView ? previousView : _attributionView
+ attribute:previousView ? NSLayoutAttributeTrailing : NSLayoutAttributeLeading
+ multiplier:1
+ constant:8]];
+ }
+}
+
+- (void)dealloc {
+ [self.window removeObserver:self forKeyPath:@"contentLayoutRect"];
+ [self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"];
+
+ // Close any annotation callout immediately.
+ [self.calloutForSelectedAnnotation close];
+ self.calloutForSelectedAnnotation = nil;
+
+ // Removing the annotations unregisters any outstanding KVO observers.
+ [self removeAnnotations:self.annotations];
+
+ if (_mbglMap) {
+ delete _mbglMap;
+ _mbglMap = nullptr;
+ }
+ if (_mbglView) {
+ delete _mbglView;
+ _mbglView = nullptr;
+ }
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(__unused NSDictionary *)change context:(void *)context {
+ if ([keyPath isEqualToString:@"contentLayoutRect"] ||
+ [keyPath isEqualToString:@"titlebarAppearsTransparent"]) {
+ [self adjustContentInsets];
+ } else if ([keyPath isEqualToString:@"coordinate"] &&
+ [object conformsToProtocol:@protocol(MGLAnnotation)] &&
+ ![object isKindOfClass:[MGLMultiPoint class]]) {
+ id <MGLAnnotation> annotation = object;
+ MGLAnnotationTag annotationTag = (MGLAnnotationTag)(NSUInteger)context;
+ // We can get here because a subclass registered itself as an observer
+ // of the coordinate key path of a non-multipoint annotation but failed
+ // to handle the change. This check deters us from treating the
+ // subclass’s context as an annotation tag. If the context happens to
+ // match a valid annotation tag, the annotation will be unnecessarily
+ // but safely updated.
+ if (annotation == [self annotationWithTag:annotationTag]) {
+ const mbgl::Point<double> point = MGLPointFromLocationCoordinate2D(annotation.coordinate);
+ MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
+ _mbglMap->updateAnnotation(annotationTag, mbgl::SymbolAnnotation { point, annotationImage.styleIconIdentifier.UTF8String ?: "" });
+ if (annotationTag == _selectedAnnotationTag) {
+ [self deselectAnnotation:annotation];
+ }
+ }
+ }
+}
+
++ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
+ return [key isEqualToString:@"annotations"] ? YES : [super automaticallyNotifiesObserversForKey:key];
+}
+
+- (void)setDelegate:(id<MGLMapViewDelegate>)delegate {
+ _delegate = delegate;
+
+ // Cache checks for delegate method implementations that may be called in a
+ // hot loop, namely the annotation style methods.
+ _delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)];
+ _delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)];
+ _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)];
+ _delegateHasLineWidthsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)];
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wundeclared-selector"
+ if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) {
+ NSLog(@"-mapView:regionWillChangeAnimated: is not supported by the macOS SDK, but %@ implements it anyways. "
+ @"Please implement -[%@ mapView:cameraWillChangeAnimated:] instead.",
+ NSStringFromClass([delegate class]), NSStringFromClass([delegate class]));
+ }
+ if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) {
+ NSLog(@"-mapViewRegionIsChanging: is not supported by the macOS SDK, but %@ implements it anyways. "
+ @"Please implement -[%@ mapViewCameraIsChanging:] instead.",
+ NSStringFromClass([delegate class]), NSStringFromClass([delegate class]));
+ }
+ if ([self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]) {
+ NSLog(@"-mapView:regionDidChangeAnimated: is not supported by the macOS SDK, but %@ implements it anyways. "
+ @"Please implement -[%@ mapView:cameraDidChangeAnimated:] instead.",
+ NSStringFromClass([delegate class]), NSStringFromClass([delegate class]));
+ }
+#pragma clang diagnostic pop
+}
+
+#pragma mark Style
+
+- (nonnull NSURL *)styleURL {
+ NSString *styleURLString = @(_mbglMap->getStyleURL().c_str()).mgl_stringOrNilIfEmpty;
+ return styleURLString ? [NSURL URLWithString:styleURLString] : [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion];
+}
+
+- (void)setStyleURL:(nullable NSURL *)styleURL {
+ if (_isTargetingInterfaceBuilder) {
+ return;
+ }
+
+ // Default to Streets.
+ if (!styleURL) {
+ // An access token is required to load any default style, including
+ // Streets.
+ if (![MGLAccountManager accessToken]) {
+ return;
+ }
+ styleURL = [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion];
+ }
+
+ if (![styleURL scheme]) {
+ // Assume a relative path into the application’s resource folder.
+ styleURL = [NSURL URLWithString:[@"asset://" stringByAppendingString:styleURL.absoluteString]];
+ }
+
+ _mbglMap->setStyleURL(styleURL.absoluteString.UTF8String);
+}
+
+- (IBAction)reloadStyle:(__unused id)sender {
+ NSURL *styleURL = self.styleURL;
+ _mbglMap->setStyleURL("");
+ self.styleURL = styleURL;
+}
+
+#pragma mark View hierarchy and drawing
+
+- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
+ [self deselectAnnotation:self.selectedAnnotation];
+ if (!self.dormant && !newWindow) {
+ self.dormant = YES;
+ }
+
+ [self.window removeObserver:self forKeyPath:@"contentLayoutRect"];
+ [self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"];
+}
+
+- (void)viewDidMoveToWindow {
+ NSWindow *window = self.window;
+ if (self.dormant && window) {
+ self.dormant = NO;
+ }
+
+ if (window && _mbglMap->getConstrainMode() == mbgl::ConstrainMode::None) {
+ _mbglMap->setConstrainMode(mbgl::ConstrainMode::HeightOnly);
+ }
+
+ [window addObserver:self
+ forKeyPath:@"contentLayoutRect"
+ options:NSKeyValueObservingOptionInitial
+ context:NULL];
+ [window addObserver:self
+ forKeyPath:@"titlebarAppearsTransparent"
+ options:NSKeyValueObservingOptionInitial
+ context:NULL];
+}
+
+- (BOOL)wantsLayer {
+ return YES;
+}
+
+- (BOOL)wantsBestResolutionOpenGLSurface {
+ // Use an OpenGL layer, except when drawing the designable, which is just
+ // ordinary Cocoa.
+ return !_isTargetingInterfaceBuilder;
+}
+
+- (void)setFrame:(NSRect)frame {
+ super.frame = frame;
+ if (!NSEqualRects(frame, self.frame)) {
+ [self validateTileCacheSize];
+ }
+ if (!_isTargetingInterfaceBuilder) {
+ _mbglMap->update(mbgl::Update::Dimensions);
+ }
+}
+
+- (void)updateConstraints {
+ // Place the zoom controls at the lower-right corner of the view.
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:self
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:_zoomControls
+ attribute:NSLayoutAttributeBottom
+ multiplier:1
+ constant:MGLOrnamentPadding]];
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:self
+ attribute:NSLayoutAttributeTrailing
+ relatedBy:NSLayoutRelationEqual
+ toItem:_zoomControls
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1
+ constant:MGLOrnamentPadding]];
+
+ // Center the compass above the zoom controls, assuming that the compass is
+ // narrower than the zoom controls.
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:_compass
+ attribute:NSLayoutAttributeCenterX
+ relatedBy:NSLayoutRelationEqual
+ toItem:_zoomControls
+ attribute:NSLayoutAttributeCenterX
+ multiplier:1
+ constant:0]];
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:_zoomControls
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:_compass
+ attribute:NSLayoutAttributeBottom
+ multiplier:1
+ constant:8]];
+
+ // Place the logo view in the lower-left corner of the view, accounting for
+ // the logo’s alignment rect.
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:self
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:_logoView
+ attribute:NSLayoutAttributeBottom
+ multiplier:1
+ constant:MGLOrnamentPadding - _logoView.image.alignmentRect.origin.y]];
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:_logoView
+ attribute:NSLayoutAttributeLeading
+ relatedBy:NSLayoutRelationEqual
+ toItem:self
+ attribute:NSLayoutAttributeLeading
+ multiplier:1
+ constant:MGLOrnamentPadding - _logoView.image.alignmentRect.origin.x]];
+
+ // Place the attribution view to the right of the logo view and size it to
+ // fit the buttons inside.
+ [self addConstraint:[NSLayoutConstraint constraintWithItem:_logoView
+ attribute:NSLayoutAttributeBaseline
+ relatedBy:NSLayoutRelationEqual
+ toItem:_attributionView
+ attribute:NSLayoutAttributeBaseline
+ multiplier:1
+ constant:_logoView.image.alignmentRect.origin.y]];
+ [self addConstraint:[NSLayoutConstraint constraintWithItem:_attributionView
+ attribute:NSLayoutAttributeLeading
+ relatedBy:NSLayoutRelationEqual
+ toItem:_logoView
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1
+ constant:8]];
+ [self addConstraint:[NSLayoutConstraint constraintWithItem:_attributionView.subviews.firstObject
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:_attributionView
+ attribute:NSLayoutAttributeTop
+ multiplier:1
+ constant:0]];
+ [self addConstraint:[NSLayoutConstraint constraintWithItem:_attributionView
+ attribute:NSLayoutAttributeTrailing
+ relatedBy:NSLayoutRelationEqual
+ toItem:_attributionView.subviews.lastObject
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1
+ constant:8]];
+
+ [super updateConstraints];
+}
+
+- (void)renderSync {
+ if (!self.dormant) {
+ // Enable vertex buffer objects.
+ mbgl::gl::InitializeExtensions([](const char *name) {
+ static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
+ if (!framework) {
+ throw std::runtime_error("Failed to load OpenGL framework.");
+ }
+
+ CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, name, kCFStringEncodingASCII);
+ void *symbol = CFBundleGetFunctionPointerForName(framework, str);
+ CFRelease(str);
+
+ return reinterpret_cast<mbgl::gl::glProc>(symbol);
+ });
+
+ _mbglMap->render();
+
+ if (_isPrinting) {
+ _isPrinting = NO;
+ std::string png = encodePNG(_mbglView->readStillImage());
+ NSData *data = [[NSData alloc] initWithBytes:png.data() length:png.size()];
+ NSImage *image = [[NSImage alloc] initWithData:data];
+ [self performSelector:@selector(printWithImage:) withObject:image afterDelay:0];
+ }
+
+// [self updateUserLocationAnnotationView];
+ }
+}
+
+- (void)validateTileCacheSize {
+ if (!_mbglMap) {
+ return;
+ }
+
+ CGFloat zoomFactor = self.maximumZoomLevel - self.minimumZoomLevel + 1;
+ CGFloat cpuFactor = [NSProcessInfo processInfo].processorCount;
+ CGFloat memoryFactor = (CGFloat)[NSProcessInfo processInfo].physicalMemory / 1000 / 1000 / 1000;
+ CGFloat sizeFactor = (NSWidth(self.bounds) / mbgl::util::tileSize) * (NSHeight(self.bounds) / mbgl::util::tileSize);
+
+ NSUInteger cacheSize = zoomFactor * cpuFactor * memoryFactor * sizeFactor * 0.5;
+
+ _mbglMap->setSourceTileCacheSize(cacheSize);
+}
+
+- (void)invalidate {
+ MGLAssertIsMainThread();
+
+ [self.layer setNeedsDisplay];
+}
+
+- (void)notifyMapChange:(mbgl::MapChange)change {
+ // Ignore map updates when the Map object isn't set.
+ if (!_mbglMap) {
+ return;
+ }
+
+ switch (change) {
+ case mbgl::MapChangeRegionWillChange:
+ case mbgl::MapChangeRegionWillChangeAnimated:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapView:cameraWillChangeAnimated:)]) {
+ BOOL animated = change == mbgl::MapChangeRegionWillChangeAnimated;
+ [self.delegate mapView:self cameraWillChangeAnimated:animated];
+ }
+ break;
+ }
+ case mbgl::MapChangeRegionIsChanging:
+ {
+ // Update a minimum of UI that needs to stay attached to the map
+ // while animating.
+ [self updateCompass];
+ [self updateAnnotationCallouts];
+
+ if ([self.delegate respondsToSelector:@selector(mapViewCameraIsChanging:)]) {
+ [self.delegate mapViewCameraIsChanging:self];
+ }
+ break;
+ }
+ case mbgl::MapChangeRegionDidChange:
+ case mbgl::MapChangeRegionDidChangeAnimated:
+ {
+ // Update all UI at the end of an animation or atomic change to the
+ // viewport. More expensive updates can happen here, but care should
+ // still be taken to minimize the work done here because scroll
+ // gesture recognition and momentum scrolling is performed as a
+ // series of atomic changes, not an animation.
+ [self updateZoomControls];
+ [self updateCompass];
+ [self updateAnnotationCallouts];
+ [self updateAnnotationTrackingAreas];
+
+ if ([self.delegate respondsToSelector:@selector(mapView:cameraDidChangeAnimated:)]) {
+ BOOL animated = change == mbgl::MapChangeRegionDidChangeAnimated;
+ [self.delegate mapView:self cameraDidChangeAnimated:animated];
+ }
+ 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:
+ {
+ // Not yet implemented.
+ break;
+ }
+ case mbgl::MapChangeWillStartRenderingMap:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingMap:)]) {
+ [self.delegate mapViewWillStartRenderingMap:self];
+ }
+ break;
+ }
+ case mbgl::MapChangeDidFinishRenderingMap:
+ case mbgl::MapChangeDidFinishRenderingMapFullyRendered:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)]) {
+ BOOL fullyRendered = change == mbgl::MapChangeDidFinishRenderingMapFullyRendered;
+ [self.delegate mapViewDidFinishRenderingMap:self fullyRendered:fullyRendered];
+ }
+ break;
+ }
+ case mbgl::MapChangeWillStartRenderingFrame:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingFrame:)]) {
+ [self.delegate mapViewWillStartRenderingFrame:self];
+ }
+ break;
+ }
+ case mbgl::MapChangeDidFinishRenderingFrame:
+ case mbgl::MapChangeDidFinishRenderingFrameFullyRendered:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)]) {
+ BOOL fullyRendered = change == mbgl::MapChangeDidFinishRenderingFrameFullyRendered;
+ [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered];
+ }
+ break;
+ }
+ }
+}
+
+#pragma mark Printing
+
+- (void)print:(__unused id)sender {
+ _isPrinting = YES;
+ [self invalidate];
+}
+
+- (void)printWithImage:(NSImage *)image {
+ NSImageView *imageView = [[NSImageView alloc] initWithFrame:self.bounds];
+ imageView.image = image;
+
+ NSPrintOperation *op = [NSPrintOperation printOperationWithView:imageView];
+ [op runOperation];
+}
+
+#pragma mark Viewport
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCenterCoordinate {
+ return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil];
+}
+
+- (CLLocationCoordinate2D)centerCoordinate {
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets);
+ return MGLLocationCoordinate2DFromLatLng(_mbglMap->getLatLng(padding));
+}
+
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate {
+ [self setCenterCoordinate:centerCoordinate animated:NO];
+}
+
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate animated:(BOOL)animated {
+ [self willChangeValueForKey:@"centerCoordinate"];
+ _mbglMap->setLatLng(MGLLatLngFromLocationCoordinate2D(centerCoordinate),
+ MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets),
+ MGLDurationInSeconds(animated ? MGLAnimationDuration : 0));
+ [self didChangeValueForKey:@"centerCoordinate"];
+}
+
+- (void)offsetCenterCoordinateBy:(NSPoint)delta animated:(BOOL)animated {
+ [self willChangeValueForKey:@"centerCoordinate"];
+ _mbglMap->cancelTransitions();
+ _mbglMap->moveBy({ delta.x, delta.y },
+ MGLDurationInSeconds(animated ? MGLAnimationDuration : 0));
+ [self didChangeValueForKey:@"centerCoordinate"];
+}
+
+- (CLLocationDegrees)pendingLatitude {
+ return _pendingLatitude;
+}
+
+- (void)setPendingLatitude:(CLLocationDegrees)pendingLatitude {
+ _pendingLatitude = pendingLatitude;
+}
+
+- (CLLocationDegrees)pendingLongitude {
+ return _pendingLongitude;
+}
+
+- (void)setPendingLongitude:(CLLocationDegrees)pendingLongitude {
+ _pendingLongitude = pendingLongitude;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingZoomLevel {
+ return [NSSet setWithObject:@"camera"];
+}
+
+- (double)zoomLevel {
+ return _mbglMap->getZoom();
+}
+
+- (void)setZoomLevel:(double)zoomLevel {
+ [self setZoomLevel:zoomLevel animated:NO];
+}
+
+- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated {
+ [self willChangeValueForKey:@"zoomLevel"];
+ _mbglMap->setZoom(zoomLevel,
+ MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets),
+ MGLDurationInSeconds(animated ? MGLAnimationDuration : 0));
+ [self didChangeValueForKey:@"zoomLevel"];
+}
+
+- (void)zoomBy:(double)zoomDelta animated:(BOOL)animated {
+ [self setZoomLevel:self.zoomLevel + zoomDelta animated:animated];
+}
+
+- (void)scaleBy:(double)scaleFactor atPoint:(NSPoint)point animated:(BOOL)animated {
+ [self willChangeValueForKey:@"centerCoordinate"];
+ [self willChangeValueForKey:@"zoomLevel"];
+ mbgl::ScreenCoordinate center(point.x, self.bounds.size.height - point.y);
+ _mbglMap->scaleBy(scaleFactor, center, MGLDurationInSeconds(animated ? MGLAnimationDuration : 0));
+ [self didChangeValueForKey:@"zoomLevel"];
+ [self didChangeValueForKey:@"centerCoordinate"];
+}
+
+- (void)setMinimumZoomLevel:(double)minimumZoomLevel
+{
+ _mbglMap->setMinZoom(minimumZoomLevel);
+ [self validateTileCacheSize];
+}
+
+- (void)setMaximumZoomLevel:(double)maximumZoomLevel
+{
+ _mbglMap->setMaxZoom(maximumZoomLevel);
+ [self validateTileCacheSize];
+}
+
+- (double)maximumZoomLevel {
+ return _mbglMap->getMaxZoom();
+}
+
+- (double)minimumZoomLevel {
+ return _mbglMap->getMinZoom();
+}
+
+/// Respond to a click on the zoom control.
+- (IBAction)zoomInOrOut:(NSSegmentedControl *)sender {
+ switch (sender.selectedSegment) {
+ case 0:
+ // Zoom out.
+ [self moveToEndOfParagraph:sender];
+ break;
+ case 1:
+ // Zoom in.
+ [self moveToBeginningOfParagraph:sender];
+ break;
+ default:
+ break;
+ }
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingDirection {
+ return [NSSet setWithObject:@"camera"];
+}
+
+- (CLLocationDirection)direction {
+ return mbgl::util::wrap(_mbglMap->getBearing(), 0., 360.);
+}
+
+- (void)setDirection:(CLLocationDirection)direction {
+ [self setDirection:direction animated:NO];
+}
+
+- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated {
+ [self willChangeValueForKey:@"direction"];
+ _mbglMap->setBearing(direction,
+ MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets),
+ MGLDurationInSeconds(animated ? MGLAnimationDuration : 0));
+ [self didChangeValueForKey:@"direction"];
+}
+
+- (void)offsetDirectionBy:(CLLocationDegrees)delta animated:(BOOL)animated {
+ [self setDirection:_mbglMap->getBearing() + delta animated:animated];
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCamera {
+ return [NSSet setWithObjects:@"latitude", @"longitude", @"centerCoordinate", @"zoomLevel", @"direction", nil];
+}
+
+- (MGLMapCamera *)camera {
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets);
+ return [self cameraForCameraOptions:_mbglMap->getCameraOptions(padding)];
+}
+
+- (void)setCamera:(MGLMapCamera *)camera {
+ [self setCamera:camera animated:NO];
+}
+
+- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated {
+ [self setCamera:camera withDuration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:NULL];
+}
+
+- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion {
+ _mbglMap->cancelTransitions();
+ if ([self.camera isEqual:camera]) {
+ return;
+ }
+
+ mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera];
+ mbgl::AnimationOptions animationOptions;
+ if (duration > 0) {
+ animationOptions.duration = MGLDurationInSeconds(duration);
+ animationOptions.easing = MGLUnitBezierForMediaTimingFunction(function);
+ }
+ if (completion) {
+ animationOptions.transitionFinishFn = [completion]() {
+ // Must run asynchronously after the transition is completely over.
+ // Otherwise, a call to -setCamera: within the completion handler
+ // would reenter the completion handler’s caller.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion();
+ });
+ };
+ }
+
+ [self willChangeValueForKey:@"camera"];
+ _mbglMap->easeTo(cameraOptions, animationOptions);
+ [self didChangeValueForKey:@"camera"];
+}
+
+- (void)flyToCamera:(MGLMapCamera *)camera completionHandler:(nullable void (^)(void))completion {
+ [self flyToCamera:camera withDuration:-1 completionHandler:completion];
+}
+
+- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration completionHandler:(nullable void (^)(void))completion {
+ [self flyToCamera:camera withDuration:duration peakAltitude:-1 completionHandler:completion];
+}
+
+- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion {
+ _mbglMap->cancelTransitions();
+ if ([self.camera isEqual:camera]) {
+ return;
+ }
+
+ mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera];
+ mbgl::AnimationOptions animationOptions;
+ if (duration >= 0) {
+ animationOptions.duration = MGLDurationInSeconds(duration);
+ }
+ if (peakAltitude >= 0) {
+ CLLocationDegrees peakLatitude = (self.centerCoordinate.latitude + camera.centerCoordinate.latitude) / 2;
+ CLLocationDegrees peakPitch = (self.camera.pitch + camera.pitch) / 2;
+ animationOptions.minZoom = MGLZoomLevelForAltitude(peakAltitude, peakPitch,
+ peakLatitude, self.frame.size);
+ }
+ if (completion) {
+ animationOptions.transitionFinishFn = [completion]() {
+ // Must run asynchronously after the transition is completely over.
+ // Otherwise, a call to -setCamera: within the completion handler
+ // would reenter the completion handler’s caller.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion();
+ });
+ };
+ }
+
+ [self willChangeValueForKey:@"camera"];
+ _mbglMap->flyTo(cameraOptions, animationOptions);
+ [self didChangeValueForKey:@"camera"];
+}
+
+/// Returns a CameraOptions object that specifies parameters for animating to
+/// the given camera.
+- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MGLMapCamera *)camera {
+ mbgl::CameraOptions options;
+ options.center = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate);
+ options.padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets);
+ options.zoom = MGLZoomLevelForAltitude(camera.altitude, camera.pitch,
+ camera.centerCoordinate.latitude,
+ self.frame.size);
+ if (camera.heading >= 0) {
+ options.angle = MGLRadiansFromDegrees(-camera.heading);
+ }
+ if (camera.pitch >= 0) {
+ options.pitch = MGLRadiansFromDegrees(camera.pitch);
+ }
+ return options;
+}
+
++ (NSSet *)keyPathsForValuesAffectingVisibleCoordinateBounds {
+ return [NSSet setWithObjects:@"centerCoordinate", @"zoomLevel", @"direction", @"bounds", nil];
+}
+
+- (MGLCoordinateBounds)visibleCoordinateBounds {
+ return [self convertRect:self.bounds toCoordinateBoundsFromView:self];
+}
+
+- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds {
+ [self setVisibleCoordinateBounds:bounds animated:NO];
+}
+
+- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds animated:(BOOL)animated {
+ [self setVisibleCoordinateBounds:bounds edgePadding:NSEdgeInsetsZero animated:animated];
+}
+
+- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated {
+ _mbglMap->cancelTransitions();
+
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
+ padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets);
+ mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding);
+ mbgl::AnimationOptions animationOptions;
+ if (animated) {
+ animationOptions.duration = MGLDurationInSeconds(MGLAnimationDuration);
+ }
+
+ [self willChangeValueForKey:@"visibleCoordinateBounds"];
+ animationOptions.transitionFinishFn = ^() {
+ [self didChangeValueForKey:@"visibleCoordinateBounds"];
+ };
+ _mbglMap->easeTo(cameraOptions, animationOptions);
+}
+
+- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds {
+ return [self cameraThatFitsCoordinateBounds:bounds edgePadding:NSEdgeInsetsZero];
+}
+
+- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets {
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
+ padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets);
+ mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding);
+ return [self cameraForCameraOptions:cameraOptions];
+}
+
+- (MGLMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions {
+ CLLocationCoordinate2D centerCoordinate = MGLLocationCoordinate2DFromLatLng(cameraOptions.center ? *cameraOptions.center : _mbglMap->getLatLng());
+ double zoomLevel = cameraOptions.zoom ? *cameraOptions.zoom : self.zoomLevel;
+ CLLocationDirection direction = cameraOptions.angle ? -MGLDegreesFromRadians(*cameraOptions.angle) : self.direction;
+ CGFloat pitch = cameraOptions.pitch ? MGLDegreesFromRadians(*cameraOptions.pitch) : _mbglMap->getPitch();
+ CLLocationDistance altitude = MGLAltitudeForZoomLevel(zoomLevel, pitch,
+ centerCoordinate.latitude,
+ self.frame.size);
+ return [MGLMapCamera cameraLookingAtCenterCoordinate:centerCoordinate
+ fromDistance:altitude
+ pitch:pitch
+ heading:direction];
+}
+
+- (void)setAutomaticallyAdjustsContentInsets:(BOOL)automaticallyAdjustsContentInsets {
+ _automaticallyAdjustsContentInsets = automaticallyAdjustsContentInsets;
+ [self adjustContentInsets];
+}
+
+/// Updates `contentInsets` to reflect the current window geometry.
+- (void)adjustContentInsets {
+ if (!_automaticallyAdjustsContentInsets) {
+ return;
+ }
+
+ NSEdgeInsets contentInsets = self.contentInsets;
+ if ((self.window.styleMask & NSFullSizeContentViewWindowMask)
+ && !self.window.titlebarAppearsTransparent) {
+ NSRect contentLayoutRect = [self convertRect:self.window.contentLayoutRect fromView:nil];
+ if (NSMaxX(contentLayoutRect) > 0 && NSMaxY(contentLayoutRect) > 0) {
+ contentInsets = NSEdgeInsetsMake(NSHeight(self.bounds) - NSMaxY(contentLayoutRect),
+ NSMinX(contentLayoutRect),
+ NSMinY(contentLayoutRect),
+ NSWidth(self.bounds) - NSMaxX(contentLayoutRect));
+ }
+ } else {
+ contentInsets = NSEdgeInsetsZero;
+ }
+
+ self.contentInsets = contentInsets;
+}
+
+- (void)setContentInsets:(NSEdgeInsets)contentInsets {
+ [self setContentInsets:contentInsets animated:NO];
+}
+
+- (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated {
+ if (NSEdgeInsetsEqual(contentInsets, self.contentInsets)) {
+ return;
+ }
+
+ // After adjusting the content insets, move the center coordinate from the
+ // old frame of reference to the new one represented by the newly set
+ // content insets.
+ CLLocationCoordinate2D oldCenter = self.centerCoordinate;
+ _contentInsets = contentInsets;
+ [self setCenterCoordinate:oldCenter animated:animated];
+}
+
+#pragma mark Mouse events and gestures
+
+- (BOOL)acceptsFirstResponder {
+ return YES;
+}
+
+/// Drag to pan, plus drag to zoom, rotate, and tilt when a modifier key is held
+/// down.
+- (void)handlePanGesture:(NSPanGestureRecognizer *)gestureRecognizer {
+ NSPoint delta = [gestureRecognizer translationInView:self];
+ NSPoint endPoint = [gestureRecognizer locationInView:self];
+ NSPoint startPoint = NSMakePoint(endPoint.x - delta.x, endPoint.y - delta.y);
+
+ NSEventModifierFlags flags = [NSApp currentEvent].modifierFlags;
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ [self.window invalidateCursorRectsForView:self];
+ _mbglMap->setGestureInProgress(true);
+
+ if (![self isPanningWithGesture]) {
+ // Hide the cursor except when panning.
+ CGDisplayHideCursor(kCGDirectMainDisplay);
+ _didHideCursorDuringGesture = YES;
+ }
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded
+ || gestureRecognizer.state == NSGestureRecognizerStateCancelled) {
+ _mbglMap->setGestureInProgress(false);
+ [self.window invalidateCursorRectsForView:self];
+
+ if (_didHideCursorDuringGesture) {
+ _didHideCursorDuringGesture = NO;
+ // Move the cursor back to the start point and show it again, creating
+ // the illusion that it has stayed in place during the entire gesture.
+ CGPoint cursorPoint = [self convertPoint:startPoint toView:nil];
+ cursorPoint = [self.window convertRectToScreen:{ startPoint, NSZeroSize }].origin;
+ cursorPoint.y = [NSScreen mainScreen].frame.size.height - cursorPoint.y;
+ CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cursorPoint);
+ CGDisplayShowCursor(kCGDirectMainDisplay);
+ }
+ }
+
+ if (flags & NSShiftKeyMask) {
+ // Shift-drag to zoom.
+ if (!self.zoomEnabled) {
+ return;
+ }
+
+ _mbglMap->cancelTransitions();
+
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ _scaleAtBeginningOfGesture = _mbglMap->getScale();
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ CGFloat newZoomLevel = log2f(_scaleAtBeginningOfGesture) - delta.y / 75;
+ [self scaleBy:powf(2, newZoomLevel) / _mbglMap->getScale() atPoint:startPoint animated:NO];
+ }
+ } else if (flags & NSAlternateKeyMask) {
+ // Option-drag to rotate and/or tilt.
+ _mbglMap->cancelTransitions();
+
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ _directionAtBeginningOfGesture = self.direction;
+ _pitchAtBeginningOfGesture = _mbglMap->getPitch();
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ mbgl::ScreenCoordinate center(startPoint.x, self.bounds.size.height - startPoint.y);
+ if (self.rotateEnabled) {
+ CLLocationDirection newDirection = _directionAtBeginningOfGesture - delta.x / 10;
+ [self willChangeValueForKey:@"direction"];
+ _mbglMap->setBearing(newDirection, center);
+ [self didChangeValueForKey:@"direction"];
+ }
+ if (self.pitchEnabled) {
+ _mbglMap->setPitch(_pitchAtBeginningOfGesture + delta.y / 5, center);
+ }
+ }
+ } else if (self.scrollEnabled) {
+ // Otherwise, drag to pan.
+ _mbglMap->cancelTransitions();
+
+ if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ delta.y *= -1;
+ [self offsetCenterCoordinateBy:delta animated:NO];
+ [gestureRecognizer setTranslation:NSZeroPoint inView:nil];
+ }
+ }
+}
+
+/// Returns whether the user is panning using a gesture.
+- (BOOL)isPanningWithGesture {
+ NSGestureRecognizerState state = _panGestureRecognizer.state;
+ NSEventModifierFlags flags = [NSApp currentEvent].modifierFlags;
+ return ((state == NSGestureRecognizerStateBegan || state == NSGestureRecognizerStateChanged)
+ && !(flags & NSShiftKeyMask || flags & NSAlternateKeyMask));
+}
+
+/// Pinch to zoom.
+- (void)handleMagnificationGesture:(NSMagnificationGestureRecognizer *)gestureRecognizer {
+ if (!self.zoomEnabled) {
+ return;
+ }
+
+ _mbglMap->cancelTransitions();
+
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ _mbglMap->setGestureInProgress(true);
+ _scaleAtBeginningOfGesture = _mbglMap->getScale();
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ NSPoint zoomInPoint = [gestureRecognizer locationInView:self];
+ mbgl::ScreenCoordinate center(zoomInPoint.x, self.bounds.size.height - zoomInPoint.y);
+ if (gestureRecognizer.magnification > -1) {
+ [self willChangeValueForKey:@"zoomLevel"];
+ [self willChangeValueForKey:@"centerCoordinate"];
+ _mbglMap->setScale(_scaleAtBeginningOfGesture * (1 + gestureRecognizer.magnification), center);
+ [self didChangeValueForKey:@"centerCoordinate"];
+ [self didChangeValueForKey:@"zoomLevel"];
+ }
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded
+ || gestureRecognizer.state == NSGestureRecognizerStateCancelled) {
+ _mbglMap->setGestureInProgress(false);
+ }
+}
+
+/// Click or tap to select an annotation.
+- (void)handleClickGesture:(NSClickGestureRecognizer *)gestureRecognizer {
+ if (gestureRecognizer.state != NSGestureRecognizerStateEnded
+ || [self subviewContainingGesture:gestureRecognizer]) {
+ return;
+ }
+
+ NSPoint gesturePoint = [gestureRecognizer locationInView:self];
+ MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:gesturePoint persistingResults:YES];
+ if (hitAnnotationTag != MGLAnnotationTagNotFound) {
+ if (hitAnnotationTag != _selectedAnnotationTag) {
+ id <MGLAnnotation> annotation = [self annotationWithTag:hitAnnotationTag];
+ NSAssert(annotation, @"Cannot select nonexistent annotation with tag %u", hitAnnotationTag);
+ [self selectAnnotation:annotation];
+ }
+ } else {
+ [self deselectAnnotation:self.selectedAnnotation];
+ }
+}
+
+/// Double-click or double-tap to zoom in.
+- (void)handleDoubleClickGesture:(NSClickGestureRecognizer *)gestureRecognizer {
+ if (!self.zoomEnabled || gestureRecognizer.state != NSGestureRecognizerStateEnded
+ || [self subviewContainingGesture:gestureRecognizer]) {
+ return;
+ }
+
+ _mbglMap->cancelTransitions();
+
+ NSPoint gesturePoint = [gestureRecognizer locationInView:self];
+ [self scaleBy:2 atPoint:gesturePoint animated:YES];
+}
+
+- (void)smartMagnifyWithEvent:(NSEvent *)event {
+ if (!self.zoomEnabled) {
+ return;
+ }
+
+ _mbglMap->cancelTransitions();
+
+ // Tap with two fingers (“right-click”) to zoom out on mice but not trackpads.
+ NSPoint gesturePoint = [self convertPoint:event.locationInWindow fromView:nil];
+ [self scaleBy:0.5 atPoint:gesturePoint animated:YES];
+}
+
+/// Rotate fingers to rotate.
+- (void)handleRotationGesture:(NSRotationGestureRecognizer *)gestureRecognizer {
+ if (!self.rotateEnabled) {
+ return;
+ }
+
+ _mbglMap->cancelTransitions();
+
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ _mbglMap->setGestureInProgress(true);
+ _directionAtBeginningOfGesture = self.direction;
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ NSPoint rotationPoint = [gestureRecognizer locationInView:self];
+ mbgl::ScreenCoordinate center(rotationPoint.x, self.bounds.size.height - rotationPoint.y);
+ _mbglMap->setBearing(_directionAtBeginningOfGesture + gestureRecognizer.rotationInDegrees, center);
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded
+ || gestureRecognizer.state == NSGestureRecognizerStateCancelled) {
+ _mbglMap->setGestureInProgress(false);
+ }
+}
+
+- (BOOL)wantsScrollEventsForSwipeTrackingOnAxis:(__unused NSEventGestureAxis)axis {
+ // Track both horizontal and vertical swipes in -scrollWheel:.
+ return YES;
+}
+
+- (void)scrollWheel:(NSEvent *)event {
+ // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#10_7Dragging
+ BOOL isScrollWheel = event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone && !event.hasPreciseScrollingDeltas;
+ if (isScrollWheel || [[NSUserDefaults standardUserDefaults] boolForKey:MGLScrollWheelZoomsMapViewDefaultKey]) {
+ // A traditional, vertical scroll wheel zooms instead of panning.
+ if (self.zoomEnabled) {
+ const double delta =
+ event.scrollingDeltaY / ([event hasPreciseScrollingDeltas] ? 100 : 10);
+ if (delta != 0) {
+ double scale = 2.0 / (1.0 + std::exp(-std::abs(delta)));
+
+ // Zooming out.
+ if (delta < 0) {
+ scale = 1.0 / scale;
+ }
+
+ NSPoint gesturePoint = [self convertPoint:event.locationInWindow fromView:nil];
+ [self scaleBy:scale atPoint:gesturePoint animated:NO];
+ }
+ }
+ } else if (self.scrollEnabled
+ && _magnificationGestureRecognizer.state == NSGestureRecognizerStatePossible
+ && _rotationGestureRecognizer.state == NSGestureRecognizerStatePossible) {
+ // Scroll to pan.
+ _mbglMap->cancelTransitions();
+
+ CGFloat x = event.scrollingDeltaX;
+ CGFloat y = event.scrollingDeltaY;
+ if (x || y) {
+ [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO];
+ }
+
+ // Drift pan.
+ if (event.momentumPhase != NSEventPhaseNone) {
+ [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO];
+ }
+ }
+}
+
+/// Returns the subview that the gesture is located in.
+- (NSView *)subviewContainingGesture:(NSGestureRecognizer *)gestureRecognizer {
+ if (NSPointInRect([gestureRecognizer locationInView:self.compass], self.compass.bounds)) {
+ return self.compass;
+ }
+ if (NSPointInRect([gestureRecognizer locationInView:self.zoomControls], self.zoomControls.bounds)) {
+ return self.zoomControls;
+ }
+ if (NSPointInRect([gestureRecognizer locationInView:self.attributionView], self.attributionView.bounds)) {
+ return self.attributionView;
+ }
+ return nil;
+}
+
+#pragma mark Keyboard events
+
+- (void)keyDown:(NSEvent *)event {
+ if (event.modifierFlags & NSNumericPadKeyMask) {
+ // This is the recommended way to handle arrow key presses, causing
+ // methods like -moveUp: and -moveToBeginningOfParagraph: to be called
+ // for various standard keybindings.
+ [self interpretKeyEvents:@[event]];
+ } else {
+ [super keyDown:event];
+ }
+}
+
+- (IBAction)moveUp:(__unused id)sender {
+ [self offsetCenterCoordinateBy:NSMakePoint(0, MGLKeyPanningIncrement) animated:YES];
+}
+
+- (IBAction)moveDown:(__unused id)sender {
+ [self offsetCenterCoordinateBy:NSMakePoint(0, -MGLKeyPanningIncrement) animated:YES];
+}
+
+- (IBAction)moveLeft:(__unused id)sender {
+ [self offsetCenterCoordinateBy:NSMakePoint(MGLKeyPanningIncrement, 0) animated:YES];
+}
+
+- (IBAction)moveRight:(__unused id)sender {
+ [self offsetCenterCoordinateBy:NSMakePoint(-MGLKeyPanningIncrement, 0) animated:YES];
+}
+
+- (IBAction)moveToBeginningOfParagraph:(__unused id)sender {
+ if (self.zoomEnabled) {
+ [self zoomBy:1 animated:YES];
+ }
+}
+
+- (IBAction)moveToEndOfParagraph:(__unused id)sender {
+ if (self.zoomEnabled) {
+ [self zoomBy:-1 animated:YES];
+ }
+}
+
+- (IBAction)moveWordLeft:(__unused id)sender {
+ if (self.rotateEnabled) {
+ [self offsetDirectionBy:MGLKeyRotationIncrement animated:YES];
+ }
+}
+
+- (IBAction)moveWordRight:(__unused id)sender {
+ if (self.rotateEnabled) {
+ [self offsetDirectionBy:-MGLKeyRotationIncrement animated:YES];
+ }
+}
+
+- (void)setZoomEnabled:(BOOL)zoomEnabled {
+ _zoomEnabled = zoomEnabled;
+ _zoomControls.enabled = zoomEnabled;
+ _zoomControls.hidden = !zoomEnabled;
+}
+
+- (void)setRotateEnabled:(BOOL)rotateEnabled {
+ _rotateEnabled = rotateEnabled;
+ _compass.enabled = rotateEnabled;
+ _compass.hidden = !rotateEnabled;
+}
+
+#pragma mark Ornaments
+
+/// Updates the zoom controls’ enabled state based on the current zoom level.
+- (void)updateZoomControls {
+ [_zoomControls setEnabled:self.zoomLevel > self.minimumZoomLevel forSegment:0];
+ [_zoomControls setEnabled:self.zoomLevel < self.maximumZoomLevel forSegment:1];
+}
+
+/// Updates the compass to point in the same direction as the map.
+- (void)updateCompass {
+ // The circular slider control goes counterclockwise, whereas our map
+ // measures its direction clockwise.
+ _compass.doubleValue = -self.direction;
+}
+
+- (IBAction)rotate:(NSSlider *)sender {
+ [self setDirection:-sender.doubleValue animated:YES];
+}
+
+#pragma mark Annotations
+
+- (nullable NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
+ if (_annotationContextsByAnnotationTag.empty()) {
+ return nil;
+ }
+
+ // Map all the annotation tags to the annotations themselves.
+ std::vector<id <MGLAnnotation>> annotations;
+ std::transform(_annotationContextsByAnnotationTag.begin(),
+ _annotationContextsByAnnotationTag.end(),
+ std::back_inserter(annotations),
+ ^ id <MGLAnnotation> (const std::pair<MGLAnnotationTag, MGLAnnotationContext> &pair) {
+ return pair.second.annotation;
+ });
+ return [NSArray arrayWithObjects:&annotations[0] count:annotations.size()];
+}
+
+/// Returns the annotation assigned the given tag. Cheap.
+- (id <MGLAnnotation>)annotationWithTag:(MGLAnnotationTag)tag {
+ if (!_annotationContextsByAnnotationTag.count(tag)) {
+ return nil;
+ }
+
+ MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag[tag];
+ return annotationContext.annotation;
+}
+
+/// Returns the annotation tag assigned to the given annotation. Relatively expensive.
+- (MGLAnnotationTag)annotationTagForAnnotation:(id <MGLAnnotation>)annotation {
+ if (!annotation) {
+ return MGLAnnotationTagNotFound;
+ }
+
+ for (auto &pair : _annotationContextsByAnnotationTag) {
+ if (pair.second.annotation == annotation) {
+ return pair.first;
+ }
+ }
+ return MGLAnnotationTagNotFound;
+}
+
+- (void)addAnnotation:(id <MGLAnnotation>)annotation {
+ if (annotation) {
+ [self addAnnotations:@[annotation]];
+ }
+}
+
+- (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
+ if (!annotations) {
+ return;
+ }
+
+ [self willChangeValueForKey:@"annotations"];
+
+ BOOL delegateHasImagesForAnnotations = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)];
+
+ for (id <MGLAnnotation> annotation in annotations) {
+ NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation");
+
+ if ([annotation isKindOfClass:[MGLMultiPoint class]]) {
+ // Actual multipoints aren’t supported as annotations.
+ if ([annotation isMemberOfClass:[MGLMultiPoint class]]
+ || [annotation isMemberOfClass:[MGLMultiPointFeature class]]) {
+ continue;
+ }
+
+ // The multipoint knows how to style itself (with the map view’s help).
+ MGLMultiPoint *multiPoint = (MGLMultiPoint *)annotation;
+ if (!multiPoint.pointCount) {
+ continue;
+ }
+
+ MGLAnnotationTag annotationTag = _mbglMap->addAnnotation([multiPoint annotationObjectWithDelegate:self]);
+ MGLAnnotationContext context;
+ context.annotation = annotation;
+ _annotationContextsByAnnotationTag[annotationTag] = context;
+ } else if (![annotation isKindOfClass:[MGLMultiPolyline class]]
+ || ![annotation isKindOfClass:[MGLMultiPolygon class]]
+ || ![annotation isKindOfClass:[MGLShapeCollection class]]) {
+ MGLAnnotationImage *annotationImage = nil;
+ if (delegateHasImagesForAnnotations) {
+ annotationImage = [self.delegate mapView:self imageForAnnotation:annotation];
+ }
+ if (!annotationImage) {
+ annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName];
+ }
+ if (!annotationImage) {
+ annotationImage = self.defaultAnnotationImage;
+ }
+
+ NSString *symbolName = annotationImage.styleIconIdentifier;
+ if (!symbolName) {
+ symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
+ annotationImage.styleIconIdentifier = symbolName;
+ }
+
+ if (!self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) {
+ self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage;
+ [self installAnnotationImage:annotationImage];
+ }
+
+ MGLAnnotationTag annotationTag = _mbglMap->addAnnotation(mbgl::SymbolAnnotation {
+ MGLPointFromLocationCoordinate2D(annotation.coordinate),
+ symbolName.UTF8String ?: ""
+ });
+
+ MGLAnnotationContext context;
+ context.annotation = annotation;
+ context.imageReuseIdentifier = annotationImage.reuseIdentifier;
+ _annotationContextsByAnnotationTag[annotationTag] = context;
+
+ if ([annotation isKindOfClass:[NSObject class]]) {
+ NSAssert(![annotation isKindOfClass:[MGLMultiPoint class]], @"Point annotation should not be MGLMultiPoint.");
+ [(NSObject *)annotation addObserver:self forKeyPath:@"coordinate" options:0 context:(void *)(NSUInteger)annotationTag];
+ }
+
+ // Opt into potentially expensive tooltip tracking areas.
+ if (annotation.toolTip.length) {
+ _wantsToolTipRects = YES;
+ }
+ }
+ }
+
+ [self didChangeValueForKey:@"annotations"];
+
+ [self updateAnnotationTrackingAreas];
+}
+
+/// Initializes and returns a default annotation image that depicts a round pin
+/// rising from the center, with a shadow slightly below center. The alignment
+/// rect therefore excludes the bottom half.
+- (MGLAnnotationImage *)defaultAnnotationImage {
+ NSImage *image = MGLDefaultMarkerImage();
+ NSRect alignmentRect = image.alignmentRect;
+ alignmentRect.origin.y = NSMidY(alignmentRect);
+ alignmentRect.size.height /= 2;
+ image.alignmentRect = alignmentRect;
+ return [MGLAnnotationImage annotationImageWithImage:image
+ reuseIdentifier:MGLDefaultStyleMarkerSymbolName];
+}
+
+/// Sends the raw pixel data of the annotation image’s image to mbgl and
+/// calculates state needed for hit testing later.
+- (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage {
+ NSString *iconIdentifier = annotationImage.styleIconIdentifier;
+ self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage;
+
+ NSImage *image = annotationImage.image;
+ NSSize size = image.size;
+ if (size.width == 0 || size.height == 0 || !image.valid) {
+ // Can’t create an empty sprite. An image that hasn’t loaded is also useless.
+ return;
+ }
+
+ // Create a bitmap image representation from the image, respecting backing
+ // scale factor and any resizing done on the image at runtime.
+ // http://www.cocoabuilder.com/archive/cocoa/82430-nsimage-getting-raw-bitmap-data.html#82431
+ [image lockFocus];
+ NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:{ NSZeroPoint, size }];
+ [image unlockFocus];
+
+ // Get the image’s raw pixel data as an RGBA buffer.
+ std::string pixelString((const char *)rep.bitmapData, rep.pixelsWide * rep.pixelsHigh * 4 /* RGBA */);
+
+ mbgl::PremultipliedImage cPremultipliedImage(rep.pixelsWide, rep.pixelsHigh);
+ std::copy(rep.bitmapData, rep.bitmapData + cPremultipliedImage.size(), cPremultipliedImage.data.get());
+ auto cSpriteImage = std::make_shared<mbgl::SpriteImage>(std::move(cPremultipliedImage),
+ (float)(rep.pixelsWide / size.width));
+ _mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, cSpriteImage);
+
+ // Create a slop area with a “radius” equal to the annotation image’s entire
+ // size, allowing the eventual click to be on any point within this image.
+ // Union this slop area with any existing slop areas.
+ _unionedAnnotationImageSize = NSMakeSize(MAX(_unionedAnnotationImageSize.width, size.width),
+ MAX(_unionedAnnotationImageSize.height, size.height));
+
+ // Opt into potentially expensive cursor tracking areas.
+ if (annotationImage.cursor) {
+ _wantsCursorRects = YES;
+ }
+}
+
+- (void)removeAnnotation:(id <MGLAnnotation>)annotation {
+ if (annotation) {
+ [self removeAnnotations:@[annotation]];
+ }
+}
+
+- (void)removeAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
+ if (!annotations) {
+ return;
+ }
+
+ [self willChangeValueForKey:@"annotations"];
+
+ for (id <MGLAnnotation> annotation in annotations) {
+ NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation");
+
+ MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
+ NSAssert(annotationTag != MGLAnnotationTagNotFound, @"No ID for annotation %@", annotation);
+
+ if (annotationTag == _selectedAnnotationTag) {
+ [self deselectAnnotation:annotation];
+ }
+ if (annotationTag == _lastSelectedAnnotationTag) {
+ _lastSelectedAnnotationTag = MGLAnnotationTagNotFound;
+ }
+
+ _annotationContextsByAnnotationTag.erase(annotationTag);
+
+ if ([annotation isKindOfClass:[NSObject class]] &&
+ ![annotation isKindOfClass:[MGLMultiPoint class]]) {
+ [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinate" context:(void *)(NSUInteger)annotationTag];
+ }
+
+ _mbglMap->removeAnnotation(annotationTag);
+ }
+
+ [self didChangeValueForKey:@"annotations"];
+
+ [self updateAnnotationTrackingAreas];
+}
+
+- (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier {
+ return self.annotationImagesByIdentifier[identifier];
+}
+
+- (id <MGLAnnotation>)annotationAtPoint:(NSPoint)point {
+ return [self annotationWithTag:[self annotationTagAtPoint:point persistingResults:NO]];
+}
+
+/**
+ Returns the tag of the annotation at the given point in the view.
+
+ This is more involved than it sounds: if multiple point annotations overlap
+ near the point, this method cycles through them so that each of them is
+ accessible to the user at some point.
+
+ @param persist True to remember the cycleable set of annotations, so that a
+ different annotation is returned the next time this method is called
+ with the same point. Setting this parameter to false is useful for
+ asking “what if?”
+ */
+- (MGLAnnotationTag)annotationTagAtPoint:(NSPoint)point persistingResults:(BOOL)persist {
+ // Look for any annotation near the click. An annotation is “near” if the
+ // distance between its center and the click is less than the maximum height
+ // or width of an installed annotation image.
+ NSRect queryRect = NSInsetRect({ point, NSZeroSize },
+ -_unionedAnnotationImageSize.width / 2,
+ -_unionedAnnotationImageSize.height / 2);
+ queryRect = NSInsetRect(queryRect, -MGLAnnotationImagePaddingForHitTest,
+ -MGLAnnotationImagePaddingForHitTest);
+ std::vector<MGLAnnotationTag> nearbyAnnotations = [self annotationTagsInRect:queryRect];
+
+ if (nearbyAnnotations.size()) {
+ // Assume that the user is fat-fingering an annotation.
+ NSRect hitRect = NSInsetRect({ point, NSZeroSize },
+ -MGLAnnotationImagePaddingForHitTest,
+ -MGLAnnotationImagePaddingForHitTest);
+
+ // Filter out any annotation whose image is unselectable or for which
+ // hit testing fails.
+ auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag) {
+ NSAssert(_annotationContextsByAnnotationTag.count(annotationTag) != 0, @"Unknown annotation found nearby click");
+ id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
+ if (!annotation) {
+ return true;
+ }
+
+ MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
+ if (!annotationImage.selectable) {
+ return true;
+ }
+
+ // Filter out the annotation if the fattened finger didn’t land on a
+ // translucent or opaque pixel in the image.
+ NSRect annotationRect = [self frameOfImage:annotationImage.image
+ centeredAtCoordinate:annotation.coordinate];
+ return !!![annotationImage.image hitTestRect:hitRect withImageDestinationRect:annotationRect
+ context:nil hints:nil flipped:NO];
+ });
+ nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end));
+ }
+
+ MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound;
+ if (nearbyAnnotations.size()) {
+ // The annotation tags need to be stable in order to compare them with
+ // the remembered tags.
+ std::sort(nearbyAnnotations.begin(), nearbyAnnotations.end());
+
+ if (nearbyAnnotations == _annotationsNearbyLastClick) {
+ // The first selection in the cycle should be the one nearest to the
+ // click.
+ CLLocationCoordinate2D currentCoordinate = [self convertPoint:point toCoordinateFromView:self];
+ std::sort(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag tagA, const MGLAnnotationTag tagB) {
+ CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate];
+ CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate];
+ CLLocationDegrees distanceA = hypot(coordinateA.latitude - currentCoordinate.latitude,
+ coordinateA.longitude - currentCoordinate.longitude);
+ CLLocationDegrees distanceB = hypot(coordinateB.latitude - currentCoordinate.latitude,
+ coordinateB.longitude - currentCoordinate.longitude);
+ return distanceA < distanceB;
+ });
+
+ // The last time we persisted a set of annotations, we had the same
+ // set of annotations as we do now. Cycle through them.
+ if (_lastSelectedAnnotationTag == MGLAnnotationTagNotFound
+ || _lastSelectedAnnotationTag == _annotationsNearbyLastClick.back()) {
+ // Either no annotation is selected or the last annotation in
+ // the set was selected. Wrap around to the first annotation in
+ // the set.
+ hitAnnotationTag = _annotationsNearbyLastClick.front();
+ } else {
+ auto result = std::find(_annotationsNearbyLastClick.begin(),
+ _annotationsNearbyLastClick.end(),
+ _lastSelectedAnnotationTag);
+ if (result == _annotationsNearbyLastClick.end()) {
+ // An annotation from this set hasn’t been selected before.
+ // Select the first (nearest) one.
+ hitAnnotationTag = _annotationsNearbyLastClick.front();
+ } else {
+ // Step to the next annotation in the set.
+ auto distance = std::distance(_annotationsNearbyLastClick.begin(), result);
+ hitAnnotationTag = _annotationsNearbyLastClick[distance + 1];
+ }
+ }
+ } else {
+ // Remember the nearby annotations for the next time this method is
+ // called.
+ if (persist) {
+ _annotationsNearbyLastClick = nearbyAnnotations;
+ }
+
+ // Choose the first nearby annotation.
+ if (nearbyAnnotations.size()) {
+ hitAnnotationTag = nearbyAnnotations.front();
+ }
+ }
+ }
+
+ return hitAnnotationTag;
+}
+
+/// Returns the tags of the annotations coincident with the given rectangle.
+- (std::vector<MGLAnnotationTag>)annotationTagsInRect:(NSRect)rect {
+ mbgl::LatLngBounds queryBounds = [self convertRect:rect toLatLngBoundsFromView:self];
+ return _mbglMap->getPointAnnotationsInBounds(queryBounds);
+}
+
+- (id <MGLAnnotation>)selectedAnnotation {
+ if (!_annotationContextsByAnnotationTag.count(_selectedAnnotationTag)) {
+ return nil;
+ }
+ MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(_selectedAnnotationTag);
+ return annotationContext.annotation;
+}
+
+- (void)setSelectedAnnotation:(id <MGLAnnotation>)annotation {
+ [self willChangeValueForKey:@"selectedAnnotations"];
+ _selectedAnnotationTag = [self annotationTagForAnnotation:annotation];
+ if (_selectedAnnotationTag != MGLAnnotationTagNotFound) {
+ _lastSelectedAnnotationTag = _selectedAnnotationTag;
+ }
+ [self didChangeValueForKey:@"selectedAnnotations"];
+}
+
+- (NS_ARRAY_OF(id <MGLAnnotation>) *)selectedAnnotations {
+ id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation;
+ return selectedAnnotation ? @[selectedAnnotation] : @[];
+}
+
+- (void)setSelectedAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)selectedAnnotations {
+ if (!selectedAnnotations.count) {
+ return;
+ }
+
+ id <MGLAnnotation> firstAnnotation = selectedAnnotations[0];
+ NSAssert([firstAnnotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation");
+ if ([firstAnnotation isKindOfClass:[MGLMultiPoint class]]) {
+ return;
+ }
+
+ // Select the annotation if it’s visible.
+ if (MGLCoordinateInCoordinateBounds(firstAnnotation.coordinate, self.visibleCoordinateBounds)) {
+ [self selectAnnotation:firstAnnotation];
+ }
+}
+
+- (void)selectAnnotation:(id <MGLAnnotation>)annotation
+{
+ // Only point annotations can be selected.
+ if (!annotation || [annotation isKindOfClass:[MGLMultiPoint class]]) {
+ return;
+ }
+
+ id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation;
+ if (annotation == selectedAnnotation) {
+ return;
+ }
+
+ // Deselect the annotation before reselecting it.
+ [self deselectAnnotation:selectedAnnotation];
+
+ // Add the annotation to the map if it hasn’t been added yet.
+ MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
+ if (annotationTag == MGLAnnotationTagNotFound) {
+ [self addAnnotation:annotation];
+ }
+
+ // The annotation can’t be selected if no part of it is hittable.
+ NSRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag];
+ if (NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) {
+ return;
+ }
+
+ self.selectedAnnotation = annotation;
+
+ // For the callout to be shown, the annotation must have a title, its
+ // callout must not already be shown, and the annotation must be able to
+ // show a callout according to the delegate.
+ if ([annotation respondsToSelector:@selector(title)]
+ && annotation.title
+ && !self.calloutForSelectedAnnotation.shown
+ && [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)]
+ && [self.delegate mapView:self annotationCanShowCallout:annotation]) {
+ NSPopover *callout = [self calloutForAnnotation:annotation];
+
+ // Hang the callout off the right edge of the annotation image’s
+ // alignment rect, or off the left edge in a right-to-left UI.
+ callout.delegate = self;
+ self.calloutForSelectedAnnotation = callout;
+ NSRectEdge edge = (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft
+ ? NSMinXEdge
+ : NSMaxXEdge);
+ [callout showRelativeToRect:positioningRect ofView:self preferredEdge:edge];
+ }
+}
+
+/// Returns a popover detailing the annotation.
+- (NSPopover *)calloutForAnnotation:(id <MGLAnnotation>)annotation {
+ NSPopover *callout = [[NSPopover alloc] init];
+ callout.behavior = NSPopoverBehaviorTransient;
+
+ NSViewController *viewController;
+ if ([self.delegate respondsToSelector:@selector(mapView:calloutViewControllerForAnnotation:)]) {
+ NSViewController *viewControllerFromDelegate = [self.delegate mapView:self
+ calloutViewControllerForAnnotation:annotation];
+ if (viewControllerFromDelegate) {
+ viewController = viewControllerFromDelegate;
+ }
+ }
+ if (!viewController) {
+ viewController = self.calloutViewController;
+ }
+ NSAssert(viewController, @"Unable to load MGLAnnotationCallout view controller");
+ // The popover’s view controller can bind to KVO-compliant key paths of the
+ // annotation.
+ viewController.representedObject = annotation;
+ callout.contentViewController = viewController;
+
+ return callout;
+}
+
+- (NSViewController *)calloutViewController {
+ // Lazily load a default view controller.
+ if (!_calloutViewController) {
+ _calloutViewController = [[NSViewController alloc] initWithNibName:@"MGLAnnotationCallout"
+ bundle:[NSBundle mgl_frameworkBundle]];
+ }
+ return _calloutViewController;
+}
+
+/// Returns the rectangle that represents the annotation image of the annotation
+/// with the given tag. This rectangle is fitted to the image’s alignment rect
+/// and is appropriate for positioning a popover.
+- (NSRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annotationTag {
+ id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
+ if (!annotation) {
+ return NSZeroRect;
+ }
+ NSImage *image = [self imageOfAnnotationWithTag:annotationTag].image;
+ if (!image) {
+ image = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName].image;
+ }
+ if (!image) {
+ return NSZeroRect;
+ }
+
+ NSRect positioningRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate];
+ positioningRect = NSOffsetRect(image.alignmentRect, positioningRect.origin.x, positioningRect.origin.y);
+ return NSInsetRect(positioningRect, -MGLAnnotationImagePaddingForCallout,
+ -MGLAnnotationImagePaddingForCallout);
+}
+
+/// Returns the rectangle relative to the viewport that represents the given
+/// image centered at the given coordinate.
+- (NSRect)frameOfImage:(NSImage *)image centeredAtCoordinate:(CLLocationCoordinate2D)coordinate {
+ NSPoint calloutAnchorPoint = [self convertCoordinate:coordinate toPointToView:self];
+ return NSInsetRect({ calloutAnchorPoint, NSZeroSize }, -image.size.width / 2, -image.size.height / 2);
+}
+
+/// Returns the annotation image assigned to the annotation with the given tag.
+- (MGLAnnotationImage *)imageOfAnnotationWithTag:(MGLAnnotationTag)annotationTag {
+ if (annotationTag == MGLAnnotationTagNotFound
+ || _annotationContextsByAnnotationTag.count(annotationTag) == 0) {
+ return nil;
+ }
+
+ NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier;
+ NSString *symbolName = customSymbol.length ? customSymbol : MGLDefaultStyleMarkerSymbolName;
+
+ return [self dequeueReusableAnnotationImageWithIdentifier:symbolName];
+}
+
+- (void)deselectAnnotation:(id <MGLAnnotation>)annotation {
+ if (!annotation || self.selectedAnnotation != annotation) {
+ return;
+ }
+
+ // Close the callout popover gracefully.
+ NSPopover *callout = self.calloutForSelectedAnnotation;
+ [callout performClose:self];
+
+ self.selectedAnnotation = nil;
+}
+
+/// Move the annotation callout to point to the selected annotation at its
+/// current position.
+- (void)updateAnnotationCallouts {
+ NSPopover *callout = self.calloutForSelectedAnnotation;
+ if (callout) {
+ callout.positioningRect = [self positioningRectForCalloutForAnnotationWithTag:_selectedAnnotationTag];
+ }
+}
+
+#pragma mark MGLMultiPointDelegate methods
+
+- (double)alphaForShapeAnnotation:(MGLShape *)annotation {
+ if (_delegateHasAlphasForShapeAnnotations) {
+ return [self.delegate mapView:self alphaForShapeAnnotation:annotation];
+ }
+ return 1.0;
+}
+
+- (mbgl::Color)strokeColorForShapeAnnotation:(MGLShape *)annotation {
+ NSColor *color = (_delegateHasStrokeColorsForShapeAnnotations
+ ? [self.delegate mapView:self strokeColorForShapeAnnotation:annotation]
+ : [NSColor selectedMenuItemColor]);
+ return MGLColorObjectFromNSColor(color);
+}
+
+- (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation {
+ NSColor *color = (_delegateHasFillColorsForShapeAnnotations
+ ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation]
+ : [NSColor selectedMenuItemColor]);
+ return MGLColorObjectFromNSColor(color);
+}
+
+- (CGFloat)lineWidthForPolylineAnnotation:(MGLPolyline *)annotation {
+ if (_delegateHasLineWidthsForShapeAnnotations) {
+ return [self.delegate mapView:self lineWidthForPolylineAnnotation:(MGLPolyline *)annotation];
+ }
+ return 3.0;
+}
+
+#pragma mark MGLPopoverDelegate methods
+
+- (void)popoverDidShow:(__unused NSNotification *)notification {
+ id <MGLAnnotation> annotation = self.selectedAnnotation;
+ if (annotation && [self.delegate respondsToSelector:@selector(mapView:didSelectAnnotation:)]) {
+ [self.delegate mapView:self didSelectAnnotation:annotation];
+ }
+}
+
+- (void)popoverDidClose:(__unused NSNotification *)notification {
+ // Deselect the closed popover, in case the popover was closed due to user
+ // action.
+ id <MGLAnnotation> annotation = self.calloutForSelectedAnnotation.contentViewController.representedObject;
+ self.calloutForSelectedAnnotation = nil;
+ self.selectedAnnotation = nil;
+
+ if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)]) {
+ [self.delegate mapView:self didDeselectAnnotation:annotation];
+ }
+}
+
+#pragma mark Overlays
+
+- (void)addOverlay:(id <MGLOverlay>)overlay {
+ [self addOverlays:@[overlay]];
+}
+
+- (void)addOverlays:(NS_ARRAY_OF(id <MGLOverlay>) *)overlays
+{
+ for (id <MGLOverlay> overlay in overlays) {
+ NSAssert([overlay conformsToProtocol:@protocol(MGLOverlay)], @"Overlay does not conform to MGLOverlay");
+ }
+ [self addAnnotations:overlays];
+}
+
+- (void)removeOverlay:(id <MGLOverlay>)overlay {
+ [self removeOverlays:@[overlay]];
+}
+
+- (void)removeOverlays:(NS_ARRAY_OF(id <MGLOverlay>) *)overlays {
+ for (id <MGLOverlay> overlay in overlays) {
+ NSAssert([overlay conformsToProtocol:@protocol(MGLOverlay)], @"Overlay does not conform to MGLOverlay");
+ }
+ [self removeAnnotations:overlays];
+}
+
+#pragma mark Tooltips and cursors
+
+- (void)updateAnnotationTrackingAreas {
+ if (_wantsToolTipRects) {
+ [self removeAllToolTips];
+ std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:self.bounds];
+ for (MGLAnnotationTag annotationTag : annotationTags) {
+ MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
+ id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
+ if (annotation.toolTip.length) {
+ // Add a tooltip tracking area over the annotation image’s
+ // frame, accounting for the image’s alignment rect.
+ NSImage *image = annotationImage.image;
+ NSRect annotationRect = [self frameOfImage:image
+ centeredAtCoordinate:annotation.coordinate];
+ annotationRect = NSOffsetRect(image.alignmentRect, annotationRect.origin.x, annotationRect.origin.y);
+ if (!NSIsEmptyRect(annotationRect)) {
+ [self addToolTipRect:annotationRect owner:self userData:(void *)(NSUInteger)annotationTag];
+ }
+ }
+ // Opt into potentially expensive cursor tracking areas.
+ if (annotationImage.cursor) {
+ _wantsCursorRects = YES;
+ }
+ }
+ }
+
+ // Blow away any cursor tracking areas and rebuild them. That’s the
+ // potentially expensive part.
+ if (_wantsCursorRects) {
+ [self.window invalidateCursorRectsForView:self];
+ }
+}
+
+- (NSString *)view:(__unused NSView *)view stringForToolTip:(__unused NSToolTipTag)tag point:(__unused NSPoint)point userData:(void *)data {
+ NSAssert((NSUInteger)data < MGLAnnotationTagNotFound, @"Invalid annotation tag in tooltip rect user data.");
+ MGLAnnotationTag annotationTag = (MGLAnnotationTag)MIN((NSUInteger)data, MGLAnnotationTagNotFound);
+ id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
+ return annotation.toolTip;
+}
+
+- (void)resetCursorRects {
+ // Drag to pan has a grabbing hand cursor.
+ if ([self isPanningWithGesture]) {
+ [self addCursorRect:self.bounds cursor:[NSCursor closedHandCursor]];
+ return;
+ }
+
+ // The rest of this method can be expensive, so bail if no annotations have
+ // ever had custom cursors.
+ if (!_wantsCursorRects) {
+ return;
+ }
+
+ std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:self.bounds];
+ for (MGLAnnotationTag annotationTag : annotationTags) {
+ id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
+ MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
+ if (annotationImage.cursor) {
+ // Add a cursor tracking area over the annotation image, respecting
+ // the image’s alignment rect.
+ NSImage *image = annotationImage.image;
+ NSRect annotationRect = [self frameOfImage:image
+ centeredAtCoordinate:annotation.coordinate];
+ annotationRect = NSOffsetRect(image.alignmentRect, annotationRect.origin.x, annotationRect.origin.y);
+ [self addCursorRect:annotationRect cursor:annotationImage.cursor];
+ }
+ }
+}
+
+#pragma mark Data
+
+- (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesAtPoint:(NSPoint)point {
+ return [self visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:nil];
+}
+
+- (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesAtPoint:(NSPoint)point inStyleLayersWithIdentifiers:(NS_SET_OF(NSString *) *)styleLayerIdentifiers {
+ // Cocoa origin is at the lower-left corner.
+ mbgl::ScreenCoordinate screenCoordinate = { point.x, NSHeight(self.bounds) - point.y };
+
+ mbgl::optional<std::vector<std::string>> optionalLayerIDs;
+ if (styleLayerIdentifiers) {
+ __block std::vector<std::string> layerIDs;
+ layerIDs.reserve(styleLayerIdentifiers.count);
+ [styleLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) {
+ layerIDs.push_back(identifier.UTF8String);
+ }];
+ optionalLayerIDs = layerIDs;
+ }
+
+ std::vector<mbgl::Feature> features = _mbglMap->queryRenderedFeatures(screenCoordinate, optionalLayerIDs);
+ return MGLFeaturesFromMBGLFeatures(features);
+}
+
+- (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesInRect:(NSRect)rect {
+ return [self visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:nil];
+}
+
+- (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesInRect:(NSRect)rect inStyleLayersWithIdentifiers:(NS_SET_OF(NSString *) *)styleLayerIdentifiers {
+ // Cocoa origin is at the lower-left corner.
+ mbgl::ScreenBox screenBox = {
+ { NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) },
+ { NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) },
+ };
+
+ mbgl::optional<std::vector<std::string>> optionalLayerIDs;
+ if (styleLayerIdentifiers) {
+ __block std::vector<std::string> layerIDs;
+ layerIDs.reserve(styleLayerIdentifiers.count);
+ [styleLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) {
+ layerIDs.push_back(identifier.UTF8String);
+ }];
+ optionalLayerIDs = layerIDs;
+ }
+
+ std::vector<mbgl::Feature> features = _mbglMap->queryRenderedFeatures(screenBox, optionalLayerIDs);
+ return MGLFeaturesFromMBGLFeatures(features);
+}
+
+#pragma mark Interface Builder methods
+
+- (void)prepareForInterfaceBuilder {
+ [super prepareForInterfaceBuilder];
+
+ // Color the background a glorious Mapbox teal.
+ self.layer.borderColor = [NSColor colorWithRed:59/255.
+ green:178/255.
+ blue:208/255.
+ alpha:0.8].CGColor;
+ self.layer.borderWidth = 2;
+ self.layer.backgroundColor = [NSColor colorWithRed:59/255.
+ green:178/255.
+ blue:208/255.
+ alpha:0.6].CGColor;
+
+ // Place a playful marker right smack dab in the middle.
+ self.layer.contents = MGLDefaultMarkerImage();
+ self.layer.contentsGravity = kCAGravityCenter;
+ self.layer.contentsScale = [NSScreen mainScreen].backingScaleFactor;
+}
+
+#pragma mark Geometric methods
+
+- (NSPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable NSView *)view {
+ return [self convertLatLng:MGLLatLngFromLocationCoordinate2D(coordinate) toPointToView:view];
+}
+
+/// Converts a geographic coordinate to a point in the view’s coordinate system.
+- (NSPoint)convertLatLng:(mbgl::LatLng)latLng toPointToView:(nullable NSView *)view {
+ mbgl::ScreenCoordinate pixel = _mbglMap->pixelForLatLng(latLng);
+ // Cocoa origin is at the lower-left corner.
+ pixel.y = NSHeight(self.bounds) - pixel.y;
+ return [self convertPoint:NSMakePoint(pixel.x, pixel.y) toView:view];
+}
+
+- (CLLocationCoordinate2D)convertPoint:(NSPoint)point toCoordinateFromView:(nullable NSView *)view {
+ return MGLLocationCoordinate2DFromLatLng([self convertPoint:point toLatLngFromView:view]);
+}
+
+/// Converts a point in the view’s coordinate system to a geographic coordinate.
+- (mbgl::LatLng)convertPoint:(NSPoint)point toLatLngFromView:(nullable NSView *)view {
+ NSPoint convertedPoint = [self convertPoint:point fromView:view];
+ return _mbglMap->latLngForPixel({
+ convertedPoint.x,
+ // mbgl origin is at the top-left corner.
+ NSHeight(self.bounds) - convertedPoint.y,
+ }).wrapped();
+}
+
+- (NSRect)convertCoordinateBounds:(MGLCoordinateBounds)bounds toRectToView:(nullable NSView *)view {
+ return [self convertLatLngBounds:MGLLatLngBoundsFromCoordinateBounds(bounds) toRectToView:view];
+}
+
+/// Converts a geographic bounding box to a rectangle in the view’s coordinate
+/// system.
+- (NSRect)convertLatLngBounds:(mbgl::LatLngBounds)bounds toRectToView:(nullable NSView *)view {
+ NSRect rect = { [self convertLatLng:bounds.southwest() toPointToView:view], NSZeroSize };
+ rect = MGLExtendRect(rect, [self convertLatLng:bounds.northeast() toPointToView:view]);
+ return rect;
+}
+
+- (MGLCoordinateBounds)convertRect:(NSRect)rect toCoordinateBoundsFromView:(nullable NSView *)view {
+ return MGLCoordinateBoundsFromLatLngBounds([self convertRect:rect toLatLngBoundsFromView:view]);
+}
+
+/// Converts a rectangle in the given view’s coordinate system to a geographic
+/// bounding box.
+- (mbgl::LatLngBounds)convertRect:(NSRect)rect toLatLngBoundsFromView:(nullable NSView *)view {
+ mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
+ bounds.extend([self convertPoint:rect.origin toLatLngFromView:view]);
+ bounds.extend([self convertPoint:{ NSMaxX(rect), NSMinY(rect) } toLatLngFromView:view]);
+ bounds.extend([self convertPoint:{ NSMaxX(rect), NSMaxY(rect) } toLatLngFromView:view]);
+ bounds.extend([self convertPoint:{ NSMinX(rect), NSMaxY(rect) } toLatLngFromView:view]);
+
+ // The world is wrapping if a point just outside the bounds is also within
+ // the rect.
+ mbgl::LatLng outsideLatLng;
+ if (bounds.west() > -180) {
+ outsideLatLng = {
+ (bounds.south() + bounds.north()) / 2,
+ bounds.west() - 1,
+ };
+ } else if (bounds.northeast().longitude < 180) {
+ outsideLatLng = {
+ (bounds.south() + bounds.north()) / 2,
+ bounds.east() + 1,
+ };
+ }
+
+ // If the world is wrapping, extend the bounds to cover all longitudes.
+ if (NSPointInRect([self convertLatLng:outsideLatLng toPointToView:view], rect)) {
+ bounds.extend(mbgl::LatLng(bounds.south(), -180));
+ bounds.extend(mbgl::LatLng(bounds.south(), 180));
+ }
+
+ return bounds;
+}
+
+- (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude {
+ return _mbglMap->getMetersPerPixelAtLatitude(latitude, self.zoomLevel);
+}
+
+#pragma mark Debugging
+
+- (MGLMapDebugMaskOptions)debugMask {
+ mbgl::MapDebugOptions options = _mbglMap->getDebug();
+ MGLMapDebugMaskOptions mask = 0;
+ if (options & mbgl::MapDebugOptions::TileBorders) {
+ mask |= MGLMapDebugTileBoundariesMask;
+ }
+ if (options & mbgl::MapDebugOptions::ParseStatus) {
+ mask |= MGLMapDebugTileInfoMask;
+ }
+ if (options & mbgl::MapDebugOptions::Timestamps) {
+ mask |= MGLMapDebugTimestampsMask;
+ }
+ if (options & mbgl::MapDebugOptions::Collision) {
+ mask |= MGLMapDebugCollisionBoxesMask;
+ }
+ if (options & mbgl::MapDebugOptions::Wireframe) {
+ mask |= MGLMapDebugWireframesMask;
+ }
+ if (options & mbgl::MapDebugOptions::StencilClip) {
+ mask |= MGLMapDebugStencilBufferMask;
+ }
+ return mask;
+}
+
+- (void)setDebugMask:(MGLMapDebugMaskOptions)debugMask {
+ mbgl::MapDebugOptions options = mbgl::MapDebugOptions::NoDebug;
+ if (debugMask & MGLMapDebugTileBoundariesMask) {
+ options |= mbgl::MapDebugOptions::TileBorders;
+ }
+ if (debugMask & MGLMapDebugTileInfoMask) {
+ options |= mbgl::MapDebugOptions::ParseStatus;
+ }
+ if (debugMask & MGLMapDebugTimestampsMask) {
+ options |= mbgl::MapDebugOptions::Timestamps;
+ }
+ if (debugMask & MGLMapDebugCollisionBoxesMask) {
+ options |= mbgl::MapDebugOptions::Collision;
+ }
+ if (debugMask & MGLMapDebugWireframesMask) {
+ options |= mbgl::MapDebugOptions::Wireframe;
+ }
+ if (debugMask & MGLMapDebugStencilBufferMask) {
+ options |= mbgl::MapDebugOptions::StencilClip;
+ }
+ _mbglMap->setDebug(options);
+}
+
+/// Adapter responsible for bridging calls from mbgl to MGLMapView and Cocoa.
+class MGLMapViewImpl : public mbgl::View {
+public:
+ MGLMapViewImpl(MGLMapView *nativeView_, const float scaleFactor_)
+ : nativeView(nativeView_), scaleFactor(scaleFactor_) {}
+
+ float getPixelRatio() const override {
+ return scaleFactor;
+ }
+
+ std::array<uint16_t, 2> getSize() const override {
+ return {{ static_cast<uint16_t>(nativeView.bounds.size.width),
+ static_cast<uint16_t>(nativeView.bounds.size.height) }};
+ }
+
+ std::array<uint16_t, 2> getFramebufferSize() const override {
+ NSRect bounds = [nativeView convertRectToBacking:nativeView.bounds];
+ return {{ static_cast<uint16_t>(bounds.size.width),
+ static_cast<uint16_t>(bounds.size.height) }};
+ }
+
+ void notifyMapChange(mbgl::MapChange change) override {
+ [nativeView notifyMapChange:change];
+ }
+
+ void invalidate() override {
+ [nativeView invalidate];
+ }
+
+ void activate() override {
+ MGLOpenGLLayer *layer = (MGLOpenGLLayer *)nativeView.layer;
+ [layer.openGLContext makeCurrentContext];
+ }
+
+ void deactivate() override {
+ [NSOpenGLContext clearCurrentContext];
+ }
+
+ mbgl::PremultipliedImage readStillImage() override {
+ auto size = getFramebufferSize();
+ const unsigned int w = size[0];
+ const unsigned int h = size[1];
+
+ mbgl::PremultipliedImage image { w, h };
+ MBGL_CHECK_ERROR(glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.data.get()));
+
+ const size_t stride = image.stride();
+ auto tmp = std::make_unique<uint8_t[]>(stride);
+ uint8_t *rgba = image.data.get();
+ for (int i = 0, j = h - 1; i < j; i++, j--) {
+ std::memcpy(tmp.get(), rgba + i * stride, stride);
+ std::memcpy(rgba + i * stride, rgba + j * stride, stride);
+ std::memcpy(rgba + j * stride, tmp.get(), stride);
+ }
+
+ return image;
+ }
+
+private:
+ /// Cocoa map view that this adapter bridges to.
+ __weak MGLMapView *nativeView = nullptr;
+
+ /// Backing scale factor of the view.
+ const float scaleFactor;
+};
+
+@end
diff --git a/platform/macos/src/MGLMapViewDelegate.h b/platform/macos/src/MGLMapViewDelegate.h
new file mode 100644
index 0000000000..0b7eec84ac
--- /dev/null
+++ b/platform/macos/src/MGLMapViewDelegate.h
@@ -0,0 +1,220 @@
+#import <Foundation/Foundation.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class MGLMapView;
+@class MGLAnnotationImage;
+@class MGLPolygon;
+@class MGLPolyline;
+@class MGLShape;
+
+/**
+ The `MGLMapViewDelegate` protocol defines a set of optional methods that you
+ can use to receive messages from an `MGLMapView` instance. Because many map
+ operations require the `MGLMapView` class to load data asynchronously, the map
+ view calls these methods to notify your application when specific operations
+ complete. The map view also uses these methods to request information about
+ annotations displayed on the map, such as the styles and interaction modes to
+ apply to individual annotations.
+ */
+@protocol MGLMapViewDelegate <NSObject>
+
+@optional
+
+#pragma mark Responding to Map Viewpoint Changes
+
+/**
+ Tells the delegate that the viewpoint depicted by the map view is about to
+ change.
+
+ This method is called whenever the currently displayed map camera will start
+ changing for any reason.
+
+ @param mapView The map view whose viewpoint will change.
+ @param animated Whether the change will cause an animated effect on the map.
+ */
+- (void)mapView:(MGLMapView *)mapView cameraWillChangeAnimated:(BOOL)animated;
+
+/**
+ Tells the delegate that the viewpoint depicted by the map view is changing.
+
+ This method is called as the currently displayed map camera changes due to
+ animation. During movement, this method may be called many times to report
+ updates to the viewpoint. Therefore, your implementation of this method should
+ be as lightweight as possible to avoid affecting performance.
+
+ @param mapView The map view whose viewpoint is changing.
+ */
+- (void)mapViewCameraIsChanging:(MGLMapView *)mapView;
+
+/**
+ Tells the delegate that the viewpoint depicted by the map view has finished
+ changing.
+
+ This method is called whenever the currently displayed map camera has finished
+ changing.
+
+ @param mapView The map view whose viewpoint has changed.
+ @param animated Whether the change caused an animated effect on the map.
+ */
+- (void)mapView:(MGLMapView *)mapView cameraDidChangeAnimated:(BOOL)animated;
+
+#pragma mark Loading the Map
+
+/**
+ Tells the delegate that the map view will begin to load.
+
+ This method is called whenever the map view starts loading, including when a
+ new style has been set and the map must reload.
+
+ @param mapView The map view that is starting to load.
+ */
+- (void)mapViewWillStartLoadingMap:(MGLMapView *)mapView;
+
+/**
+ Tells the delegate that the map view has finished loading.
+
+ This method is called whenever the map view finishes loading, either after the
+ initial load or after a style change has forced a reload.
+
+ @param mapView The map view that has finished loading.
+ */
+- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView;
+
+- (void)mapViewWillStartRenderingMap:(MGLMapView *)mapView;
+- (void)mapViewDidFinishRenderingMap:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered;
+- (void)mapViewWillStartRenderingFrame:(MGLMapView *)mapView;
+- (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered;
+
+#pragma mark Managing the Display of Annotations
+
+/**
+ Returns an annotation image object to mark the given point annotation object on
+ the map.
+
+ @param mapView The map view that requested the annotation image.
+ @param annotation The object representing the annotation that is about to be
+ displayed.
+ @return The image object to display for the given annotation or `nil` if you
+ want to display the default marker image.
+ */
+- (nullable MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation;
+
+/**
+ Returns the alpha value to use when rendering a shape annotation.
+
+ A value of 0.0 results in a completely transparent shape. A value of 1.0, the
+ default, results in a completely opaque shape.
+
+ @param mapView The map view rendering the shape annotation.
+ @param annotation The annotation being rendered.
+ @return An alpha value between 0 and 1.0.
+ */
+- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation;
+
+/**
+ Returns the color to use when rendering the outline of a shape annotation.
+
+ The default stroke color is the selected menu item color. If a pattern color is
+ specified, the result is undefined.
+
+ @param mapView The map view rendering the shape annotation.
+ @param annotation The annotation being rendered.
+ @return A color to use for the shape outline.
+ */
+- (NSColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation;
+
+/**
+ Returns the color to use when rendering the fill of a polygon annotation.
+
+ The default fill color is selected menu item color. If a pattern color is
+ specified, the result is undefined.
+
+ @param mapView The map view rendering the polygon annotation.
+ @param annotation The annotation being rendered.
+ @return The polygon’s interior fill color.
+ */
+- (NSColor *)mapView:(MGLMapView *)mapView fillColorForPolygonAnnotation:(MGLPolygon *)annotation;
+
+/**
+ Returns the line width in points to use when rendering the outline of a
+ polyline annotation.
+
+ By default, the polyline is outlined with a line 3.0 points wide.
+
+ @param mapView The map view rendering the polygon annotation.
+ @param annotation The annotation being rendered.
+ @return A line width for the polyline, measured in points.
+ */
+- (CGFloat)mapView:(MGLMapView *)mapView lineWidthForPolylineAnnotation:(MGLPolyline *)annotation;
+
+#pragma mark Selecting Annotations
+
+/**
+ Tells the delegate that one of its annotations has been selected.
+
+ You can use this method to track changes to the selection state of annotations.
+
+ @param mapView The map view containing the annotation.
+ @param annotation The annotation that was selected.
+ */
+- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id <MGLAnnotation>)annotation;
+
+/**
+ Tells the delegate that one of its annotations has been deselected.
+
+ You can use this method to track changes in the selection state of annotations.
+
+ @param mapView The map view containing the annotation.
+ @param annotation The annotation that was deselected.
+ */
+- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id <MGLAnnotation>)annotation;
+
+#pragma mark Displaying Information About Annotations
+
+/**
+ Returns a Boolean value indicating whether the annotation is able to display
+ extra information in a callout popover.
+
+ This method is called after an annotation is selected, before any callout is
+ displayed for the annotation.
+
+ If the return value is `YES`, a callout popover is shown when the user clicks
+ on a selected annotation. The default callout displays the annotation’s title
+ and subtitle. You can customize the popover’s contents by implementing the
+ `-mapView:calloutViewControllerForAnnotation:` method.
+
+ If the return value is `NO`, or if this method is unimplemented, or if the
+ annotation lacks a title, the annotation will not show a callout even when
+ selected.
+
+ @param mapView The map view that has selected the annotation.
+ @param annotation The object representing the annotation.
+ @return A Boolean value indicating whether the annotation should show a
+ callout.
+ */
+- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation;
+
+/**
+ Returns a view controller to manage the callout popover’s content view.
+
+ Like any instance of `NSPopover`, an annotation callout manages its contents
+ with a view controller. The annotation object is the view controller’s
+ represented object. This means that you can bind controls in the view
+ controller’s content view to KVO-compliant properties of the annotation object,
+ such as `title` and `subtitle`.
+
+ If each annotation should have an identical callout, you can set the
+ `MGLMapView` instance’s `-setCalloutViewController:` method instead.
+
+ @param mapView The map view that is requesting a callout view controller.
+ @param annotation The object representing the annotation.
+ @return A view controller for the given annotation.
+ */
+- (nullable NSViewController *)mapView:(MGLMapView *)mapView calloutViewControllerForAnnotation:(id <MGLAnnotation>)annotation;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/macos/src/MGLMapView_Private.h b/platform/macos/src/MGLMapView_Private.h
new file mode 100644
index 0000000000..76b1727925
--- /dev/null
+++ b/platform/macos/src/MGLMapView_Private.h
@@ -0,0 +1,23 @@
+#import "MGLMapView.h"
+
+@interface MGLMapView (Private)
+
+/// True if the view or application is in a state where it is not expected to be
+/// actively drawing.
+@property (nonatomic, readonly, getter=isDormant) BOOL dormant;
+
+// These properties exist because initially, both the latitude and longitude are
+// NaN. You have to set both the latitude and longitude simultaneously. If you
+// set the latitude but reuse the current longitude, and the current longitude
+// happens to be NaN, there will be no change because the resulting coordinate
+// pair is invalid.
+
+/// Center latitude set independently of the center longitude in an inspectable.
+@property (nonatomic) CLLocationDegrees pendingLatitude;
+/// Center longitude set independently of the center latitude in an inspectable.
+@property (nonatomic) CLLocationDegrees pendingLongitude;
+
+/// Synchronously render a frame of the map.
+- (void)renderSync;
+
+@end
diff --git a/platform/macos/src/MGLOpenGLLayer.h b/platform/macos/src/MGLOpenGLLayer.h
new file mode 100644
index 0000000000..5c8ac43e9e
--- /dev/null
+++ b/platform/macos/src/MGLOpenGLLayer.h
@@ -0,0 +1,12 @@
+#import <Cocoa/Cocoa.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// A subclass of NSOpenGLLayer that creates the environment mbgl needs to
+/// render good-looking maps.
+@interface MGLOpenGLLayer : NSOpenGLLayer
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/macos/src/MGLOpenGLLayer.mm b/platform/macos/src/MGLOpenGLLayer.mm
new file mode 100644
index 0000000000..e8fa521351
--- /dev/null
+++ b/platform/macos/src/MGLOpenGLLayer.mm
@@ -0,0 +1,49 @@
+#import "MGLOpenGLLayer.h"
+
+#import "MGLMapView_Private.h"
+
+#import <mbgl/gl/gl.hpp>
+
+@implementation MGLOpenGLLayer
+
+- (MGLMapView *)mapView {
+ return (MGLMapView *)super.view;
+}
+
+//- (BOOL)isAsynchronous {
+// return YES;
+//}
+
+- (BOOL)needsDisplayOnBoundsChange {
+ return YES;
+}
+
+- (CGRect)frame {
+ return self.view.bounds;
+}
+
+- (NSOpenGLPixelFormat *)openGLPixelFormatForDisplayMask:(uint32_t)mask {
+ NSOpenGLPixelFormatAttribute pfas[] = {
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFAClosestPolicy,
+ NSOpenGLPFAAccumSize, 32,
+ NSOpenGLPFAColorSize, 24,
+ NSOpenGLPFAAlphaSize, 8,
+ NSOpenGLPFADepthSize, 16,
+ NSOpenGLPFAStencilSize, 8,
+ NSOpenGLPFAScreenMask, mask,
+ 0
+ };
+ return [[NSOpenGLPixelFormat alloc] initWithAttributes:pfas];
+}
+
+- (BOOL)canDrawInOpenGLContext:(__unused NSOpenGLContext *)context pixelFormat:(__unused NSOpenGLPixelFormat *)pixelFormat forLayerTime:(__unused CFTimeInterval)t displayTime:(__unused const CVTimeStamp *)ts {
+ return !self.mapView.dormant;
+}
+
+- (void)drawInOpenGLContext:(NSOpenGLContext *)context pixelFormat:(NSOpenGLPixelFormat *)pixelFormat forLayerTime:(CFTimeInterval)t displayTime:(const CVTimeStamp *)ts {
+ [self.mapView renderSync];
+ [super drawInOpenGLContext:context pixelFormat:pixelFormat forLayerTime:t displayTime:ts];
+}
+
+@end
diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h
new file mode 100644
index 0000000000..e4545e04bc
--- /dev/null
+++ b/platform/macos/src/Mapbox.h
@@ -0,0 +1,34 @@
+#import <Cocoa/Cocoa.h>
+
+/// Project version number for Mapbox.
+FOUNDATION_EXPORT double MapboxVersionNumber;
+
+/// Project version string for Mapbox.
+FOUNDATION_EXPORT const unsigned char MapboxVersionString[];
+
+#import "MGLAccountManager.h"
+#import "MGLAnnotation.h"
+#import "MGLAnnotationImage.h"
+#import "MGLClockDirectionFormatter.h"
+#import "MGLCompassDirectionFormatter.h"
+#import "MGLCoordinateFormatter.h"
+#import "MGLFeature.h"
+#import "MGLGeometry.h"
+#import "MGLMapCamera.h"
+#import "MGLMapView.h"
+#import "MGLMapView+IBAdditions.h"
+#import "MGLMapViewDelegate.h"
+#import "MGLMultiPoint.h"
+#import "MGLOfflinePack.h"
+#import "MGLOfflineRegion.h"
+#import "MGLOfflineStorage.h"
+#import "MGLOverlay.h"
+#import "MGLPointAnnotation.h"
+#import "MGLPolygon.h"
+#import "MGLPolyline.h"
+#import "MGLShape.h"
+#import "MGLShapeCollection.h"
+#import "MGLStyle.h"
+#import "MGLTilePyramidOfflineRegion.h"
+#import "MGLTypes.h"
+#import "NSValue+MGLAdditions.h"
diff --git a/platform/macos/test/Info.plist b/platform/macos/test/Info.plist
new file mode 100644
index 0000000000..ba72822e87
--- /dev/null
+++ b/platform/macos/test/Info.plist
@@ -0,0 +1,24 @@
+<?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>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</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>