summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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"