summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Bounds <jesse@rebounds.net>2016-04-22 07:43:55 -0600
committerJesse Bounds <jesse@rebounds.net>2016-05-13 15:51:40 -0700
commit39669243b742a399818e5152c873ca71fa971648 (patch)
tree68f7828306b1c538052ad7c436979e4423edc77a
parenta6ca18f23c0bff126507c5e038b789e7c125814a (diff)
downloadqtlocation-mapboxgl-39669243b742a399818e5152c873ca71fa971648.tar.gz
[ios] Introduce MGLAnnotationView and support for view annotations
Add an UIView subclass that can be used as the base class for all client provided UIViews for annotations. Teach MGLMapView to be able to display annotation views over the map if provided by the client delegate. For now, if the delegate provides a UIView then it will be used. If not, the map view will fall back to the old strategy of using GL annotations with an image provided by the delegate or a default image if not. The map keeps a reuse queue and will store annotation views that are panned offscreen in the queue if the application developer supplied a reuse queue identifer. The views in the queue are reused when more annotation views are required. This view reuse provides a performance gain when many annotations are shown and most of them are offscreen. iosapp now implements the new delegate method to supply a native view. Add a playground to the workspace to facilitate experimentation with new features. A playground is capable of importing frameworks if it exists in the same workspace that builds the imported framework. The initial playground demonstrates annotation views. This also fixes a crash due to nullptr in annotations array If the `annotations` method is called while the user dot's callout view was showing, the userdot annotation is represented as null in the annotation context map. This caused a crash when the null pointer was attempted to be converted into an NSArray via C array. This protects against this bug by filtering out such a null annotation.
-rw-r--r--.gitignore1
-rw-r--r--platform/ios/Mapbox.playground/Contents.swift133
-rw-r--r--platform/ios/Mapbox.playground/contents.xcplayground4
-rw-r--r--platform/ios/Mapbox.playground/timeline.xctimeline6
-rw-r--r--platform/ios/app/MBXAnnotationView.h7
-rw-r--r--platform/ios/app/MBXAnnotationView.m28
-rw-r--r--platform/ios/app/MBXViewController.m20
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj32
-rw-r--r--platform/ios/ios.xcodeproj/xcshareddata/xcschemes/iosapp.xcscheme5
-rw-r--r--platform/ios/src/MGLAnnotationView.h20
-rw-r--r--platform/ios/src/MGLAnnotationView.m40
-rw-r--r--platform/ios/src/MGLAnnotationView_Private.h13
-rw-r--r--platform/ios/src/MGLMapView.h5
-rw-r--r--platform/ios/src/MGLMapView.mm272
-rw-r--r--platform/ios/src/Mapbox.h1
15 files changed, 554 insertions, 33 deletions
diff --git a/.gitignore b/.gitignore
index 91df4776b7..d887dd3f90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,4 @@ xcuserdata
/node_modules
/platform/ios/benchmark/assets/glyphs/DIN*
/platform/ios/benchmark/assets/tiles/mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6
+**/token
diff --git a/platform/ios/Mapbox.playground/Contents.swift b/platform/ios/Mapbox.playground/Contents.swift
new file mode 100644
index 0000000000..14dba0cc4e
--- /dev/null
+++ b/platform/ios/Mapbox.playground/Contents.swift
@@ -0,0 +1,133 @@
+import UIKit
+import XCPlayground
+import Mapbox
+
+let width: CGFloat = 700
+let height: CGFloat = 800
+
+//: A control panel
+let panelWidth: CGFloat = 200
+let panel = UIView(frame: CGRect(x: width - panelWidth, y: 0, width: 200, height: 100))
+panel.alpha = 0.8
+panel.backgroundColor = UIColor.whiteColor()
+let deleteSwitchLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 30))
+deleteSwitchLabel.adjustsFontSizeToFitWidth = true
+deleteSwitchLabel.text = "Delete Markers"
+let deleteMarkerSwitchView = UISwitch(frame: CGRect(x: panelWidth - panelWidth / 2.0, y:0, width: 100, height: 50))
+panel.addSubview(deleteSwitchLabel)
+panel.addSubview(deleteMarkerSwitchView)
+let hideSwitchLabel = UILabel(frame: CGRect(x: 0, y: 30, width: 100, height: 30))
+hideSwitchLabel.adjustsFontSizeToFitWidth = true
+hideSwitchLabel.text = "Hide Markers"
+let hideMarkerSwitchView = UISwitch(frame: CGRect(x: panelWidth - panelWidth / 2.0, y: 30, width: 100, height: 50))
+panel.addSubview(hideSwitchLabel)
+panel.addSubview(hideMarkerSwitchView)
+
+//: # Mapbox Maps
+
+/*:
+ Put your access token into a plain text file called `token`. Then select the “token” placeholder below, go to Editor ‣ Insert File Literal, and select the `token` file.
+ */
+var accessToken = try String(contentsOfURL: <#token#>)
+MGLAccountManager.setAccessToken(accessToken)
+
+class PlaygroundAnnotationView: MGLAnnotationView {
+
+ override func prepareForReuse() {
+ hidden = hideMarkerSwitchView.on
+ }
+
+}
+
+//: Define a map delegate
+
+class MapDelegate: NSObject, MGLMapViewDelegate {
+
+ var annotationViewByAnnotation = [MGLPointAnnotation: PlaygroundAnnotationView]()
+
+ func mapView(mapView: MGLMapView, viewForAnnotation annotation: MGLAnnotation) -> MGLAnnotationView? {
+
+ var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier("annotation") as? PlaygroundAnnotationView
+
+ if (annotationView == nil) {
+ let av = PlaygroundAnnotationView(reuseIdentifier: "annotation")
+ av.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
+ let centerView = UIView(frame: CGRectInset(av.bounds, 3, 3))
+ centerView.backgroundColor = UIColor.whiteColor()
+ av.addSubview(centerView)
+ av.backgroundColor = UIColor.purpleColor()
+ annotationView = av
+ } else {
+ annotationView!.subviews.first?.backgroundColor = UIColor.greenColor()
+ }
+
+ annotationViewByAnnotation[annotation as! MGLPointAnnotation] = annotationView
+
+ return annotationView
+ }
+
+ func mapView(mapView: MGLMapView, didSelectAnnotation annotation: MGLAnnotation) {
+ let pointAnnotation = annotation as! MGLPointAnnotation
+ let annotationView: PlaygroundAnnotationView = annotationViewByAnnotation[pointAnnotation]!
+
+ for view in annotationViewByAnnotation.values {
+ view.layer.zPosition = -1
+ }
+
+ annotationView.layer.zPosition = 1
+
+ UIView.animateWithDuration(1.25, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.6, options: .CurveEaseOut, animations: {
+ annotationView.transform = CGAffineTransformMakeScale(1.8, 1.8)
+ }) { _ in
+ annotationView.transform = CGAffineTransformMakeScale(1, 1)
+
+ if deleteMarkerSwitchView.on {
+ mapView.removeAnnotation(pointAnnotation)
+ return
+ }
+
+ if hideMarkerSwitchView.on {
+ annotationView.hidden = true
+ }
+ }
+ }
+
+ func handleTap(press: UILongPressGestureRecognizer) {
+ let mapView: MGLMapView = press.view as! MGLMapView
+
+ if (press.state == .Recognized) {
+ let coordiante: CLLocationCoordinate2D = mapView.convertPoint(press.locationInView(mapView), toCoordinateFromView: mapView)
+ let annotation = MGLPointAnnotation()
+ annotation.title = "Dropped Marker"
+ annotation.coordinate = coordiante
+ mapView.addAnnotation(annotation)
+ mapView.showAnnotations([annotation], animated: true)
+ }
+ }
+
+}
+
+//: Create a map and its delegate
+
+let lat: CLLocationDegrees = 37.174057
+let lng: CLLocationDegrees = -104.490984
+let centerCoordinate = CLLocationCoordinate2D(latitude: lat, longitude: lng)
+
+let mapView = MGLMapView(frame: CGRect(x: 0, y: 0, width: width, height: height))
+mapView.frame = CGRect(x: 0, y: 0, width: width, height: height)
+
+XCPlaygroundPage.currentPage.liveView = mapView
+
+let mapDelegate = MapDelegate()
+mapView.delegate = mapDelegate
+
+let tapGesture = UILongPressGestureRecognizer(target: mapDelegate, action: #selector(mapDelegate.handleTap))
+mapView.addGestureRecognizer(tapGesture)
+
+//: Zoom in to a location
+
+mapView.setCenterCoordinate(centerCoordinate, zoomLevel: 12, animated: false)
+
+//: Add control panel
+
+mapView.addSubview(panel)
diff --git a/platform/ios/Mapbox.playground/contents.xcplayground b/platform/ios/Mapbox.playground/contents.xcplayground
new file mode 100644
index 0000000000..35968656f5
--- /dev/null
+++ b/platform/ios/Mapbox.playground/contents.xcplayground
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<playground version='5.0' target-platform='ios' display-mode='raw'>
+ <timeline fileName='timeline.xctimeline'/>
+</playground> \ No newline at end of file
diff --git a/platform/ios/Mapbox.playground/timeline.xctimeline b/platform/ios/Mapbox.playground/timeline.xctimeline
new file mode 100644
index 0000000000..bf468afeca
--- /dev/null
+++ b/platform/ios/Mapbox.playground/timeline.xctimeline
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Timeline
+ version = "3.0">
+ <TimelineItems>
+ </TimelineItems>
+</Timeline>
diff --git a/platform/ios/app/MBXAnnotationView.h b/platform/ios/app/MBXAnnotationView.h
new file mode 100644
index 0000000000..78dfe17699
--- /dev/null
+++ b/platform/ios/app/MBXAnnotationView.h
@@ -0,0 +1,7 @@
+#import <Mapbox/Mapbox.h>
+
+@interface MBXAnnotationView : MGLAnnotationView
+
+@property (nonatomic) UIColor *centerColor;
+
+@end
diff --git a/platform/ios/app/MBXAnnotationView.m b/platform/ios/app/MBXAnnotationView.m
new file mode 100644
index 0000000000..8cbe07a367
--- /dev/null
+++ b/platform/ios/app/MBXAnnotationView.m
@@ -0,0 +1,28 @@
+#import "MBXAnnotationView.h"
+
+@interface MBXAnnotationView ()
+
+@property (nonatomic) UIView *centerView;
+
+@end
+
+@implementation MBXAnnotationView
+
+- (void)layoutSubviews {
+ [super layoutSubviews];
+ if (!self.centerView) {
+ self.backgroundColor = [UIColor blueColor];
+ self.centerView = [[UIView alloc] initWithFrame:CGRectInset(self.bounds, 5.0, 5.0)];
+ self.centerView.backgroundColor = self.centerColor;
+ [self addSubview:self.centerView];
+ }
+}
+
+- (void)setCenterColor:(UIColor *)centerColor {
+ if (![_centerColor isEqual:centerColor]) {
+ _centerColor = centerColor;
+ self.centerView.backgroundColor = centerColor;
+ }
+}
+
+@end
diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m
index 88117de1a6..64d8c63d47 100644
--- a/platform/ios/app/MBXViewController.m
+++ b/platform/ios/app/MBXViewController.m
@@ -3,6 +3,7 @@
#import "MBXAppDelegate.h"
#import "MBXCustomCalloutView.h"
#import "MBXOfflinePacksTableViewController.h"
+#import "MBXAnnotationView.h"
#import <Mapbox/Mapbox.h>
@@ -17,6 +18,8 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
{ .latitude = -13.15589555, .longitude = -74.2178961777998 },
};
+static NSString * const MBXViewControllerAnnotationViewReuseIdentifer = @"MBXViewControllerAnnotationViewReuseIdentifer";
+
@interface MBXDroppedPinAnnotation : MGLPointAnnotation
@end
@@ -461,7 +464,7 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
toCoordinateFromView:self.mapView];
point.title = @"Dropped Pin";
point.subtitle = [[[MGLCoordinateFormatter alloc] init] stringFromCoordinate:point.coordinate];
- [self.mapView addAnnotation:point];
+ // Calling `addAnnotation:` on mapView is not required since `selectAnnotation:animated` has the side effect of adding the annotation if required
[self.mapView selectAnnotation:point animated:YES];
}
}
@@ -608,6 +611,21 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
#pragma mark - MGLMapViewDelegate
+- (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id<MGLAnnotation>)annotation
+{
+ MBXAnnotationView *annotationView = (MBXAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:MBXViewControllerAnnotationViewReuseIdentifer];
+ if (!annotationView)
+ {
+ annotationView = [[MBXAnnotationView alloc] initWithReuseIdentifier:MBXViewControllerAnnotationViewReuseIdentifer];
+ annotationView.frame = CGRectMake(0, 0, 40, 40);
+ annotationView.centerColor = [UIColor whiteColor];
+ } else {
+ // orange indicates that the annotation view was reused
+ annotationView.centerColor = [UIColor orangeColor];
+ }
+ return annotationView;
+}
+
- (MGLAnnotationImage *)mapView:(MGLMapView * __nonnull)mapView imageForAnnotation:(id <MGLAnnotation> __nonnull)annotation
{
if ([annotation isKindOfClass:[MBXDroppedPinAnnotation class]]
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index 8265f1c2d8..6271fc455b 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -7,6 +7,12 @@
objects = {
/* Begin PBXBuildFile section */
+ 4018B1C71CDC287F00F666AF /* MGLAnnotationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4018B1C41CDC277F00F666AF /* MGLAnnotationView.m */; };
+ 4018B1C81CDC287F00F666AF /* MGLAnnotationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4018B1C41CDC277F00F666AF /* MGLAnnotationView.m */; };
+ 4018B1C91CDC288A00F666AF /* MGLAnnotationView_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4018B1C31CDC277F00F666AF /* MGLAnnotationView_Private.h */; };
+ 4018B1CA1CDC288E00F666AF /* MGLAnnotationView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4018B1C51CDC277F00F666AF /* MGLAnnotationView.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 4018B1CB1CDC288E00F666AF /* MGLAnnotationView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4018B1C51CDC277F00F666AF /* MGLAnnotationView.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 40FDA76B1CCAAA6800442548 /* MBXAnnotationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 40FDA76A1CCAAA6800442548 /* MBXAnnotationView.m */; };
DA17BE301CC4BAC300402C41 /* MGLMapView_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = DA17BE2F1CC4BAC300402C41 /* MGLMapView_Internal.h */; };
DA17BE311CC4BDAA00402C41 /* MGLMapView_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = DA17BE2F1CC4BAC300402C41 /* MGLMapView_Internal.h */; };
DA1DC96A1CB6C6B7006E619F /* MBXCustomCalloutView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1DC9671CB6C6B7006E619F /* MBXCustomCalloutView.m */; };
@@ -304,6 +310,12 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 4018B1C31CDC277F00F666AF /* MGLAnnotationView_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAnnotationView_Private.h; sourceTree = "<group>"; };
+ 4018B1C41CDC277F00F666AF /* MGLAnnotationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLAnnotationView.m; sourceTree = "<group>"; };
+ 4018B1C51CDC277F00F666AF /* MGLAnnotationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAnnotationView.h; sourceTree = "<group>"; };
+ 402E9DE01CD2C76200FD4519 /* Mapbox.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Mapbox.playground; sourceTree = "<group>"; };
+ 40FDA7691CCAAA6800442548 /* MBXAnnotationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXAnnotationView.h; sourceTree = "<group>"; };
+ 40FDA76A1CCAAA6800442548 /* MBXAnnotationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXAnnotationView.m; sourceTree = "<group>"; };
DA17BE2F1CC4BAC300402C41 /* MGLMapView_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapView_Internal.h; sourceTree = "<group>"; };
DA1DC94A1CB6C1C2006E619F /* Mapbox GL.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Mapbox GL.app"; sourceTree = BUILT_PRODUCTS_DIR; };
DA1DC9501CB6C1C2006E619F /* MBXAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MBXAppDelegate.h; sourceTree = "<group>"; };
@@ -511,9 +523,18 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 402E9DE21CD3A56500FD4519 /* Playground */ = {
+ isa = PBXGroup;
+ children = (
+ 402E9DE01CD2C76200FD4519 /* Mapbox.playground */,
+ );
+ name = Playground;
+ sourceTree = "<group>";
+ };
DA1DC9411CB6C1C2006E619F = {
isa = PBXGroup;
children = (
+ 402E9DE21CD3A56500FD4519 /* Playground */,
DA1DC94C1CB6C1C2006E619F /* Demo App */,
DABCABA91CB80692000A7C39 /* Benchmarking App */,
DA8847D31CBAF91600AB86E3 /* SDK */,
@@ -543,6 +564,8 @@
children = (
DA1DC9501CB6C1C2006E619F /* MBXAppDelegate.h */,
DA1DC9981CB6E054006E619F /* MBXAppDelegate.m */,
+ 40FDA7691CCAAA6800442548 /* MBXAnnotationView.h */,
+ 40FDA76A1CCAAA6800442548 /* MBXAnnotationView.m */,
DA1DC9661CB6C6B7006E619F /* MBXCustomCalloutView.h */,
DA1DC9671CB6C6B7006E619F /* MBXCustomCalloutView.m */,
DA1DC9681CB6C6B7006E619F /* MBXOfflinePacksTableViewController.h */,
@@ -690,6 +713,9 @@
DA8848331CBAFB2A00AB86E3 /* Kit */ = {
isa = PBXGroup;
children = (
+ 4018B1C31CDC277F00F666AF /* MGLAnnotationView_Private.h */,
+ 4018B1C41CDC277F00F666AF /* MGLAnnotationView.m */,
+ 4018B1C51CDC277F00F666AF /* MGLAnnotationView.h */,
DA8848341CBAFB8500AB86E3 /* MGLAnnotationImage.h */,
DA8848401CBAFB9800AB86E3 /* MGLAnnotationImage_Private.h */,
DA8848411CBAFB9800AB86E3 /* MGLAnnotationImage.m */,
@@ -844,6 +870,7 @@
DA8848861CBB033F00AB86E3 /* Fabric+FABKits.h in Headers */,
DA8848201CBAFA6200AB86E3 /* MGLOfflinePack_Private.h in Headers */,
DA8847FA1CBAFA5100AB86E3 /* MGLPolyline.h in Headers */,
+ 4018B1C91CDC288A00F666AF /* MGLAnnotationView_Private.h in Headers */,
DA88482C1CBAFA6200AB86E3 /* NSBundle+MGLAdditions.h in Headers */,
DA88488E1CBB047F00AB86E3 /* reachability.h in Headers */,
DA8848231CBAFA6200AB86E3 /* MGLOfflineStorage_Private.h in Headers */,
@@ -855,6 +882,7 @@
DA8847FC1CBAFA5100AB86E3 /* MGLStyle.h in Headers */,
DA8847F01CBAFA5100AB86E3 /* MGLAnnotation.h in Headers */,
DA88483E1CBAFB8500AB86E3 /* MGLMapView+MGLCustomStyleLayerAdditions.h in Headers */,
+ 4018B1CA1CDC288E00F666AF /* MGLAnnotationView.h in Headers */,
DA8847EF1CBAFA5100AB86E3 /* MGLAccountManager.h in Headers */,
DA8848511CBAFB9800AB86E3 /* MGLAPIClient.h in Headers */,
DA35A2C91CCAAAD200E826B2 /* NSValue+MGLAdditions.h in Headers */,
@@ -919,6 +947,7 @@
DA35A2B21CCA141D00E826B2 /* MGLCompassDirectionFormatter.h in Headers */,
DABFB8731CBE9A9900D62B32 /* Mapbox.h in Headers */,
DABFB86B1CBE99E500D62B32 /* MGLTilePyramidOfflineRegion.h in Headers */,
+ 4018B1CB1CDC288E00F666AF /* MGLAnnotationView.h in Headers */,
DABFB85F1CBE99E500D62B32 /* MGLGeometry.h in Headers */,
DABFB85D1CBE99E500D62B32 /* MGLAccountManager.h in Headers */,
DA35A2BC1CCA9A6900E826B2 /* MGLClockDirectionFormatter.h in Headers */,
@@ -1217,6 +1246,7 @@
DA1DC96B1CB6C6B7006E619F /* MBXOfflinePacksTableViewController.m in Sources */,
DA1DC96A1CB6C6B7006E619F /* MBXCustomCalloutView.m in Sources */,
DA1DC99B1CB6E064006E619F /* MBXViewController.m in Sources */,
+ 40FDA76B1CCAAA6800442548 /* MBXAnnotationView.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1259,6 +1289,7 @@
DA88481C1CBAFA6200AB86E3 /* MGLGeometry.mm in Sources */,
DA88481F1CBAFA6200AB86E3 /* MGLMultiPoint.mm in Sources */,
DA88482B1CBAFA6200AB86E3 /* MGLTypes.m in Sources */,
+ 4018B1C71CDC287F00F666AF /* MGLAnnotationView.m in Sources */,
DA88481D1CBAFA6200AB86E3 /* MGLMapCamera.mm in Sources */,
DA8848261CBAFA6200AB86E3 /* MGLPolygon.mm in Sources */,
DA8848521CBAFB9800AB86E3 /* MGLAPIClient.m in Sources */,
@@ -1296,6 +1327,7 @@
DAA4E4301CBB730400178DFB /* MGLLocationManager.m in Sources */,
DAA4E4321CBB730400178DFB /* MGLMapView.mm in Sources */,
DAA4E41E1CBB730400178DFB /* MGLMapCamera.mm in Sources */,
+ 4018B1C81CDC287F00F666AF /* MGLAnnotationView.m in Sources */,
DAA4E4341CBB730400178DFB /* MGLUserLocationAnnotationView.m in Sources */,
DAA4E42C1CBB730400178DFB /* reachability.m in Sources */,
DAA4E4311CBB730400178DFB /* MGLMapboxEvents.m in Sources */,
diff --git a/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/iosapp.xcscheme b/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/iosapp.xcscheme
index 064add0fea..de74d64314 100644
--- a/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/iosapp.xcscheme
+++ b/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/iosapp.xcscheme
@@ -70,7 +70,8 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
- <MacroExpansion>
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA1DC9491CB6C1C2006E619F"
@@ -78,7 +79,7 @@
BlueprintName = "iosapp"
ReferencedContainer = "container:ios.xcodeproj">
</BuildableReference>
- </MacroExpansion>
+ </BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
diff --git a/platform/ios/src/MGLAnnotationView.h b/platform/ios/src/MGLAnnotationView.h
new file mode 100644
index 0000000000..475d9aa758
--- /dev/null
+++ b/platform/ios/src/MGLAnnotationView.h
@@ -0,0 +1,20 @@
+#import <UIKit/UIKit.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MGLAnnotationView : UIView
+
+- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier;
+
+/**
+ The string that identifies that this annotation view is reusable. (read-only)
+ */
+@property (nonatomic, readonly, nullable) NSString *reuseIdentifier;
+
+- (void)prepareForReuse;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLAnnotationView.m b/platform/ios/src/MGLAnnotationView.m
new file mode 100644
index 0000000000..9a82a8b332
--- /dev/null
+++ b/platform/ios/src/MGLAnnotationView.m
@@ -0,0 +1,40 @@
+#import "MGLAnnotationView.h"
+#import "MGLAnnotationView_Private.h"
+
+@interface MGLAnnotationView ()
+
+@property (nonatomic) id<MGLAnnotation> annotation;
+@property (nonatomic, readwrite, nullable) NSString *reuseIdentifier;
+
+@end
+
+@implementation MGLAnnotationView
+
+- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
+{
+ self = [super init];
+
+ if (self)
+ {
+ _reuseIdentifier = [reuseIdentifier copy];
+ }
+
+ return self;
+}
+
+- (void)prepareForReuse
+{
+ // Intentionally left blank. The default implementation of this method does nothing.
+}
+
+- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
+{
+ // Allow mbgl to drive animation of this view’s bounds.
+ if ([event isEqualToString:@"bounds"])
+ {
+ return [NSNull null];
+ }
+ return [super actionForLayer:layer forKey:event];
+}
+
+@end \ No newline at end of file
diff --git a/platform/ios/src/MGLAnnotationView_Private.h b/platform/ios/src/MGLAnnotationView_Private.h
new file mode 100644
index 0000000000..c9a887b6cc
--- /dev/null
+++ b/platform/ios/src/MGLAnnotationView_Private.h
@@ -0,0 +1,13 @@
+#import "MGLAnnotationView.h"
+#import "MGLAnnotation.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MGLAnnotationView (Private)
+
+@property (nonatomic) id<MGLAnnotation> annotation;
+@property (nonatomic, readwrite, nullable) NSString *reuseIdentifier;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h
index 0339a5457b..9435564942 100644
--- a/platform/ios/src/MGLMapView.h
+++ b/platform/ios/src/MGLMapView.h
@@ -8,6 +8,7 @@
NS_ASSUME_NONNULL_BEGIN
+@class MGLAnnotationView;
@class MGLAnnotationImage;
@class MGLUserLocation;
@class MGLPolyline;
@@ -931,6 +932,8 @@ IB_DESIGNABLE
*/
- (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier;
+- (nullable MGLAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier;
+
#pragma mark Managing Annotation Selections
/**
@@ -1151,6 +1154,8 @@ IB_DESIGNABLE
#pragma mark Managing the Display of Annotations
+- (nullable MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id <MGLAnnotation>)annotation;
+
/**
Returns an image object to use for the marker for the specified point annotation object.
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index a0c24eebe5..742e247fc5 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -37,6 +37,7 @@
#import "MGLUserLocationAnnotationView.h"
#import "MGLUserLocation_Private.h"
#import "MGLAnnotationImage_Private.h"
+#import "MGLAnnotationView_Private.h"
#import "MGLMapboxEvents.h"
#import "MGLCompactCalloutView.h"
@@ -167,6 +168,8 @@ public:
/// The annotation’s image’s reuse identifier.
NSString *imageReuseIdentifier;
MGLAnnotationAccessibilityElement *accessibilityElement;
+ MGLAnnotationView *annotationView;
+ NSString *viewReuseIdentifier;
};
/** An accessibility element representing the MGLMapView at large. */
@@ -240,10 +243,12 @@ public:
BOOL _opaque;
NS_MUTABLE_ARRAY_OF(NSURL *) *_bundledStyleURLs;
-
+
MGLAnnotationContextMap _annotationContextsByAnnotationTag;
/// Tag of the selected annotation. If the user location annotation is selected, this ivar is set to `MGLAnnotationTagNotFound`.
MGLAnnotationTag _selectedAnnotationTag;
+ NS_MUTABLE_DICTIONARY_OF(NSString *, NS_MUTABLE_ARRAY_OF(MGLAnnotationView *) *) *_annotationViewReuseQueueByIdentifier;
+
BOOL _userLocationAnnotationIsSelected;
/// Size of the rectangle formed by unioning the maximum slop area around every annotation image.
CGSize _unionedAnnotationImageSize;
@@ -272,6 +277,8 @@ public:
BOOL _delegateHasLineWidthsForShapeAnnotations;
MGLCompassDirectionFormatter *_accessibilityCompassFormatter;
+
+ CGSize _largestAnnotationViewSize;
}
#pragma mark - Setup & Teardown -
@@ -404,6 +411,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
// Set up annotation management and selection state.
_annotationImagesByIdentifier = [NSMutableDictionary dictionary];
_annotationContextsByAnnotationTag = {};
+ _annotationViewReuseQueueByIdentifier = [NSMutableDictionary dictionary];
_selectedAnnotationTag = MGLAnnotationTagNotFound;
_annotationsNearbyLastTap = {};
@@ -1404,6 +1412,13 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
}
return;
}
+
+ MGLAnnotationView *hitAnnotationView = [self annotationViewAtPoint:tapPoint];
+ if (hitAnnotationView)
+ {
+ [self selectAnnotation:hitAnnotationView.annotation animated:YES];
+ return;
+ }
MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:tapPoint persistingResults:YES];
if (hitAnnotationTag != MGLAnnotationTagNotFound)
@@ -2713,6 +2728,11 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
{
return pair.second.annotation;
});
+
+ annotations.erase(std::remove_if(annotations.begin(), annotations.end(),
+ [](const id <MGLAnnotation> annotation) { return annotation == nullptr; }),
+ annotations.end());
+
return [NSArray arrayWithObjects:&annotations[0] count:annotations.size()];
}
@@ -2763,8 +2783,11 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
std::vector<mbgl::PointAnnotation> points;
std::vector<mbgl::ShapeAnnotation> shapes;
- NSMutableArray *annotationImages = [NSMutableArray arrayWithCapacity:annotations.count];
+
+ NSMutableDictionary *annotationImagesForAnnotation = [NSMutableDictionary dictionary];
+ NSMutableDictionary *annotationViewsForAnnotation = [NSMutableDictionary dictionary];
+ BOOL delegateImplementsViewForAnnotation = [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)];
BOOL delegateImplementsImageForPoint = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)];
for (id <MGLAnnotation> annotation in annotations)
@@ -2777,32 +2800,51 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
}
else
{
- MGLAnnotationImage *annotationImage;
- if (delegateImplementsImageForPoint)
- {
- annotationImage = [self.delegate mapView:self imageForAnnotation:annotation];
- }
- if ( ! annotationImage)
- {
- annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName];
- }
- if ( ! annotationImage)
- {
- annotationImage = self.defaultAnnotationImage;
- }
+ MGLAnnotationView *annotationView;
+ NSString *symbolName;
+ NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation];
- NSString *symbolName = annotationImage.styleIconIdentifier;
- if ( ! symbolName)
+ if (delegateImplementsViewForAnnotation)
{
- symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
- annotationImage.styleIconIdentifier = symbolName;
+ annotationView = [self annotationViewForAnnotation:annotation];
+ if (annotationView)
+ {
+ annotationViewsForAnnotation[annotationValue] = annotationView;
+ annotationView.center = [self convertCoordinate:annotation.coordinate toPointToView:self];
+ [self.glView addSubview:annotationView];
+ }
}
- if ( ! self.annotationImagesByIdentifier[annotationImage.reuseIdentifier])
- {
- [self installAnnotationImage:annotationImage];
+ if ( ! annotationView) {
+ MGLAnnotationImage *annotationImage;
+
+ if (delegateImplementsImageForPoint)
+ {
+ annotationImage = [self.delegate mapView:self imageForAnnotation:annotation];
+ }
+ if ( ! annotationImage)
+ {
+ annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName];
+ }
+ if ( ! annotationImage)
+ {
+ annotationImage = self.defaultAnnotationImage;
+ }
+
+ symbolName = annotationImage.styleIconIdentifier;
+
+ if ( ! symbolName)
+ {
+ symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
+ annotationImage.styleIconIdentifier = symbolName;
+ }
+ if ( ! self.annotationImagesByIdentifier[annotationImage.reuseIdentifier])
+ {
+ [self installAnnotationImage:annotationImage];
+ }
+
+ annotationImagesForAnnotation[annotationValue] = annotationImage;
}
- [annotationImages addObject:annotationImage];
points.emplace_back(MGLLatLngFromLocationCoordinate2D(annotation.coordinate), symbolName.UTF8String ?: "");
}
@@ -2810,20 +2852,30 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
if (points.size())
{
+ // refactor this to build contexts above and just associate with tags here
+
std::vector<MGLAnnotationTag> annotationTags = _mbglMap->addPointAnnotations(points);
for (size_t i = 0; i < annotationTags.size(); ++i)
{
- MGLAnnotationTag annotationTag = annotationTags[i];
- MGLAnnotationImage *annotationImage = annotationImages[i];
- annotationImage.styleIconIdentifier = @(points[i].icon.c_str());
- id <MGLAnnotation> annotation = annotations[i];
+ id<MGLAnnotation> annotation = annotations[i];
+ NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation];
MGLAnnotationContext context;
context.annotation = annotation;
- context.imageReuseIdentifier = annotationImage.reuseIdentifier;
- _annotationContextsByAnnotationTag[annotationTag] = context;
+ MGLAnnotationImage *annotationImage = annotationImagesForAnnotation[annotationValue];
+
+ if (annotationImage) {
+ context.imageReuseIdentifier = annotationImage.reuseIdentifier;
+ }
+ MGLAnnotationView *annotationView = annotationViewsForAnnotation[annotationValue];
+ if (annotationView) {
+ context.annotationView = annotationView;
+ context.viewReuseIdentifier = annotationView.reuseIdentifier;
+ }
+ MGLAnnotationTag annotationTag = annotationTags[i];
+ _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];
@@ -2864,6 +2916,20 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
return annotationImage;
}
+- (MGLAnnotationView *)annotationViewForAnnotation:(id<MGLAnnotation>)annotation
+{
+ MGLAnnotationView *annotationView = [self.delegate mapView:self viewForAnnotation:annotation];
+
+ if (annotationView)
+ {
+ annotationView.annotation = annotation;
+ CGRect bounds = UIEdgeInsetsInsetRect({ CGPointZero, annotationView.frame.size }, annotationView.alignmentRectInsets);
+ _largestAnnotationViewSize = CGSizeMake(bounds.size.width / 2.0, bounds.size.height / 2.0);
+ }
+
+ return annotationView;
+}
+
- (double)alphaForShapeAnnotation:(MGLShape *)annotation
{
if (_delegateHasAlphasForShapeAnnotations)
@@ -2962,6 +3028,11 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
{
continue;
}
+
+ MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
+ MGLAnnotationView *annotationView = annotationContext.annotationView;
+ [annotationView removeFromSuperview];
+
annotationTagsToRemove.push_back(annotationTag);
if (annotationTag == _selectedAnnotationTag)
@@ -3025,6 +3096,35 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
return self.annotationImagesByIdentifier[identifier];
}
+- (nullable MGLAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier
+{
+ NSMutableArray *annotationViewReuseQueue = [self annotationViewReuseQueueForIdentifier:identifier];
+ MGLAnnotationView *reusableView = annotationViewReuseQueue.firstObject;
+ [reusableView prepareForReuse];
+ [annotationViewReuseQueue removeObject:reusableView];
+
+ return reusableView;
+}
+
+- (MGLAnnotationView *)annotationViewAtPoint:(CGPoint)point
+{
+ std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:self.bounds];
+
+ for(auto const& annotationTag: annotationTags)
+ {
+ auto &annotationContext = _annotationContextsByAnnotationTag[annotationTag];
+ MGLAnnotationView *annotationView = annotationContext.annotationView;
+ CGPoint convertedPoint = [self convertPoint:point toView:annotationView];
+
+ if ([annotationView pointInside:convertedPoint withEvent:nil])
+ {
+ return annotationView;
+ }
+ }
+
+ return nil;
+}
+
/**
Returns the tag of the annotation at the given point in the view.
@@ -3067,6 +3167,8 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
NSAssert(annotation, @"Unknown annotation found nearby tap");
+ MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag[annotationTag];
+
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
if ( ! annotationImage.enabled)
{
@@ -3076,8 +3178,10 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
// Filter out the annotation if the fattened finger didn’t land
// within the image’s alignment rect.
CGRect annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate];
+
return !!!CGRectIntersectsRect(annotationRect, hitRect);
});
+
nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end));
}
@@ -3224,10 +3328,26 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
if (annotationTag == MGLAnnotationTagNotFound && annotation != self.userLocation)
{
[self addAnnotation:annotation];
+ annotationTag = [self annotationTagForAnnotation:annotation];
+ if (annotationTag == MGLAnnotationTagNotFound) return;
}
- // The annotation can’t be selected if no part of it is hittable.
+ // By default attempt to use the GL annotation image frame as the positioning rect.
CGRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag];
+
+ if (annotation != self.userLocation)
+ {
+ MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
+
+ if (annotationContext.annotationView)
+ {
+ // Annotations represented by views use the view frame as the positioning rect.
+ positioningRect = annotationContext.annotationView.frame;
+ }
+ }
+
+ // The client can request that any annotation be selected (even ones that are offscreen).
+ // The annotation can’t be selected if no part of it is hittable.
if ( ! CGRectIntersectsRect(positioningRect, self.bounds) && annotation != self.userLocation)
{
return;
@@ -3320,6 +3440,8 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
/// and is appropriate for positioning a popover.
- (CGRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annotationTag
{
+ MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag[annotationTag];
+
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
if ( ! annotation)
{
@@ -3337,6 +3459,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
CGRect positioningRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate];
positioningRect.origin.x -= 0.5;
+
return CGRectInset(positioningRect, -MGLAnnotationImagePaddingForCallout,
-MGLAnnotationImagePaddingForCallout);
}
@@ -4221,6 +4344,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
{
[self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:(change == mbgl::MapChangeDidFinishRenderingFrameFullyRendered)];
}
+ [self updateAnnotationViews];
break;
}
}
@@ -4231,6 +4355,85 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
[self updateUserLocationAnnotationViewAnimatedWithDuration:0];
}
+- (void)updateAnnotationViews
+{
+ BOOL delegateImplementsViewForAnnotation = [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)];
+
+ if (!delegateImplementsViewForAnnotation)
+ {
+ return;
+ }
+
+ // Update all visible annotation views
+ std::set<MGLAnnotationTag> visibleTags;
+ std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:CGRectInset(self.bounds, -_largestAnnotationViewSize.width - MGLAnnotationUpdateViewportOutset.width, -_largestAnnotationViewSize.height - MGLAnnotationUpdateViewportOutset.width)];
+
+ for(auto const& annotationTag: annotationTags)
+ {
+ auto &annotationContext = _annotationContextsByAnnotationTag[annotationTag];
+ id<MGLAnnotation> annotation = annotationContext.annotation;
+
+
+ // If there is no annotation view at this point, it means the context's view was reused by some
+ // other context so we need to reuse or make a new view.
+ if (!annotationContext.annotationView)
+ {
+ MGLAnnotationView *annotationView = [self annotationViewForAnnotation:annotation];
+
+ if (annotationView)
+ {
+ // If the annotation view has no superview it means it was never used before so add it
+ if (!annotationView.superview)
+ {
+ [self.glView addSubview:annotationView];
+ }
+
+ annotationContext.annotationView = annotationView;
+ }
+ }
+
+ annotationContext.annotationView.center = [self convertCoordinate:annotation.coordinate toPointToView:self];;
+ visibleTags.insert(annotationTag);
+ }
+
+ // Hide and add offscreen annotation views to reuse queue
+ for (auto &pair : _annotationContextsByAnnotationTag)
+ {
+ MGLAnnotationTag annotationTag = pair.first;
+ MGLAnnotationContext &annotationContext = pair.second;
+ MGLAnnotationView *annotationView = annotationContext.annotationView;
+ const bool tagIsNotVisible = visibleTags.find(annotationTag) == visibleTags.end();
+
+ // The call to `annotationTagsInRect:` (above) does not return the correct result when the
+ // map is tilted and the user is scrolling quickly. So, some annotation views get stuck in
+ // a limbo state where they are onscreen and put on the reuse queue. Hiding the views hides
+ // the bug until we fix the result of `annotationTagsInRect:`.
+ annotationView.hidden = tagIsNotVisible;
+
+ if (annotationView && annotationView.reuseIdentifier && tagIsNotVisible)
+ {
+ [self enqueueAnnotationViewForAnnotationContext:annotationContext];
+ }
+ }
+}
+
+- (void)enqueueAnnotationViewForAnnotationContext:(MGLAnnotationContext &)annotationContext
+{
+ MGLAnnotationView *annotationView = annotationContext.annotationView;
+
+ if (!annotationView) return;
+
+ if (annotationContext.viewReuseIdentifier)
+ {
+ NSMutableArray *annotationViewReuseQueue = [self annotationViewReuseQueueForIdentifier:annotationContext.viewReuseIdentifier];
+ if (![annotationViewReuseQueue containsObject:annotationView])
+ {
+ [annotationViewReuseQueue addObject:annotationView];
+ annotationContext.annotationView = nil;
+ }
+ }
+}
+
- (void)updateUserLocationAnnotationViewAnimatedWithDuration:(NSTimeInterval)duration
{
MGLUserLocationAnnotationView *annotationView = self.userLocationAnnotationView;
@@ -4492,6 +4695,15 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
views:views]];
}
+- (NS_MUTABLE_ARRAY_OF(MGLAnnotationView *) *)annotationViewReuseQueueForIdentifier:(NSString *)identifier {
+ if (!_annotationViewReuseQueueByIdentifier[identifier])
+ {
+ _annotationViewReuseQueueByIdentifier[identifier] = [NSMutableArray array];
+ }
+
+ return _annotationViewReuseQueueByIdentifier[identifier];
+}
+
class MBGLView : public mbgl::View
{
public:
diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h
index df08c1b4a2..3b7361a756 100644
--- a/platform/ios/src/Mapbox.h
+++ b/platform/ios/src/Mapbox.h
@@ -6,6 +6,7 @@ FOUNDATION_EXPORT double MapboxVersionNumber;
/// Project version string for Mapbox.
FOUNDATION_EXPORT const unsigned char MapboxVersionString[];
+#import "MGLAnnotationView.h"
#import "MGLAccountManager.h"
#import "MGLAnnotation.h"
#import "MGLAnnotationImage.h"