summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin R. Miller <incanus@codesorcery.net>2015-03-24 18:43:24 -0700
committerJustin R. Miller <incanus@codesorcery.net>2015-03-24 18:43:24 -0700
commit10a30daed66ac1795ee9a0097e8aeb2e735e8968 (patch)
tree3c1a050fce8b282cab9eb771d0aa58c2e101387d
parent3490622dd25cda45ec4d6804de397417e4e8f672 (diff)
downloadqtlocation-mapboxgl-10a30daed66ac1795ee9a0097e8aeb2e735e8968.tar.gz
refs #894, fixes #1074: callout views for iOS
-rw-r--r--.gitmodules4
-rw-r--r--Makefile13
-rw-r--r--gyp/platform-ios.gypi3
-rw-r--r--include/mbgl/ios/MGLMapView.h79
-rw-r--r--include/mbgl/map/map.hpp1
-rw-r--r--include/mbgl/map/view.hpp17
-rw-r--r--ios/app/MBXViewController.mm11
-rw-r--r--platform/ios/MGLMapView.mm193
m---------platform/ios/vendor/SMCalloutView0
-rw-r--r--src/mbgl/map/map.cpp6
10 files changed, 278 insertions, 49 deletions
diff --git a/.gitmodules b/.gitmodules
index 19de5b6f78..9e5265a6a1 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -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
diff --git a/Makefile b/Makefile
index 33135ae96e..4c830fd83f 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi
index 73f611a8e7..de8745bf25 100644
--- a/gyp/platform-ios.gypi
+++ b/gyp/platform-ios.gypi
@@ -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',
],
diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h
index 3a6e622e12..0fbd498fc3 100644
--- a/include/mbgl/ios/MGLMapView.h
+++ b/include/mbgl/ios/MGLMapView.h
@@ -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
diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp
index 0fb2065cca..cb93916284 100644
--- a/include/mbgl/map/map.hpp
+++ b/include/mbgl/map/map.hpp
@@ -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);
diff --git a/include/mbgl/map/view.hpp b/include/mbgl/map/view.hpp
index 1ee9d300c5..bcfeb62cfc 100644
--- a/include/mbgl/map/view.hpp
+++ b/include/mbgl/map/view.hpp
@@ -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 {
diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm
index 6d1175e4f1..2c7bfd6b40 100644
--- a/ios/app/MBXViewController.mm
+++ b/ios/app/MBXViewController.mm
@@ -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
diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm
index ce5997e0a9..bd0726d515 100644
--- a/platform/ios/MGLMapView.mm
+++ b/platform/ios/MGLMapView.mm
@@ -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];
+ }
}
-- (void)deselectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated
+- (SMCalloutView *)calloutViewForAnnotation:(id <MGLAnnotation>)annotation
{
- (void)animated;
+ 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
+{
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)];
}
}
diff --git a/platform/ios/vendor/SMCalloutView b/platform/ios/vendor/SMCalloutView
new file mode 160000
+Subproject da691eceee57cdecce0235d2946552e105d8b7c
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
index 24f72e8dca..aa56ea1e71 100644
--- a/src/mbgl/map/map.cpp
+++ b/src/mbgl/map/map.cpp
@@ -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 });