refs #894, fixes #1074: callout views for iOS

This commit is contained in:
Justin R. Miller
2015-03-24 18:43:24 -07:00
parent 3490622dd2
commit 10a30daed6
10 changed files with 279 additions and 49 deletions

4
.gitmodules vendored
View File

@@ -13,3 +13,7 @@
[submodule "test/ios/KIF"]
path = test/ios/KIF
url = https://github.com/mapbox/KIF.git
[submodule "platform/ios/vendor/SMCalloutView"]
path = platform/ios/vendor/SMCalloutView
url = https://github.com/nfarina/calloutview.git

View File

@@ -25,10 +25,13 @@ config/%.gypi: configure
styles/styles:
git submodule update --init styles
SMCalloutView:
git submodule update --init platform/ios/vendor/SMCalloutView
#### Library builds ############################################################
.PRECIOUS: Makefile/mbgl
Makefile/mbgl: config/$(HOST).gypi styles/styles
Makefile/mbgl: config/$(HOST).gypi styles/styles SMCalloutView
deps/run_gyp mbgl.gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f make
mbgl: Makefile/mbgl
@@ -41,13 +44,13 @@ install: Makefile/mbgl
LINK=`pwd`/gyp/link.py $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) install
.PRECIOUS: Xcode/mbgl
Xcode/mbgl: config/$(HOST).gypi styles/styles
Xcode/mbgl: config/$(HOST).gypi styles/styles SMCalloutView
deps/run_gyp mbgl.gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f xcode
##### Test builds ##############################################################
.PRECIOUS: Makefile/test
Makefile/test: test/test.gyp config/$(HOST).gypi styles/styles
Makefile/test: test/test.gyp config/$(HOST).gypi styles/styles SMCalloutView
deps/run_gyp test/test.gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f make
test: Makefile/test
@@ -58,7 +61,7 @@ test-%: test
.PRECIOUS: Xcode/test
Xcode/test: test/test.gyp config/osx.gypi styles/styles
Xcode/test: test/test.gyp config/osx.gypi styles/styles SMCalloutView
deps/run_gyp test/test.gyp $(CONFIG_osx) $(LIBS_osx) --generator-output=./build/osx -f xcode
.PHONY: lproj lbuild run-xlinux
@@ -108,7 +111,7 @@ xproj: xosx-proj
#### iOS application builds ####################################################
.PRECIOUS: Xcode/ios
Xcode/ios: ios/app/mapboxgl-app.gyp config/ios.gypi styles/styles
Xcode/ios: ios/app/mapboxgl-app.gyp config/ios.gypi styles/styles SMCalloutView
deps/run_gyp ios/app/mapboxgl-app.gyp $(CONFIG_ios) $(LIBS_ios) --generator-output=./build/ios -f xcode
.PHONY: ios-proj ios run-ios

View File

@@ -34,6 +34,8 @@
'../platform/ios/NSDictionary+MGLAdditions.m',
'../include/mbgl/ios/UIColor+MGLAdditions.h',
'../platform/ios/UIColor+MGLAdditions.m',
'../platform/ios/vendor/SMCalloutView/SMCalloutView.h',
'../platform/ios/vendor/SMCalloutView/SMCalloutView.m',
],
'variables': {
@@ -52,6 +54,7 @@
'-framework GLKit',
'-framework MobileCoreServices',
'-framework OpenGLES',
'-framework QuartzCore',
'-framework SystemConfiguration',
'-framework UIKit',
],

View File

@@ -251,11 +251,15 @@
@end
#pragma mark - MGLMapViewDelegate
/** The MGLMapViewDelegate protocol defines a set of optional methods that you can use to receive map-related update messages. Because many map operations require the MGLMapView class to load data asynchronously, the map view calls these methods to notify your application when specific operations complete. The map view also uses these methods to request annotation marker symbology and to manage interactions with those markers. */
@protocol MGLMapViewDelegate <NSObject>
@optional
#pragma mark - Managing the Display of Annotations
/** @name Managing the Display of Annotations */
/** Returns the style's symbol name to use for the marker for the specified point annotation object.
@@ -264,14 +268,54 @@
* @return The marker symbol to display for the specified annotation or `nil` if you want to display the default symbol. */
- (NSString *)mapView:(MGLMapView *)mapView symbolNameForAnnotation:(id <MGLAnnotation>)annotation;
/** Returns a Boolean value indicating whether the annotation is able to display extra information in a callout bubble.
*
* If the value returned is `YES`, a standard callout bubble is shown when the user taps a selected annotation. The callout uses the title and subtitle text from the associated annotation object. If there is no title text, though, the annotation will not show a callout. The callout also displays any custom callout views returned by the delegate for the left and right callout accessory views.
*
* If the value returned is `NO`, the value of the title and subtitle strings are ignored.
*
* @param mapView The map view that requested the annotation callout ability.
* @param annotation The object representing the annotation.
* @return A Boolean indicating whether the annotation should show a callout. */
- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation;
/** Return the view to display on the left side of the standard callout bubble.
*
* The default value is treated as if `nil`. The left callout view is typically used to display information about the annotation or to link to custom information provided by your application.
*
* If the view you specify is also a descendant of the `UIControl` class, you can use the map view’s delegate to receive notifications when your control is tapped. If it does not descend from `UIControl`, your view is responsible for handling any touch events within its bounds.
*
* @param mapView The map view presenting the annotation callout.
* @param annotation The object representing the annotation with the callout.
* @return The accessory view to display. */
- (UIView *)mapView:(MGLMapView *)mapView leftCalloutAccessoryViewForAnnotation:(id <MGLAnnotation>)annotation;
/** Return the view to display on the right side of the standard callout bubble.
*
* The default value is treated is if `nil`. The right callout view is typically used to link to more detailed information about the annotation. A common view to specify for this property is `UIButton` object whose type is set to `UIButtonTypeDetailDisclosure`.
*
* If the view you specify is also a descendant of the `UIControl` class, you can use the map view’s delegate to receive notifications when your control is tapped. If it does not descend from `UIControl`, your view is responsible for handling any touch events within its bounds.
*
* @param mapView The map view presenting the annotation callout.
* @param annotation The object representing the annotation with the callout.
* @return The accessory view to display. */
- (UIView *)mapView:(MGLMapView *)mapView rightCalloutAccessoryViewForAnnotation:(id <MGLAnnotation>)annotation;
#pragma mark - Responding to Map Position Changes
// Responding to Map Position Changes
// TODO
- (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated;
// TODO
- (void)mapViewRegionIsChanging:(MGLMapView *)mapView;
// TODO
- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated;
#pragma mark - Loading the Map Data
// Loading the Map Data
// TODO
@@ -289,4 +333,39 @@
// TODO
- (void)mapViewDidFinishRenderingMap:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered;
#pragma mark - Managing Annotations
/** @name Managing Annotations */
/* Tells the delegate that the user tapped one of the annotation's accessory buttons.
*
* Accessory views contain custom content and are positioned on either side of the annotation title text. If a view you specify is a descendant of the `UIControl` class, the map view calls this method as a convenience whenever the user taps your view. You can use this method to respond to taps and perform any actions associated with that control. For example, if your control displayed additional information about the annotation, you could use this method to present a modal panel with that information.
*
* If your custom accessory views are not descendants of the `UIControl` class, the map view does not call this method.
*
* @param mapView The map view containing the specified annotation.
* @param annotation The annotation whose button was tapped.
* @param control The control that was tapped. */
- (void)mapView:(MGLMapView *)mapView annotation:(id <MGLAnnotation>)annotation calloutAccessoryControlTapped:(UIControl *)control;
#pragma mark - Selecting Annotations
/** @name Selecting Annotations */
/* Tells the delegate that one of its annotations was selected.
*
* You can use this method to track changes in the selection state of annotations.
*
* @param mapView The map view containing the annotation.
* @param annotation The annotation that was selected. */
- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id <MGLAnnotation>)annotation;
/* Tells the delegate that one of its annotations was deselected.
*
* You can use this method to track changes in the selection state of annotations.
*
* @param mapView The map view containing the annotation.
* @param annotation The annotation that was deselected. */
- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id <MGLAnnotation>)annotation;
@end

View File

@@ -153,6 +153,7 @@ public:
// Annotations
void setDefaultPointAnnotationSymbol(const std::string&);
double getTopOffsetPixelsForAnnotationSymbol(const std::string&);
uint32_t addPointAnnotation(const LatLng&, const std::string& symbol);
std::vector<uint32_t> addPointAnnotations(const std::vector<LatLng>&,
const std::vector<std::string>& symbols);

View File

@@ -10,14 +10,15 @@ class Map;
enum MapChange : uint8_t {
MapChangeRegionWillChange = 0,
MapChangeRegionWillChangeAnimated = 1,
MapChangeRegionDidChange = 2,
MapChangeRegionDidChangeAnimated = 3,
MapChangeWillStartLoadingMap = 4,
MapChangeDidFinishLoadingMap = 5,
MapChangeDidFailLoadingMap = 6,
MapChangeWillStartRenderingMap = 7,
MapChangeDidFinishRenderingMap = 8,
MapChangeDidFinishRenderingMapFullyRendered = 9
MapChangeRegionIsChanging = 2,
MapChangeRegionDidChange = 3,
MapChangeRegionDidChangeAnimated = 4,
MapChangeWillStartLoadingMap = 5,
MapChangeDidFinishLoadingMap = 6,
MapChangeDidFailLoadingMap = 7,
MapChangeWillStartRenderingMap = 8,
MapChangeDidFinishRenderingMap = 9,
MapChangeDidFinishRenderingMapFullyRendered = 10
};
class View {

View File

@@ -21,7 +21,7 @@ static NSArray *const kStyleNames = @[
static NSString *const kStyleVersion = @"v7";
@interface MBXViewController () <UIActionSheetDelegate, CLLocationManagerDelegate>
@interface MBXViewController () <UIActionSheetDelegate, CLLocationManagerDelegate, MGLMapViewDelegate>
@property (nonatomic) MGLMapView *mapView;
@property (nonatomic) CLLocationManager *locationManager;
@@ -70,6 +70,8 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
self.mapView.viewControllerForLayoutGuides = self;
self.mapView.delegate = self;
self.view.tintColor = kTintColor;
self.navigationController.navigationBar.tintColor = kTintColor;
self.mapView.tintColor = kTintColor;
@@ -338,6 +340,13 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
[self.locationManager stopUpdatingLocation];
}
#pragma mark - MGLMapViewDelegate
- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation
{
return YES;
}
#pragma clang diagnostic pop
@end

View File

@@ -18,6 +18,8 @@
#import "MGLStyleFunctionValue.h"
#import "MGLAnnotation.h"
#import "SMCalloutView.h"
#import "UIColor+MGLAdditions.h"
#import "NSArray+MGLAdditions.h"
#import "NSDictionary+MGLAdditions.h"
@@ -77,9 +79,10 @@ NSString *const MGLAnnotationIDKey = @"MGLAnnotationIDKey";
@property (nonatomic) UIRotationGestureRecognizer *rotate;
@property (nonatomic) UILongPressGestureRecognizer *quickZoom;
@property (nonatomic) NSMutableArray *bundledStyleNames;
@property (nonatomic) NSMapTable *annotationsStore;
@property (nonatomic) NSMapTable *annotationIDsByAnnotation;
@property (nonatomic) std::vector<uint32_t> annotationsNearbyLastTap;
@property (nonatomic, weak) id <MGLAnnotation> selectedAnnotation;
@property (nonatomic) SMCalloutView *selectedAnnotationCalloutView;
@property (nonatomic, readonly) NSDictionary *allowedStyleTypes;
@property (nonatomic) CGPoint centerPoint;
@property (nonatomic) CGFloat scale;
@@ -108,8 +111,6 @@ NSString *const MGLAnnotationIDKey = @"MGLAnnotationIDKey";
@implementation MGLMapView
@synthesize bundledStyleNames=_bundledStyleNames;
#pragma mark - Setup & Teardown -
@dynamic debugActive;
@@ -291,7 +292,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
// setup annotations
//
_annotationsStore = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory];
_annotationIDsByAnnotation = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory];
std::string defaultSymbolName([MGLDefaultStyleMarkerSymbolName cStringUsingEncoding:[NSString defaultCStringEncoding]]);
mbglMap->setDefaultPointAnnotationSymbol(defaultSymbolName);
@@ -675,7 +676,11 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
[weakSelf notifyMapChange:@(mbgl::MapChangeRegionDidChangeAnimated)];
}];
}
else
{
[self notifyMapChange:@(mbgl::MapChangeRegionDidChange)];
}
// Send Map Drag End Event
CGPoint ptInView = CGPointMake([pan locationInView:pan.view].x, [pan locationInView:pan.view].y);
CLLocationCoordinate2D coord = [self convertPoint:ptInView toCoordinateFromView:pan.view];
@@ -683,7 +688,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
[dict setValue:[[NSNumber alloc] initWithDouble:coord.latitude] forKey:@"lat"];
[dict setValue:[[NSNumber alloc] initWithDouble:coord.longitude] forKey:@"lng"];
[dict setValue:[[NSNumber alloc] initWithDouble:[self zoomLevel]] forKey:@"zoom"];
[[MGLMapboxEvents sharedManager] pushEvent:@"map.dragend" withAttributes:dict];
}
}
@@ -720,7 +725,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
[self unrotateIfNeededAnimated:YES];
[self notifyMapChange:@(mbgl::MapChangeRegionDidChangeAnimated)];
[self notifyMapChange:@(mbgl::MapChangeRegionDidChange)];
}
}
@@ -760,7 +765,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
[self unrotateIfNeededAnimated:YES];
[self notifyMapChange:@(mbgl::MapChangeRegionDidChangeAnimated)];
[self notifyMapChange:@(mbgl::MapChangeRegionDidChange)];
}
}
@@ -771,8 +776,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
CGPoint tapPoint = [singleTap locationInView:self];
// tolerances based on touch size & typical marker aspect ratio
CGFloat toleranceWidth = 50;
CGFloat toleranceHeight = 75;
CGFloat toleranceWidth = 40;
CGFloat toleranceHeight = 60;
// setup a recognition area weighted 2/3 of the way above the point to account for average marker imagery
CGRect tapRect = CGRectMake(tapPoint.x - toleranceWidth / 2, tapPoint.y - 2 * toleranceHeight / 3, toleranceWidth, toleranceHeight);
@@ -813,7 +818,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
{
// the selection candidates haven't changed; cycle through them
if (self.selectedAnnotation &&
[[[self.annotationsStore objectForKey:self.selectedAnnotation]
[[[self.annotationIDsByAnnotation objectForKey:self.selectedAnnotation]
objectForKey:MGLAnnotationIDKey] unsignedIntValue] == self.annotationsNearbyLastTap.back())
{
// the selected annotation is the last in the set; cycle back to the first
@@ -823,7 +828,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
else if (self.selectedAnnotation)
{
// otherwise increment the selection through the candidates
uint32_t currentID = [[[self.annotationsStore objectForKey:self.selectedAnnotation] objectForKey:MGLAnnotationIDKey] unsignedIntValue];
uint32_t currentID = [[[self.annotationIDsByAnnotation objectForKey:self.selectedAnnotation] objectForKey:MGLAnnotationIDKey] unsignedIntValue];
auto result = std::find(self.annotationsNearbyLastTap.begin(), self.annotationsNearbyLastTap.end(), currentID);
auto distance = std::distance(self.annotationsNearbyLastTap.begin(), result);
newSelectedAnnotationID = self.annotationsNearbyLastTap[distance + 1];
@@ -852,11 +857,11 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
if (newSelectedAnnotationID >= 0)
{
// find & select model object for selection
NSEnumerator *enumerator = self.annotationsStore.keyEnumerator;
NSEnumerator *enumerator = self.annotationIDsByAnnotation.keyEnumerator;
while (id <MGLAnnotation> annotation = enumerator.nextObject)
{
if ([[[self.annotationsStore objectForKey:annotation] objectForKey:MGLAnnotationIDKey] integerValue] == newSelectedAnnotationID)
if ([[[self.annotationIDsByAnnotation objectForKey:annotation] objectForKey:MGLAnnotationIDKey] integerValue] == newSelectedAnnotationID)
{
// only change selection status if not the currently selected annotation
if ( ! [annotation isEqual:self.selectedAnnotation])
@@ -874,8 +879,6 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
// deselect any selected annotation
if (self.selectedAnnotation) [self deselectAnnotation:self.selectedAnnotation animated:YES];
}
NSLog(@"%i (%@)", newSelectedAnnotationID, self.selectedAnnotation.title);
}
- (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap
@@ -962,7 +965,16 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
{
[self unrotateIfNeededAnimated:YES];
[self notifyMapChange:@(mbgl::MapChangeRegionDidChangeAnimated)];
[self notifyMapChange:@(mbgl::MapChangeRegionDidChange)];
}
}
- (void)handleCalloutAccessoryTapGesture:(UITapGestureRecognizer *)tap
{
if ([self.delegate respondsToSelector:@selector(mapView:annotation:calloutAccessoryControlTapped:)])
{
[self.delegate mapView:self annotation:self.selectedAnnotation
calloutAccessoryControlTapped:(UIControl *)tap.view];
}
}
@@ -1029,6 +1041,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
{
if (finished)
{
[self notifyMapChange:@(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
[UIView animateWithDuration:MGLAnimationDuration
animations:^
{
@@ -1041,6 +1055,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
- (void)resetPosition
{
mbglMap->resetPosition();
[self notifyMapChange:@(mbgl::MapChangeRegionDidChange)];
}
- (void)toggleDebug
@@ -1055,6 +1071,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
CGFloat duration = (animated ? MGLAnimationDuration : 0);
mbglMap->setLatLng(coordinateToLatLng(coordinate), secondsAsDuration(duration));
[self notifyMapChange:@(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
}
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
@@ -1074,6 +1092,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
mbglMap->setLatLngZoom(coordinateToLatLng(centerCoordinate), zoomLevel, secondsAsDuration(duration));
[self unrotateIfNeededAnimated:animated];
[self notifyMapChange:@(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
}
- (double)zoomLevel
@@ -1088,6 +1108,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
mbglMap->setZoom(zoomLevel, secondsAsDuration(duration));
[self unrotateIfNeededAnimated:animated];
[self notifyMapChange:@(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
}
- (void)setZoomLevel:(double)zoomLevel
@@ -1112,6 +1134,8 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
CGFloat duration = (animated ? MGLAnimationDuration : 0);
mbglMap->setBearing(direction * -1, secondsAsDuration(duration));
[self notifyMapChange:@(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
}
- (void)setDirection:(CLLocationDirection)direction
@@ -1635,11 +1659,11 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng)
- (NSArray *)annotations
{
if ([_annotationsStore count])
if ([_annotationIDsByAnnotation count])
{
NSMutableArray *result = [NSMutableArray array];
NSEnumerator *keyEnumerator = [_annotationsStore keyEnumerator];
NSEnumerator *keyEnumerator = [_annotationIDsByAnnotation keyEnumerator];
id <MGLAnnotation> annotation;
while (annotation = [keyEnumerator nextObject])
@@ -1695,7 +1719,7 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng)
for (size_t i = 0; i < annotationIDs.size(); ++i)
{
[self.annotationsStore setObject:@{ MGLAnnotationIDKey : @(annotationIDs[i]) }
[self.annotationIDsByAnnotation setObject:@{ MGLAnnotationIDKey : @(annotationIDs[i]) }
forKey:annotations[i]];
}
}
@@ -1722,8 +1746,13 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng)
{
assert([annotation conformsToProtocol:@protocol(MGLAnnotation)]);
annotationIDsToRemove.push_back([[[self.annotationsStore objectForKey:annotation] objectForKey:MGLAnnotationIDKey] unsignedIntValue]);
[self.annotationsStore removeObjectForKey:annotation];
annotationIDsToRemove.push_back([[[self.annotationIDsByAnnotation objectForKey:annotation] objectForKey:MGLAnnotationIDKey] unsignedIntValue]);
[self.annotationIDsByAnnotation removeObjectForKey:annotation];
if (annotation == self.selectedAnnotation)
{
[self deselectAnnotation:annotation animated:NO];
}
}
mbglMap->removeAnnotations(annotationIDsToRemove);
@@ -1744,27 +1773,112 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng)
if ( ! [self viewportBounds].contains(coordinateToLatLng(firstAnnotation.coordinate))) return;
self.selectedAnnotation = firstAnnotation;
[self selectAnnotation:firstAnnotation animated:NO];
}
- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated
{
(void)animated;
if ( ! annotation) return;
if ( ! [self viewportBounds].contains(coordinateToLatLng(annotation.coordinate))) return;
if (annotation == self.selectedAnnotation) return;
[self deselectAnnotation:self.selectedAnnotation animated:NO];
self.selectedAnnotation = annotation;
if (annotation.title && [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] &&
[self.delegate mapView:self annotationCanShowCallout:annotation])
{
// build the callout
self.selectedAnnotationCalloutView = [self calloutViewForAnnotation:annotation];
// determine symbol in use for point
NSString *symbol = MGLDefaultStyleMarkerSymbolName;
if ([self.delegate respondsToSelector:@selector(mapView:symbolNameForAnnotation:)])
{
symbol = [self.delegate mapView:self symbolNameForAnnotation:annotation];
}
std::string symbolName([symbol cStringUsingEncoding:[NSString defaultCStringEncoding]]);
// determine anchor point based on symbol
CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self];
double y = mbglMap->getTopOffsetPixelsForAnnotationSymbol(symbolName);
CGRect calloutBounds = CGRectMake(calloutAnchorPoint.x, calloutAnchorPoint.y + y, 0, 0);
// consult delegate for left and/or right accessory views
if ([self.delegate respondsToSelector:@selector(mapView:leftCalloutAccessoryViewForAnnotation:)])
{
self.selectedAnnotationCalloutView.leftAccessoryView =
[self.delegate mapView:self leftCalloutAccessoryViewForAnnotation:annotation];
if ([self.selectedAnnotationCalloutView.leftAccessoryView isKindOfClass:[UIControl class]])
{
UITapGestureRecognizer *calloutAccessoryTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleCalloutAccessoryTapGesture:)];
[self.selectedAnnotationCalloutView.leftAccessoryView addGestureRecognizer:calloutAccessoryTap];
}
}
if ([self.delegate respondsToSelector:@selector(mapView:rightCalloutAccessoryViewForAnnotation:)])
{
self.selectedAnnotationCalloutView.rightAccessoryView =
[self.delegate mapView:self rightCalloutAccessoryViewForAnnotation:annotation];
if ([self.selectedAnnotationCalloutView.rightAccessoryView isKindOfClass:[UIControl class]])
{
UITapGestureRecognizer *calloutAccessoryTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleCalloutAccessoryTapGesture:)];
[self.selectedAnnotationCalloutView.rightAccessoryView addGestureRecognizer:calloutAccessoryTap];
}
}
// present popup
[self.selectedAnnotationCalloutView presentCalloutFromRect:calloutBounds
inView:self.glView
constrainedToView:self.glView
animated:animated];
}
// notify delegate
if ([self.delegate respondsToSelector:@selector(mapView:didSelectAnnotation:)])
{
[self.delegate mapView:self didSelectAnnotation:annotation];
}
}
- (SMCalloutView *)calloutViewForAnnotation:(id <MGLAnnotation>)annotation
{
SMCalloutView *calloutView = [SMCalloutView platformCalloutView];
if ([annotation respondsToSelector:@selector(title)]) calloutView.title = annotation.title;
if ([annotation respondsToSelector:@selector(subtitle)]) calloutView.subtitle = annotation.subtitle;
return calloutView;
}
- (void)deselectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated
{
(void)animated;
if ( ! annotation) return;
if ([self.selectedAnnotation isEqual:annotation]) self.selectedAnnotation = nil;
if ([self.selectedAnnotation isEqual:annotation])
{
// dismiss popup
[self.selectedAnnotationCalloutView dismissCalloutAnimated:animated];
// clean up
self.selectedAnnotationCalloutView = nil;
self.selectedAnnotation = nil;
}
// notify delegate
if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)])
{
[self.delegate mapView:self didDeselectAnnotation:annotation];
}
}
#pragma mark - Utility -
@@ -1844,6 +1958,8 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng)
case mbgl::MapChangeRegionWillChange:
case mbgl::MapChangeRegionWillChangeAnimated:
{
[self deselectAnnotation:self.selectedAnnotation animated:NO];
BOOL animated = ([change unsignedIntegerValue] == mbgl::MapChangeRegionWillChangeAnimated);
@synchronized (self.regionChangeDelegateQueue)
@@ -1874,6 +1990,13 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng)
}
break;
}
case mbgl::MapChangeRegionIsChanging:
{
if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)])
{
[self.delegate mapViewRegionIsChanging:self];
}
}
case mbgl::MapChangeRegionDidChange:
case mbgl::MapChangeRegionDidChangeAnimated:
{
@@ -1993,8 +2116,11 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng)
- (void)invalidate
{
// This is run in the main/UI thread.
assert([[NSThread currentThread] isMainThread]);
[self.glView setNeedsDisplay];
[self notifyMapChange:@(mbgl::MapChangeRegionIsChanging)];
}
class MBGLView : public mbgl::View
@@ -2022,12 +2148,9 @@ class MBGLView : public mbgl::View
}
else
{
dispatch_async(dispatch_get_main_queue(), ^
{
[nativeView performSelector:@selector(notifyMapChange:)
withObject:@(change)
afterDelay:0];
});
assert([[NSThread currentThread] isMainThread]);
[nativeView notifyMapChange:@(change)];
}
}

View File

@@ -545,6 +545,12 @@ void Map::setDefaultPointAnnotationSymbol(const std::string& symbol) {
annotationManager->setDefaultPointAnnotationSymbol(symbol);
}
double Map::getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol) {
SpritePosition pos = sprite->getSpritePosition(symbol);
return -pos.height / pos.pixelRatio / 2;
}
uint32_t Map::addPointAnnotation(const LatLng& point, const std::string& symbol) {
assert(Environment::currentlyOn(ThreadType::Main));
std::vector<LatLng> points({ point });