diff options
author | Justin R. Miller <incanus@codesorcery.net> | 2015-03-24 18:43:24 -0700 |
---|---|---|
committer | Justin R. Miller <incanus@codesorcery.net> | 2015-03-24 18:43:24 -0700 |
commit | 10a30daed66ac1795ee9a0097e8aeb2e735e8968 (patch) | |
tree | 3c1a050fce8b282cab9eb771d0aa58c2e101387d /platform/ios/MGLMapView.mm | |
parent | 3490622dd25cda45ec4d6804de397417e4e8f672 (diff) | |
download | qtlocation-mapboxgl-10a30daed66ac1795ee9a0097e8aeb2e735e8968.tar.gz |
refs #894, fixes #1074: callout views for iOS
Diffstat (limited to 'platform/ios/MGLMapView.mm')
-rw-r--r-- | platform/ios/MGLMapView.mm | 193 |
1 files changed, 158 insertions, 35 deletions
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)]; } } |