[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.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||
|
||||
133
platform/ios/Mapbox.playground/Contents.swift
Normal file
133
platform/ios/Mapbox.playground/Contents.swift
Normal file
@@ -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)
|
||||
4
platform/ios/Mapbox.playground/contents.xcplayground
Normal file
4
platform/ios/Mapbox.playground/contents.xcplayground
Normal file
@@ -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>
|
||||
6
platform/ios/Mapbox.playground/timeline.xctimeline
Normal file
6
platform/ios/Mapbox.playground/timeline.xctimeline
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
7
platform/ios/app/MBXAnnotationView.h
Normal file
7
platform/ios/app/MBXAnnotationView.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#import <Mapbox/Mapbox.h>
|
||||
|
||||
@interface MBXAnnotationView : MGLAnnotationView
|
||||
|
||||
@property (nonatomic) UIColor *centerColor;
|
||||
|
||||
@end
|
||||
28
platform/ios/app/MBXAnnotationView.m
Normal file
28
platform/ios/app/MBXAnnotationView.m
Normal file
@@ -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
|
||||
@@ -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]]
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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">
|
||||
|
||||
20
platform/ios/src/MGLAnnotationView.h
Normal file
20
platform/ios/src/MGLAnnotationView.h
Normal file
@@ -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
|
||||
40
platform/ios/src/MGLAnnotationView.m
Normal file
40
platform/ios/src/MGLAnnotationView.m
Normal file
@@ -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
|
||||
13
platform/ios/src/MGLAnnotationView_Private.h
Normal file
13
platform/ios/src/MGLAnnotationView_Private.h
Normal file
@@ -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
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
MGLAnnotationView *annotationView;
|
||||
NSString *symbolName;
|
||||
NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation];
|
||||
|
||||
if (delegateImplementsViewForAnnotation)
|
||||
{
|
||||
annotationImage = [self.delegate mapView:self imageForAnnotation:annotation];
|
||||
}
|
||||
if ( ! annotationImage)
|
||||
{
|
||||
annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName];
|
||||
}
|
||||
if ( ! annotationImage)
|
||||
{
|
||||
annotationImage = self.defaultAnnotationImage;
|
||||
annotationView = [self annotationViewForAnnotation:annotation];
|
||||
if (annotationView)
|
||||
{
|
||||
annotationViewsForAnnotation[annotationValue] = annotationView;
|
||||
annotationView.center = [self convertCoordinate:annotation.coordinate toPointToView:self];
|
||||
[self.glView addSubview:annotationView];
|
||||
}
|
||||
}
|
||||
|
||||
NSString *symbolName = annotationImage.styleIconIdentifier;
|
||||
if ( ! symbolName)
|
||||
{
|
||||
symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
|
||||
annotationImage.styleIconIdentifier = symbolName;
|
||||
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;
|
||||
}
|
||||
|
||||
if ( ! self.annotationImagesByIdentifier[annotationImage.reuseIdentifier])
|
||||
{
|
||||
[self installAnnotationImage: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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user