From 6a1e7d5707762561e4947e90f94c1ede9e2940cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Mon, 30 Mar 2015 16:27:28 -0700 Subject: [PATCH 1/7] Fixed race condition after setting access token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Map` should be able to deal with having no access token or JSON even while it’s running. Most of `Map` accounts for this situation, but `reloadStyle()` incorrectly assumes that one or the other is set. This change corrects the assumption in `reloadStyle()`. Also expose the access token and style name to clients as ordinary KVO-compliant properties. Actually, they’re not so ordinary because they’re inspectable! Ref #1070, #1147 --- gyp/platform-ios.gypi | 3 ++ include/mbgl/ios/MGLMapView.h | 6 ++-- include/mbgl/map/map.hpp | 1 + ios/app/MBXViewController.mm | 7 ++--- platform/ios/MGLMapView.mm | 47 ++++++++++++++++++++-------- platform/ios/NSString+MGLAdditions.h | 8 +++++ platform/ios/NSString+MGLAdditions.m | 10 ++++++ src/mbgl/map/map.cpp | 6 +++- 8 files changed, 66 insertions(+), 22 deletions(-) create mode 100644 platform/ios/NSString+MGLAdditions.h create mode 100644 platform/ios/NSString+MGLAdditions.m diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi index 9965db57a..fc6771d83 100644 --- a/gyp/platform-ios.gypi +++ b/gyp/platform-ios.gypi @@ -30,6 +30,8 @@ '../include/mbgl/ios/MGLMetricsLocationManager.h', '../platform/ios/MGLMetricsLocationManager.m', '../include/mbgl/ios/MGLTypes.h', + '../platform/ios/NSString+MGLAdditions.h', + '../platform/ios/NSString+MGLAdditions.m', '../platform/ios/vendor/SMCalloutView/SMCalloutView.h', '../platform/ios/vendor/SMCalloutView/SMCalloutView.m', ], @@ -50,6 +52,7 @@ '-framework MobileCoreServices', '-framework QuartzCore', '-framework SystemConfiguration', + '-ObjC', ], }, diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index 17741b171..354e32cc5 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -54,7 +54,7 @@ /** Sets a Mapbox API access token for the map view. * @param accessToken A Mapbox API token. */ -- (void)setAccessToken:(NSString *)accessToken; +@property (nonatomic) IBInspectable NSString *accessToken; #pragma mark - Managing Constraints @@ -194,11 +194,11 @@ /** Sets the map style to a named, bundled style. * @param styleName The map style name to use. */ -- (void)useBundledStyleNamed:(NSString *)styleName; +@property (nonatomic) IBInspectable NSString *styleName; /** Sets the map style URL to use. * @param styleURL The map style URL to use. Can be either an HTTP/HTTPS URL or a Mapbox map ID style URL (`mapbox://`). */ -- (void)setStyleURL:(NSURL *)styleURL; +@property (nonatomic) NSURL *styleURL; #pragma mark - Annotating the Map diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index cdcfa27e4..764ff24ed 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -96,6 +96,7 @@ public: Duration getDefaultTransitionDuration(); void setStyleURL(const std::string& url); void setStyleJSON(const std::string& json, const std::string& base = ""); + std::string getStyleURL() const; std::string getStyleJSON() const; // Transition diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm index c9973bcb7..146143969 100644 --- a/ios/app/MBXViewController.mm +++ b/ios/app/MBXViewController.mm @@ -19,7 +19,7 @@ static NSArray *const kStyleNames = @[ @"Hybrid", ]; -static NSString *const kStyleVersion = @"v7"; +static NSString *const kStyleVersion = @"7"; @interface MBXViewController () @@ -246,10 +246,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; styleName = [kStyleNames objectAtIndex:index]; } - [self.mapView useBundledStyleNamed: - [[[styleName lowercaseString] - stringByAppendingString:@"-"] - stringByAppendingString:kStyleVersion]]; + self.mapView.styleName = [NSString stringWithFormat:@"%@-v%@", styleName.lowercaseString, kStyleVersion]; [titleButton setTitle:styleName forState:UIControlStateNormal]; } diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index d48694917..b6519178d 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -15,6 +15,7 @@ #include #import "MGLTypes.h" +#import "NSString+MGLAdditions.h" #import "MGLAnnotation.h" #import "MGLUserLocationAnnotationView.h" #import "MGLUserLocation_Private.h" @@ -43,7 +44,7 @@ const std::string &defaultCacheDatabase() { static dispatch_once_t loadGLExtensions; NSString *const MGLDefaultStyleName = @"Emerald"; -NSString *const MGLStyleVersion = @"v7"; +NSString *const MGLStyleVersion = @"7"; NSString *const MGLDefaultStyleMarkerSymbolName = @"default_marker"; const NSTimeInterval MGLAnimationDuration = 0.3; @@ -106,7 +107,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; if (self && [self commonInit]) { - if (accessToken) [self setAccessToken:accessToken]; + [self setAccessToken:accessToken]; if (styleJSON || accessToken) { @@ -126,8 +127,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; if (self && [self commonInit]) { - if (accessToken) [self setAccessToken:accessToken]; - if (styleName) [self useBundledStyleNamed:styleName]; + [self setAccessToken:accessToken]; + if (styleName) [self setStyleName:styleName]; } return self; @@ -163,22 +164,22 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; return nil; } +- (NSString *)accessToken +{ + return @(mbglMap->getAccessToken().c_str()).mgl_stringOrNilIfEmpty; +} + - (void)setAccessToken:(NSString *)accessToken { - if (accessToken) - { - mbglMap->setAccessToken((std::string)[accessToken UTF8String]); - [MGLMapboxEvents setToken:accessToken]; - } + mbglMap->setAccessToken((std::string)[accessToken UTF8String]); + [MGLMapboxEvents setToken:accessToken.mgl_stringOrNilIfEmpty]; } - (void)setStyleJSON:(NSString *)styleJSON { if ( ! styleJSON) { - [self useBundledStyleNamed:[[[MGLDefaultStyleName lowercaseString] - stringByAppendingString:@"-"] - stringByAppendingString:MGLStyleVersion]]; + [self setStyleName:[NSString stringWithFormat:@"%@-v%@", MGLDefaultStyleName.lowercaseString, MGLStyleVersion]]; } else { @@ -186,6 +187,12 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; } } +- (NSURL *)styleURL +{ + NSString *styleURLString = @(mbglMap->getStyleURL().c_str()).mgl_stringOrNilIfEmpty; + return styleURLString ? [NSURL URLWithString:styleURLString] : nil; +} + - (void)setStyleURL:(NSURL *)styleURL { std::string styleURLString([[styleURL absoluteString] UTF8String]); @@ -1322,7 +1329,21 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng) return [NSArray arrayWithArray:_bundledStyleNames]; } -- (void)useBundledStyleNamed:(NSString *)styleName +- (NSString *)styleName +{ + NSURL *styleURL = self.styleURL; + NSString *styleName; + if ([styleURL.scheme isEqualToString:@"asset"]) + { + styleName = styleURL.lastPathComponent.stringByDeletingPathExtension; + } + else if ([styleURL.scheme isEqualToString:@"mapbox"]) { + styleName = styleURL.host; + } + return styleName.mgl_stringOrNilIfEmpty; +} + +- (void)setStyleName:(NSString *)styleName { NSString *hybridStylePrefix = @"hybrid-"; BOOL isHybrid = [styleName hasPrefix:hybridStylePrefix]; diff --git a/platform/ios/NSString+MGLAdditions.h b/platform/ios/NSString+MGLAdditions.h new file mode 100644 index 000000000..263cfc8cb --- /dev/null +++ b/platform/ios/NSString+MGLAdditions.h @@ -0,0 +1,8 @@ +#import + +@interface NSString (MGLAdditions) + +/// Returns the receiver if non-empty or \c nil if empty. +- (NSString *)mgl_stringOrNilIfEmpty; + +@end diff --git a/platform/ios/NSString+MGLAdditions.m b/platform/ios/NSString+MGLAdditions.m new file mode 100644 index 000000000..d80c34cef --- /dev/null +++ b/platform/ios/NSString+MGLAdditions.m @@ -0,0 +1,10 @@ +#import "NSString+MGLAdditions.h" + +@implementation NSString (MGLAdditions) + +- (NSString *)mgl_stringOrNilIfEmpty +{ + return self.length ? self : nil; +} + +@end diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 09e6b08a1..ac2be3f06 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -396,6 +396,10 @@ void Map::setup() { painter->setup(); } +std::string Map::getStyleURL() const { + return data->getStyleInfo().url; +} + void Map::setStyleURL(const std::string &url) { assert(Environment::currentlyOn(ThreadType::Main)); @@ -756,7 +760,7 @@ void Map::reloadStyle() { Log::Error(Event::Setup, "loading style failed: %s", res.message.c_str()); } }); - } else { + } else if (!styleInfo.json.empty()) { // We got JSON data directly. loadStyleJSON(styleInfo.json, styleInfo.base); } From e18e4b0de5f601cdfb07eed209e64f88a114e492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Tue, 31 Mar 2015 15:48:01 -0700 Subject: [PATCH 2/7] Removed redundant initializers and setters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change removes most of the ways you used to be able to apply a style to the map. Building on #1163, `styleURL` (HTTP(S), mapbox:, asset:) is the canonical way to apply a style, and `mapID` is a convenient shorthand for Mapbox-hosted styles. A relative style URL is interpreted as a path relative to the app’s main bundle. We now construct asset: URLs in lieu of “bundled style names”. --- include/mbgl/ios/MGLMapView.h | 43 +++-------- ios/app/MBXViewController.mm | 3 +- platform/ios/MGLMapView.mm | 134 +++++++++++----------------------- 3 files changed, 54 insertions(+), 126 deletions(-) diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index 354e32cc5..dcf1313a8 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -19,20 +19,6 @@ /** @name Initializing a Map View */ -/** Initialize a map view with a given frame, style, and access token. -* @param frame The frame with which to initialize the map view. -* @param accessToken A Mapbox API access token. -* @param styleJSON The map stylesheet as JSON text. -* @return An initialized map view, or `nil` if the map view was unable to be initialized. */ -- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken styleJSON:(NSString *)styleJSON; - -/** Initialize a map view with a given frame, bundled style name, and access token. -* @param frame The frame with which to initialize the map view. -* @param accessToken A Mapbox API access token. -* @param styleName The map style name to use. -* @return An initialized map view, or `nil` if the map view was unable to be initialized. */ -- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken bundledStyleNamed:(NSString *)styleName; - /** Initialize a map view with a given frame, style URL, and access token. * @param frame The frame with which to initialize the map view. * @param accessToken A Mapbox API access token. @@ -178,26 +164,19 @@ /** @name Styling the Map */ -/** Sets the map style. -* @param styleJSON The map stylesheet as JSON text. */ -- (void)setStyleJSON:(NSString *)styleJSON; +/** Mapbox map ID of the style currently displayed in the receiver, or `nil` if the style does not have a map ID. + + The style may lack a map ID if it is located at an HTTP, HTTPS, or asset: URL. Use -styleURL to get the URL in these cases. + */ +@property (nonatomic) IBInspectable NSString *mapID; -/** Returns the raw JSON style as a native dictionary object. */ -- (NSDictionary *)getRawStyle; +/** Returns the URLs to the styles bundled with the library. */ +- (NSArray *)bundledStyleURLs; -/** Sets the raw JSON style as a native dictionary object with a transition animation. -* @param style The style JSON as a dictionary object. */ -- (void)setRawStyle:(NSDictionary *)style; - -/** Returns the names of the styles bundled with the library. */ -- (NSArray *)bundledStyleNames; - -/** Sets the map style to a named, bundled style. -* @param styleName The map style name to use. */ -@property (nonatomic) IBInspectable NSString *styleName; - -/** Sets the map style URL to use. -* @param styleURL The map style URL to use. Can be either an HTTP/HTTPS URL or a Mapbox map ID style URL (`mapbox://`). */ +/** URL of the style currently displayed in the receiver. + + The URL may be a full HTTP or HTTPS URL or a Mapbox URL indicating the style’s map ID (`mapbox://`). To display the default style, set this property to `nil`. + */ @property (nonatomic) NSURL *styleURL; #pragma mark - Annotating the Map diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm index 146143969..28ac68589 100644 --- a/ios/app/MBXViewController.mm +++ b/ios/app/MBXViewController.mm @@ -16,7 +16,6 @@ static NSArray *const kStyleNames = @[ @"Basic", @"Outdoors", @"Satellite", - @"Hybrid", ]; static NSString *const kStyleVersion = @"7"; @@ -246,7 +245,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; styleName = [kStyleNames objectAtIndex:index]; } - self.mapView.styleName = [NSString stringWithFormat:@"%@-v%@", styleName.lowercaseString, kStyleVersion]; + self.mapView.styleURL = [NSURL URLWithString:[NSString stringWithFormat:@"asset://styles/%@-v%@.json", styleName.lowercaseString, kStyleVersion]]; [titleButton setTitle:styleName forState:UIControlStateNormal]; } diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index b6519178d..67a62680e 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -53,6 +53,11 @@ const CGFloat MGLMinimumZoom = 3; NSString *const MGLAnnotationIDKey = @"MGLAnnotationIDKey"; +static NSURL *MGLURLForBundledStyleNamed(NSString *styleName) +{ + return [NSURL URLWithString:[NSString stringWithFormat:@"asset://styles/%@.json", styleName]]; +} + #pragma mark - Private - @interface MGLMapView () @@ -67,7 +72,7 @@ NSString *const MGLAnnotationIDKey = @"MGLAnnotationIDKey"; @property (nonatomic) UIPinchGestureRecognizer *pinch; @property (nonatomic) UIRotationGestureRecognizer *rotate; @property (nonatomic) UILongPressGestureRecognizer *quickZoom; -@property (nonatomic) NSMutableArray *bundledStyleNames; +@property (nonatomic) NSMutableArray *bundledStyleURLs; @property (nonatomic) NSMapTable *annotationIDsByAnnotation; @property (nonatomic) std::vector annotationsNearbyLastTap; @property (nonatomic, weak) id selectedAnnotation; @@ -101,7 +106,7 @@ MBGLView *mbglView = nullptr; mbgl::SQLiteCache *mbglFileCache = nullptr; mbgl::DefaultFileSource *mbglFileSource = nullptr; -- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken styleJSON:(NSString *)styleJSON +- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken { self = [super initWithFrame:frame]; @@ -109,55 +114,38 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; { [self setAccessToken:accessToken]; - if (styleJSON || accessToken) + if (accessToken) { // If style is set directly, pass it on. If not, if we have an access // token, we can pass nil and use the default style. // - [self setStyleJSON:styleJSON]; + self.styleURL = nil; } } return self; } -- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken bundledStyleNamed:(NSString *)styleName -{ - self = [super initWithFrame:frame]; - - if (self && [self commonInit]) - { - [self setAccessToken:accessToken]; - if (styleName) [self setStyleName:styleName]; - } - - return self; -} - - (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken styleURL:(NSURL *)styleURL { self = [super initWithFrame:frame]; if (self && [self commonInit]) { - if (accessToken) [self setAccessToken:accessToken]; - if (styleURL) [self setStyleURL:styleURL]; + self.accessToken = accessToken; + self.styleURL = styleURL; } return self; } -- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken -{ - return [self initWithFrame:frame accessToken:accessToken styleJSON:nil]; -} - - (instancetype)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (self && [self commonInit]) { + self.styleURL = nil; return self; } @@ -175,16 +163,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; [MGLMapboxEvents setToken:accessToken.mgl_stringOrNilIfEmpty]; } -- (void)setStyleJSON:(NSString *)styleJSON -{ - if ( ! styleJSON) - { - [self setStyleName:[NSString stringWithFormat:@"%@-v%@", MGLDefaultStyleName.lowercaseString, MGLStyleVersion]]; - } - else - { - mbglMap->setStyleJSON((std::string)[styleJSON UTF8String]); - } ++ (NSSet *)keyPathsForValuesAffectingStyleURL { + return [NSSet setWithObjects:@"mapID", @"accessToken", nil]; } - (NSURL *)styleURL @@ -195,16 +175,20 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (void)setStyleURL:(NSURL *)styleURL { - std::string styleURLString([[styleURL absoluteString] UTF8String]); - + if ( ! styleURL) + { + styleURL = MGLURLForBundledStyleNamed([NSString stringWithFormat:@"%@-v%@", + MGLDefaultStyleName.lowercaseString, + MGLStyleVersion]); + } + if ( ! [styleURL scheme]) { - mbglMap->setStyleURL(std::string("asset://") + styleURLString); - } - else - { - mbglMap->setStyleURL(styleURLString); + // Assume a relative path into the developer’s bundle. + styleURL = [[NSBundle mainBundle] URLForResource:styleURL.path withExtension:nil]; } + + mbglMap->setStyleURL([[styleURL absoluteString] UTF8String]); } - (BOOL)commonInit @@ -1291,69 +1275,35 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng) #pragma mark - Styling - -- (NSDictionary *)getRawStyle +- (NSArray *)bundledStyleURLs { - const std::string styleJSON = mbglMap->getStyleJSON(); + if ( ! _bundledStyleURLs) + { + NSString *stylesPath = [[MGLMapView resourceBundlePath] stringByAppendingPathComponent:@"styles"]; - return [NSJSONSerialization JSONObjectWithData:[@(styleJSON.c_str()) dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]; -} - -- (void)setRawStyle:(NSDictionary *)style -{ - NSData *data = [NSJSONSerialization dataWithJSONObject:style options:0 error:nil]; - - [self setStyleJSON:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; -} - -- (NSArray *)bundledStyleNames -{ - if (!_bundledStyleNames) { - NSString *stylesPath = [[MGLMapView resourceBundlePath] stringByAppendingString:@"/styles"]; - - _bundledStyleNames = [NSMutableArray array]; + _bundledStyleURLs = [NSMutableArray array]; NSArray *bundledStyleNamesWithExtensions = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:stylesPath error:nil]; - NSString *hybridStylePrefix = @"hybrid-"; - NSString *satelliteStylePrefix = @"satellite-"; - for (NSString *fileName in bundledStyleNamesWithExtensions) { - NSString *styleName = [fileName stringByDeletingPathExtension]; - [_bundledStyleNames addObject:styleName]; - - // Add satellite raster & "hybrid" (satellite raster + vector contours & labels) - if ([styleName hasPrefix:satelliteStylePrefix]) { - [_bundledStyleNames addObject:[hybridStylePrefix stringByAppendingString:[styleName substringFromIndex:[satelliteStylePrefix length]]]]; - } + for (NSString *fileName in bundledStyleNamesWithExtensions) + { + [_bundledStyleURLs addObject:MGLURLForBundledStyleNamed([fileName stringByDeletingPathExtension])]; } } - return [NSArray arrayWithArray:_bundledStyleNames]; + return [NSArray arrayWithArray:_bundledStyleURLs]; } -- (NSString *)styleName -{ ++ (NSSet *)keyPathsForValuesAffectingMapID { + return [NSSet setWithObjects:@"styleURL", @"accessToken", nil]; +} + +- (NSString *)mapID { NSURL *styleURL = self.styleURL; - NSString *styleName; - if ([styleURL.scheme isEqualToString:@"asset"]) - { - styleName = styleURL.lastPathComponent.stringByDeletingPathExtension; - } - else if ([styleURL.scheme isEqualToString:@"mapbox"]) { - styleName = styleURL.host; - } - return styleName.mgl_stringOrNilIfEmpty; + return [styleURL.scheme isEqualToString:@"mapbox"] ? styleURL.host.mgl_stringOrNilIfEmpty : nil; } -- (void)setStyleName:(NSString *)styleName -{ - NSString *hybridStylePrefix = @"hybrid-"; - BOOL isHybrid = [styleName hasPrefix:hybridStylePrefix]; - if (isHybrid) { - styleName = [@"satellite-" stringByAppendingString:[styleName substringFromIndex:[hybridStylePrefix length]]]; - } - [self setStyleURL:[NSURL URLWithString:[NSString stringWithFormat:@"styles/%@.json", styleName]]]; - if (isHybrid) { - [self setStyleClasses:@[@"contours", @"labels"]]; - } +- (void)setMapID:(NSString *)mapID { + self.styleURL = [NSURL URLWithString:[NSString stringWithFormat:@"mapbox://%@", mapID]]; } - (NSArray *)getAppliedStyleClasses From da13225a816ca40b8be209639c8ad0dc67681089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Wed, 1 Apr 2015 15:57:36 -0700 Subject: [PATCH 3/7] Made MGLMapView designable in IB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an access token is set in the Attributes inspector (or as a user-defined runtime attribute), we draw some lovely Mapbox branding so the view shows up. (Manipulating invisible rectangles is a frustrating exercise, I’m told.) In the absence of an access token, the view displays a helpful message with directions for obtaining and setting the access token. Along the way, completely opt out of `MGLMapboxEvents` when targeting Interface Builder, because touching Core Location throws an exception in that environment and it doesn’t make sense to record any metrics when designing on the Interface Builder canvas. Also, don’t start `mbgl::Map` at all (and don’t update it) because none of the runtime drawing code should ever be run in the designable. Normally these chunks of code would be excluded in IB using the TARGET_INTERFACE_BUILDER preprocessor macro. However, Mapbox GL is being packaged as a static library, so the macro is only evaluated when the library is prebuilt, even if the library eventually makes its way into the CocoaPods-generated framework. Instead, we detect that we’re being run by the IBDesignablesAgentCocoaTouch process. Overrode `-[MGLMapView initWithFrame:]` to call `-commonInit`. We’ve marked this initializer unavailable in the header, but IB still calls it regardless. Fixes #929. --- gyp/platform-ios.gypi | 2 + include/mbgl/ios/MGLMapView.h | 11 +- platform/ios/MGLMapView.mm | 175 ++++++++++++++++++++-- platform/ios/MGLMapboxEvents.m | 24 +-- platform/ios/NSProcessInfo+MGLAdditions.h | 7 + platform/ios/NSProcessInfo+MGLAdditions.m | 10 ++ 6 files changed, 199 insertions(+), 30 deletions(-) create mode 100644 platform/ios/NSProcessInfo+MGLAdditions.h create mode 100644 platform/ios/NSProcessInfo+MGLAdditions.m diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi index fc6771d83..998a0d1b8 100644 --- a/gyp/platform-ios.gypi +++ b/gyp/platform-ios.gypi @@ -30,6 +30,8 @@ '../include/mbgl/ios/MGLMetricsLocationManager.h', '../platform/ios/MGLMetricsLocationManager.m', '../include/mbgl/ios/MGLTypes.h', + '../platform/ios/NSProcessInfo+MGLAdditions.h', + '../platform/ios/NSProcessInfo+MGLAdditions.m', '../platform/ios/NSString+MGLAdditions.h', '../platform/ios/NSString+MGLAdditions.m', '../platform/ios/vendor/SMCalloutView/SMCalloutView.h', diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index dcf1313a8..4139ef51c 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -13,20 +13,21 @@ * Use of MGLMapView requires a Mapbox API access token. Obtain an access token on the [Mapbox account page](https://www.mapbox.com/account/apps/). If you instantiate an MGLMapView from Interface Builder, rendering of the map won't begin until the accessToken property has been set. * * @warning Please note that you are responsible for getting permission to use the map data, and for ensuring your use adheres to the relevant terms of use. */ +IB_DESIGNABLE @interface MGLMapView : UIView #pragma mark - Initializing a Map View /** @name Initializing a Map View */ -/** Initialize a map view with a given frame, style URL, and access token. +/** Initialize a map view with a given frame, access token, and style URL. * @param frame The frame with which to initialize the map view. * @param accessToken A Mapbox API access token. * @param styleURL The map style URL to use. Can be either an HTTP/HTTPS URL or a Mapbox map ID style URL (`mapbox://`). * @return An initialized map view, or `nil` if the map view was unable to be initialized. */ - (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken styleURL:(NSURL *)styleURL; -/** Initialize a map view with a given frame, the default style, and an access token. +/** Initialize a map view with the default style given a frame and access token. * @param frame The frame with which to initialize the map view. * @param accessToken A Mapbox API access token. * @return An initialized map view, or `nil` if the map view was unable to be initialized. */ @@ -49,7 +50,7 @@ /** A view controller whose top and bottom layout guides to use for proper setup of constraints in the map view internals. * * Certain components of the map view, such as the heading compass and the data attribution button, need to be aware of the view controller layout in order to avoid positioning content under a top navigation bar or a bottom toolbar. */ -@property (nonatomic, weak) UIViewController *viewControllerForLayoutGuides; +@property (nonatomic, weak) IBOutlet UIViewController *viewControllerForLayoutGuides; #pragma mark - Accessing Map Properties @@ -81,7 +82,7 @@ /** @name Accessing the Delegate */ // TODO -@property(nonatomic, weak) id delegate; +@property(nonatomic, weak) IBOutlet id delegate; #pragma mark - Manipulating the Visible Portion of the Map @@ -134,7 +135,7 @@ - (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated; /** Resets the map rotation to a northern heading. */ -- (void)resetNorth; +- (IBAction)resetNorth; #pragma mark - Converting Map Coordinates diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 67a62680e..b1a2eeadb 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -16,6 +16,7 @@ #import "MGLTypes.h" #import "NSString+MGLAdditions.h" +#import "NSProcessInfo+MGLAdditions.h" #import "MGLAnnotation.h" #import "MGLUserLocationAnnotationView.h" #import "MGLUserLocation_Private.h" @@ -46,6 +47,7 @@ static dispatch_once_t loadGLExtensions; NSString *const MGLDefaultStyleName = @"Emerald"; NSString *const MGLStyleVersion = @"7"; NSString *const MGLDefaultStyleMarkerSymbolName = @"default_marker"; +NSString *const MGLMapboxAccessTokenManagerURLDisplayString = @"mapbox.com/account/apps"; const NSTimeInterval MGLAnimationDuration = 0.3; const CGSize MGLAnnotationUpdateViewportOutset = {150, 150}; @@ -88,7 +90,9 @@ static NSURL *MGLURLForBundledStyleNamed(NSString *styleName) @end -@implementation MGLMapView +@implementation MGLMapView { + BOOL _isTargetingInterfaceBuilder; +} #pragma mark - Setup & Teardown - @@ -106,24 +110,22 @@ MBGLView *mbglView = nullptr; mbgl::SQLiteCache *mbglFileCache = nullptr; mbgl::DefaultFileSource *mbglFileSource = nullptr; -- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken +- (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self && [self commonInit]) { - [self setAccessToken:accessToken]; - - if (accessToken) - { - // If style is set directly, pass it on. If not, if we have an access - // token, we can pass nil and use the default style. - // - self.styleURL = nil; - } + self.styleURL = nil; + return self; } - return self; + return nil; +} + +- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken +{ + return [self initWithFrame:frame accessToken:accessToken styleURL:nil]; } - (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken styleURL:(NSURL *)styleURL @@ -175,6 +177,11 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (void)setStyleURL:(NSURL *)styleURL { + if ( _isTargetingInterfaceBuilder ) + { + return; + } + if ( ! styleURL) { styleURL = MGLURLForBundledStyleNamed([NSString stringWithFormat:@"%@-v%@", @@ -193,6 +200,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (BOOL)commonInit { + _isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent; + // create context // _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; @@ -362,8 +371,11 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; _regionChangeDelegateQueue = [NSOperationQueue new]; _regionChangeDelegateQueue.maxConcurrentOperationCount = 1; - // start the main loop - mbglMap->start(); + // start the main loop, but not on the IB canvas + if ( ! _isTargetingInterfaceBuilder) + { + mbglMap->start(); + } // metrics: map load event const mbgl::LatLng latLng = mbglMap->getLatLng(); @@ -550,7 +562,11 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (void)layoutSubviews { [super layoutSubviews]; - mbglMap->triggerUpdate(); + + if ( ! _isTargetingInterfaceBuilder) + { + mbglMap->triggerUpdate(); + } } #pragma mark - Life Cycle - @@ -2186,6 +2202,135 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng) [self notifyMapChange:@(mbgl::MapChangeRegionIsChanging)]; } +- (void)prepareForInterfaceBuilder +{ + [super prepareForInterfaceBuilder]; + + self.layer.borderColor = [UIColor colorWithWhite:184/255. alpha:1].CGColor; + self.layer.borderWidth = 1; + + if (self.accessToken) + { + self.layer.backgroundColor = [UIColor colorWithRed:59/255. + green:178/255. + blue:208/255. + alpha:0.8].CGColor; + + UIImage *image = [[self class] resourceImageNamed:@"mapbox.png"]; + UIImageView *previewView = [[UIImageView alloc] initWithImage:image]; + previewView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:previewView]; + [self addConstraint: + [NSLayoutConstraint constraintWithItem:previewView + attribute:NSLayoutAttributeCenterXWithinMargins + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeCenterXWithinMargins + multiplier:1 + constant:0]]; + [self addConstraint: + [NSLayoutConstraint constraintWithItem:previewView + attribute:NSLayoutAttributeCenterYWithinMargins + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeCenterYWithinMargins + multiplier:1 + constant:0]]; + } + else + { + UIView *diagnosticView = [[UIView alloc] init]; + diagnosticView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:diagnosticView]; + + // Headline + UILabel *headlineLabel = [[UILabel alloc] init]; + headlineLabel.text = @"No Access Token"; + headlineLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; + headlineLabel.textAlignment = NSTextAlignmentCenter; + headlineLabel.numberOfLines = 1; + headlineLabel.translatesAutoresizingMaskIntoConstraints = NO; + [headlineLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow + forAxis:UILayoutConstraintAxisHorizontal]; + [diagnosticView addSubview:headlineLabel]; + + // Explanation + UILabel *explanationLabel = [[UILabel alloc] init]; + explanationLabel.text = @"To display a map here, you must provide a Mapbox access token. Get an access token from:"; + explanationLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; + explanationLabel.numberOfLines = 0; + explanationLabel.translatesAutoresizingMaskIntoConstraints = NO; + [explanationLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow + forAxis:UILayoutConstraintAxisHorizontal]; + [diagnosticView addSubview:explanationLabel]; + + // Link + UIButton *linkButton = [UIButton buttonWithType:UIButtonTypeSystem]; + [linkButton setTitle:MGLMapboxAccessTokenManagerURLDisplayString forState:UIControlStateNormal]; + linkButton.translatesAutoresizingMaskIntoConstraints = NO; + [linkButton setContentCompressionResistancePriority:UILayoutPriorityDefaultLow + forAxis:UILayoutConstraintAxisHorizontal]; + [diagnosticView addSubview:linkButton]; + + // More explanation + UILabel *explanationLabel2 = [[UILabel alloc] init]; + explanationLabel2.text = @"and enter it into the Access Token field in the Attributes inspector."; + explanationLabel2.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; + explanationLabel2.numberOfLines = 0; + explanationLabel2.translatesAutoresizingMaskIntoConstraints = NO; + [explanationLabel2 setContentCompressionResistancePriority:UILayoutPriorityDefaultLow + forAxis:UILayoutConstraintAxisHorizontal]; + [diagnosticView addSubview:explanationLabel2]; + + // Constraints + NSDictionary *views = @{ + @"container": diagnosticView, + @"headline": headlineLabel, + @"explanation": explanationLabel, + @"link": linkButton, + @"explanation2": explanationLabel2, + }; + [self addConstraint: + [NSLayoutConstraint constraintWithItem:diagnosticView + attribute:NSLayoutAttributeCenterYWithinMargins + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeCenterYWithinMargins + multiplier:1 + constant:0]]; + [self addConstraints: + [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[container(20@20)]-|" + options:NSLayoutFormatAlignAllCenterY + metrics:nil + views:views]]; + [self addConstraints: + [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[headline]-[explanation]-[link]-[explanation2]|" + options:0 + metrics:nil + views:views]]; + [self addConstraints: + [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[headline]|" + options:0 + metrics:nil + views:views]]; + [self addConstraints: + [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[explanation]|" + options:0 + metrics:nil + views:views]]; + [self addConstraints: + [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[link]|" + options:0 + metrics:nil + views:views]]; + [self addConstraints: + [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[explanation2]|" + options:0 + metrics:nil + views:views]]; + } +} + class MBGLView : public mbgl::View { public: diff --git a/platform/ios/MGLMapboxEvents.m b/platform/ios/MGLMapboxEvents.m index 1db0f9e9f..d21a723d9 100644 --- a/platform/ios/MGLMapboxEvents.m +++ b/platform/ios/MGLMapboxEvents.m @@ -6,6 +6,7 @@ #import #import "MGLMetricsLocationManager.h" +#import "NSProcessInfo+MGLAdditions.h" #include @@ -176,17 +177,20 @@ NSString *const MGLEventGestureRotateStart = @"Rotation"; static dispatch_once_t onceToken; static MGLMapboxEvents *_sharedManager; dispatch_once(&onceToken, ^{ - void (^setupBlock)() = ^{ - _sharedManager = [[self alloc] init]; - // setup dedicated location manager on first use - [MGLMetricsLocationManager sharedManager]; - }; - if ( ! [[NSThread currentThread] isMainThread]) { - dispatch_sync(dispatch_get_main_queue(), ^{ + if ( ! NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) { + void (^setupBlock)() = ^{ + _sharedManager = [[self alloc] init]; + // setup dedicated location manager on first use + [MGLMetricsLocationManager sharedManager]; + }; + if ( ! [[NSThread currentThread] isMainThread]) { + dispatch_sync(dispatch_get_main_queue(), ^{ + setupBlock(); + }); + } + else { setupBlock(); - }); - } else { - setupBlock(); + } } }); return _sharedManager; diff --git a/platform/ios/NSProcessInfo+MGLAdditions.h b/platform/ios/NSProcessInfo+MGLAdditions.h new file mode 100644 index 000000000..a117f3b76 --- /dev/null +++ b/platform/ios/NSProcessInfo+MGLAdditions.h @@ -0,0 +1,7 @@ +#import + +@interface NSProcessInfo (MGLAdditions) + +- (BOOL)mgl_isInterfaceBuilderDesignablesAgent; + +@end diff --git a/platform/ios/NSProcessInfo+MGLAdditions.m b/platform/ios/NSProcessInfo+MGLAdditions.m new file mode 100644 index 000000000..fca183c1f --- /dev/null +++ b/platform/ios/NSProcessInfo+MGLAdditions.m @@ -0,0 +1,10 @@ +#import "NSProcessInfo+MGLAdditions.h" + +@implementation NSProcessInfo (MGLAdditions) + +- (BOOL)mgl_isInterfaceBuilderDesignablesAgent +{ + return [self.processName isEqualToString:@"IBDesignablesAgentCocoaTouch"]; +} + +@end From 836de8bc1c9ebbe86e0b20cf11291ef449faccfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Thu, 2 Apr 2015 10:04:19 -0700 Subject: [PATCH 4/7] Made lat/lon/zoom inspectable Declared these IB-specific inspectables in a separate category that developers are not expected to import. Currently these inspectables are order-dependent due to #1181. Ref #929 --- gyp/platform-ios.gypi | 1 + include/mbgl/ios/MGLMapView+IBAdditions.h | 29 ++++++++++ include/mbgl/ios/MGLMapView.h | 14 ++--- platform/ios/MGLMapView.mm | 66 +++++++++++++++++++++++ 4 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 include/mbgl/ios/MGLMapView+IBAdditions.h diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi index 998a0d1b8..f70743118 100644 --- a/gyp/platform-ios.gypi +++ b/gyp/platform-ios.gypi @@ -20,6 +20,7 @@ '../include/mbgl/ios/MGLMapboxEvents.h', '../platform/ios/MGLMapboxEvents.m', '../include/mbgl/ios/MGLMapView.h', + '../include/mbgl/ios/MGLMapView+IBAdditions.h', '../platform/ios/MGLMapView.mm', '../include/mbgl/ios/MGLAnnotation.h', '../include/mbgl/ios/MGLUserLocation.h', diff --git a/include/mbgl/ios/MGLMapView+IBAdditions.h b/include/mbgl/ios/MGLMapView+IBAdditions.h new file mode 100644 index 000000000..4661e549e --- /dev/null +++ b/include/mbgl/ios/MGLMapView+IBAdditions.h @@ -0,0 +1,29 @@ +#import "MGLMapView.h" + +@interface MGLMapView (IBAdditions) + +// 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. + +@property (nonatomic) IBInspectable NSString *accessToken; +@property (nonatomic) IBInspectable NSString *mapID; + +// Some 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. + +/// Initial latitude at which the receiver is centered. +@property (nonatomic) IBInspectable double latitude; + +/// Initial longitude at which the receiver is centered. +@property (nonatomic) IBInspectable double longitude; + +/// Initial zoom level of the receiver. +@property (nonatomic) IBInspectable double zoomLevel; + +@end diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index 4139ef51c..632175a30 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -39,9 +39,8 @@ IB_DESIGNABLE /** @name Authorizing Access */ -/** Sets a Mapbox API access token for the map view. -* @param accessToken A Mapbox API token. */ -@property (nonatomic) IBInspectable NSString *accessToken; +/** Mapbox API access token for the map view. */ +@property (nonatomic) NSString *accessToken; #pragma mark - Managing Constraints @@ -166,10 +165,11 @@ IB_DESIGNABLE /** @name Styling the Map */ /** Mapbox map ID of the style currently displayed in the receiver, or `nil` if the style does not have a map ID. - - The style may lack a map ID if it is located at an HTTP, HTTPS, or asset: URL. Use -styleURL to get the URL in these cases. - */ -@property (nonatomic) IBInspectable NSString *mapID; +* +* The style may lack a map ID if it is located at an HTTP, HTTPS, or local file URL. Use `styleURL` to get the URL in these cases. +* +* To display the default style, set this property to `nil`. */ +@property (nonatomic) NSString *mapID; /** Returns the URLs to the styles bundled with the library. */ - (NSArray *)bundledStyleURLs; diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index b1a2eeadb..2ad9db688 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1,4 +1,5 @@ #import "MGLMapView.h" +#import "MGLMapView+IBAdditions.h" #import #import @@ -92,6 +93,8 @@ static NSURL *MGLURLForBundledStyleNamed(NSString *styleName) @implementation MGLMapView { BOOL _isTargetingInterfaceBuilder; + CLLocationDegrees _pendingLatitude; + CLLocationDegrees _pendingLongitude; } #pragma mark - Setup & Teardown - @@ -365,6 +368,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; // set initial position // mbglMap->setLatLngZoom(mbgl::LatLng(0, 0), mbglMap->getMinZoom()); + _pendingLatitude = NAN; + _pendingLongitude = NAN; // setup change delegate queue // @@ -1126,6 +1131,11 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; #pragma mark - Geography - ++ (NSSet *)keyPathsForValuesAffectingCenterCoordinate +{ + return [NSSet setWithObjects:@"latitude", @"longitude", nil]; +} + - (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated preservingTracking:(BOOL)tracking { self.userTrackingMode = (tracking ? self.userTrackingMode : MGLUserTrackingModeNone); @@ -2388,3 +2398,59 @@ class MBGLView : public mbgl::View }; @end + +@implementation MGLMapView (IBAdditions) + ++ (NSSet *)keyPathsForValuesAffectingLatitude +{ + return [NSSet setWithObject:@"centerCoordinate"]; +} + +- (double)latitude +{ + return self.centerCoordinate.latitude; +} + +- (void)setLatitude:(double)latitude +{ + if ( ! isnan(_pendingLongitude)) + { + self.centerCoordinate = CLLocationCoordinate2DMake(latitude, _pendingLongitude); + _pendingLatitude = NAN; + _pendingLongitude = NAN; + } + else + { + // Not enough info to make a valid center coordinate yet. Stash this + // latitude away until the longitude is set too. + _pendingLatitude = latitude; + } +} + ++ (NSSet *)keyPathsForValuesAffectingLongitude +{ + return [NSSet setWithObject:@"centerCoordinate"]; +} + +- (double)longitude +{ + return self.centerCoordinate.longitude; +} + +- (void)setLongitude:(double)longitude +{ + if ( ! isnan(_pendingLatitude)) + { + self.centerCoordinate = CLLocationCoordinate2DMake(_pendingLatitude, longitude); + _pendingLatitude = NAN; + _pendingLongitude = NAN; + } + else + { + // Not enough info to make a valid center coordinate yet. Stash this + // longitude away until the latitude is set too. + _pendingLongitude = longitude; + } +} + +@end From 2916ae2735bd46cdd87f32c0ab3efb5651ea8998 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Thu, 2 Apr 2015 15:41:29 -0700 Subject: [PATCH 5/7] formatting & docs changes Ran this through `./ios/docs/install_docs.sh` to double-check things. * Less-specific initializers above more-specific per Apple convention. * Shores up `accessToken` docs as property, not a method. * Edited comments for consistency and/or to keep them out of the docs. --- include/mbgl/ios/MGLMapView+IBAdditions.h | 5 ----- include/mbgl/ios/MGLMapView.h | 23 +++++++++++------------ platform/ios/MGLMapView.mm | 23 +++++++++++++---------- platform/ios/NSString+MGLAdditions.h | 2 +- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/include/mbgl/ios/MGLMapView+IBAdditions.h b/include/mbgl/ios/MGLMapView+IBAdditions.h index 4661e549e..fdd1ea350 100644 --- a/include/mbgl/ios/MGLMapView+IBAdditions.h +++ b/include/mbgl/ios/MGLMapView+IBAdditions.h @@ -17,13 +17,8 @@ // must use the type double because Interface Builder is unaware that // CLLocationDegrees is a typedef for double. -/// Initial latitude at which the receiver is centered. @property (nonatomic) IBInspectable double latitude; - -/// Initial longitude at which the receiver is centered. @property (nonatomic) IBInspectable double longitude; - -/// Initial zoom level of the receiver. @property (nonatomic) IBInspectable double zoomLevel; @end diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index 632175a30..3ee1b4e9d 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -20,19 +20,19 @@ IB_DESIGNABLE /** @name Initializing a Map View */ -/** Initialize a map view with a given frame, access token, and style URL. -* @param frame The frame with which to initialize the map view. -* @param accessToken A Mapbox API access token. -* @param styleURL The map style URL to use. Can be either an HTTP/HTTPS URL or a Mapbox map ID style URL (`mapbox://`). -* @return An initialized map view, or `nil` if the map view was unable to be initialized. */ -- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken styleURL:(NSURL *)styleURL; - -/** Initialize a map view with the default style given a frame and access token. +/** Initialize a map view with the default style and a given frame and access token. * @param frame The frame with which to initialize the map view. * @param accessToken A Mapbox API access token. * @return An initialized map view, or `nil` if the map view was unable to be initialized. */ - (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken; +/** Initialize a map view with a given frame, access token, and style URL. + * @param frame The frame with which to initialize the map view. + * @param accessToken A Mapbox API access token. + * @param styleURL The map style URL to use. Can be either an HTTP/HTTPS URL or a Mapbox map ID style URL (`mapbox://`). + * @return An initialized map view, or `nil` if the map view was unable to be initialized. */ +- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken styleURL:(NSURL *)styleURL; + - (instancetype)initWithFrame:(CGRect)frame __attribute__((unavailable("Instantiating an MGLMapView requires setting a style and/or an access token."))); #pragma mark - Authorizing Access @@ -166,7 +166,7 @@ IB_DESIGNABLE /** Mapbox map ID of the style currently displayed in the receiver, or `nil` if the style does not have a map ID. * -* The style may lack a map ID if it is located at an HTTP, HTTPS, or local file URL. Use `styleURL` to get the URL in these cases. +* The style may lack a map ID if it is located at an HTTP, HTTPS, or local file URL. Use `styleURL` to get the URL in these cases. * * To display the default style, set this property to `nil`. */ @property (nonatomic) NSString *mapID; @@ -175,9 +175,8 @@ IB_DESIGNABLE - (NSArray *)bundledStyleURLs; /** URL of the style currently displayed in the receiver. - - The URL may be a full HTTP or HTTPS URL or a Mapbox URL indicating the style’s map ID (`mapbox://`). To display the default style, set this property to `nil`. - */ +* +* The URL may be a full HTTP or HTTPS URL or a Mapbox URL indicating the style’s map ID (`mapbox://`). To display the default style, set this property to `nil`. */ @property (nonatomic) NSURL *styleURL; #pragma mark - Annotating the Map diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 2ad9db688..63ed8e11a 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -91,7 +91,8 @@ static NSURL *MGLURLForBundledStyleNamed(NSString *styleName) @end -@implementation MGLMapView { +@implementation MGLMapView +{ BOOL _isTargetingInterfaceBuilder; CLLocationDegrees _pendingLatitude; CLLocationDegrees _pendingLongitude; @@ -168,7 +169,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; [MGLMapboxEvents setToken:accessToken.mgl_stringOrNilIfEmpty]; } -+ (NSSet *)keyPathsForValuesAffectingStyleURL { ++ (NSSet *)keyPathsForValuesAffectingStyleURL +{ return [NSSet setWithObjects:@"mapID", @"accessToken", nil]; } @@ -180,10 +182,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (void)setStyleURL:(NSURL *)styleURL { - if ( _isTargetingInterfaceBuilder ) - { - return; - } + if (_isTargetingInterfaceBuilder) return; if ( ! styleURL) { @@ -245,7 +244,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; // load extensions // - dispatch_once(&loadGLExtensions, ^{ + dispatch_once(&loadGLExtensions, ^ + { const std::string extensions = (char *)glGetString(GL_EXTENSIONS); using namespace mbgl; @@ -1319,16 +1319,19 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng) return [NSArray arrayWithArray:_bundledStyleURLs]; } -+ (NSSet *)keyPathsForValuesAffectingMapID { ++ (NSSet *)keyPathsForValuesAffectingMapID +{ return [NSSet setWithObjects:@"styleURL", @"accessToken", nil]; } -- (NSString *)mapID { +- (NSString *)mapID +{ NSURL *styleURL = self.styleURL; return [styleURL.scheme isEqualToString:@"mapbox"] ? styleURL.host.mgl_stringOrNilIfEmpty : nil; } -- (void)setMapID:(NSString *)mapID { +- (void)setMapID:(NSString *)mapID +{ self.styleURL = [NSURL URLWithString:[NSString stringWithFormat:@"mapbox://%@", mapID]]; } diff --git a/platform/ios/NSString+MGLAdditions.h b/platform/ios/NSString+MGLAdditions.h index 263cfc8cb..913f1cacd 100644 --- a/platform/ios/NSString+MGLAdditions.h +++ b/platform/ios/NSString+MGLAdditions.h @@ -2,7 +2,7 @@ @interface NSString (MGLAdditions) -/// Returns the receiver if non-empty or \c nil if empty. +/** Returns the receiver if non-empty or nil if empty. */ - (NSString *)mgl_stringOrNilIfEmpty; @end From 6eb79338f3298de8f0137125783f505f3cb9b407 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Thu, 2 Apr 2015 16:15:36 -0700 Subject: [PATCH 6/7] allow setting nil mapID for default style --- include/mbgl/ios/MGLMapView.h | 4 +++- platform/ios/MGLMapView.mm | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index 3ee1b4e9d..5db1f425d 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -176,7 +176,9 @@ IB_DESIGNABLE /** URL of the style currently displayed in the receiver. * -* The URL may be a full HTTP or HTTPS URL or a Mapbox URL indicating the style’s map ID (`mapbox://`). To display the default style, set this property to `nil`. */ +* The URL may be a full HTTP or HTTPS URL or a Mapbox URL indicating the style’s map ID (`mapbox://`). +* +* To display the default style, set this property to `nil`. */ @property (nonatomic) NSURL *styleURL; #pragma mark - Annotating the Map diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 63ed8e11a..18937b94f 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1332,7 +1332,14 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng) - (void)setMapID:(NSString *)mapID { - self.styleURL = [NSURL URLWithString:[NSString stringWithFormat:@"mapbox://%@", mapID]]; + if (mapID) + { + self.styleURL = [NSURL URLWithString:[NSString stringWithFormat:@"mapbox://%@", mapID]]; + } + else + { + self.styleURL = nil; + } } - (NSArray *)getAppliedStyleClasses From d1fb43e37182e6364f6f0efa3c2644709893159a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Fri, 3 Apr 2015 00:13:34 -0700 Subject: [PATCH 7/7] Inspectable toggles Added inspectables for toggling zooming, panning, rotating, and showing the user location for parity with `MKMapView`. --- include/mbgl/ios/MGLMapView+IBAdditions.h | 11 ++++- platform/ios/MGLMapView.mm | 60 +++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/include/mbgl/ios/MGLMapView+IBAdditions.h b/include/mbgl/ios/MGLMapView+IBAdditions.h index fdd1ea350..df9a19a6b 100644 --- a/include/mbgl/ios/MGLMapView+IBAdditions.h +++ b/include/mbgl/ios/MGLMapView+IBAdditions.h @@ -11,7 +11,7 @@ @property (nonatomic) IBInspectable NSString *accessToken; @property (nonatomic) IBInspectable NSString *mapID; -// Some convenience properties related to the initial viewport. These properties +// 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 @@ -21,4 +21,13 @@ @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. + +@property (nonatomic) IBInspectable BOOL allowsZooming; +@property (nonatomic) IBInspectable BOOL allowsScrolling; +@property (nonatomic) IBInspectable BOOL allowsRotating; +@property (nonatomic) IBInspectable BOOL showsUserLocation; + @end diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 18937b94f..741f659ad 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1064,6 +1064,21 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; #pragma mark - Properties - ++ (NSSet *)keyPathsForValuesAffectingZoomEnabled +{ + return [NSSet setWithObject:@"allowsZooming"]; +} + ++ (NSSet *)keyPathsForValuesAffectingScrollEnabled +{ + return [NSSet setWithObject:@"allowsScrolling"]; +} + ++ (NSSet *)keyPathsForValuesAffectingRotateEnabled +{ + return [NSSet setWithObject:@"allowsRotating"]; +} + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" @@ -2463,4 +2478,49 @@ class MBGLView : public mbgl::View } } ++ (NSSet *)keyPathsForValuesAffectingAllowsZooming +{ + return [NSSet setWithObject:@"zoomEnabled"]; +} + +- (BOOL)allowsZooming +{ + return self.zoomEnabled; +} + +- (void)setAllowsZooming:(BOOL)allowsZooming +{ + self.zoomEnabled = allowsZooming; +} + ++ (NSSet *)keyPathsForValuesAffectingAllowsScrolling +{ + return [NSSet setWithObject:@"scrollEnabled"]; +} + +- (BOOL)allowsScrolling +{ + return self.scrollEnabled; +} + +- (void)setAllowsScrolling:(BOOL)allowsScrolling +{ + self.scrollEnabled = allowsScrolling; +} + ++ (NSSet *)keyPathsForValuesAffectingAllowsRotating +{ + return [NSSet setWithObject:@"rotateEnabled"]; +} + +- (BOOL)allowsRotating +{ + return self.rotateEnabled; +} + +- (void)setAllowsRotating:(BOOL)allowsRotating +{ + self.rotateEnabled = allowsRotating; +} + @end