Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
Made MGLMapView designable in IB
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
1ec5 committed Apr 3, 2015
1 parent e18e4b0 commit da13225
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 30 deletions.
2 changes: 2 additions & 0 deletions gyp/platform-ios.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
11 changes: 6 additions & 5 deletions include/mbgl/ios/MGLMapView.h
Original file line number Diff line number Diff line change
Expand Up @@ -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://<user.style>`).
* @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. */
Expand All @@ -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

Expand Down Expand Up @@ -81,7 +82,7 @@
/** @name Accessing the Delegate */

// TODO
@property(nonatomic, weak) id<MGLMapViewDelegate> delegate;
@property(nonatomic, weak) IBOutlet id<MGLMapViewDelegate> delegate;

#pragma mark - Manipulating the Visible Portion of the Map

Expand Down Expand Up @@ -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

Expand Down
175 changes: 160 additions & 15 deletions platform/ios/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -46,6 +47,7 @@
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};
Expand Down Expand Up @@ -88,7 +90,9 @@ @interface MGLMapView () <UIGestureRecognizerDelegate, GLKViewDelegate, CLLocati

@end

@implementation MGLMapView
@implementation MGLMapView {
BOOL _isTargetingInterfaceBuilder;
}

#pragma mark - Setup & Teardown -

Expand All @@ -106,24 +110,22 @@ @implementation MGLMapView
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
Expand Down Expand Up @@ -175,6 +177,11 @@ - (NSURL *)styleURL

- (void)setStyleURL:(NSURL *)styleURL
{
if ( _isTargetingInterfaceBuilder )
{
return;
}

if ( ! styleURL)
{
styleURL = MGLURLForBundledStyleNamed([NSString stringWithFormat:@"%@-v%@",
Expand All @@ -193,6 +200,8 @@ - (void)setStyleURL:(NSURL *)styleURL

- (BOOL)commonInit
{
_isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent;

// create context
//
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
Expand Down Expand Up @@ -362,8 +371,11 @@ - (BOOL)commonInit
_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();
Expand Down Expand Up @@ -550,7 +562,11 @@ - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
- (void)layoutSubviews
{
[super layoutSubviews];
mbglMap->triggerUpdate();

if ( ! _isTargetingInterfaceBuilder)
{
mbglMap->triggerUpdate();
}
}

#pragma mark - Life Cycle -
Expand Down Expand Up @@ -2186,6 +2202,135 @@ - (void)invalidate
[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:
Expand Down
24 changes: 14 additions & 10 deletions platform/ios/MGLMapboxEvents.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#import <CoreTelephony/CTCarrier.h>

#import "MGLMetricsLocationManager.h"
#import "NSProcessInfo+MGLAdditions.h"

#include <sys/sysctl.h>

Expand Down Expand Up @@ -176,17 +177,20 @@ + (instancetype)sharedManager {
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;
Expand Down
7 changes: 7 additions & 0 deletions platform/ios/NSProcessInfo+MGLAdditions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>

@interface NSProcessInfo (MGLAdditions)

- (BOOL)mgl_isInterfaceBuilderDesignablesAgent;

@end
10 changes: 10 additions & 0 deletions platform/ios/NSProcessInfo+MGLAdditions.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import "NSProcessInfo+MGLAdditions.h"

@implementation NSProcessInfo (MGLAdditions)

- (BOOL)mgl_isInterfaceBuilderDesignablesAgent
{
return [self.processName isEqualToString:@"IBDesignablesAgentCocoaTouch"];
}

@end

0 comments on commit da13225

Please sign in to comment.