summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Wray <jason@kulturny.com>2015-08-18 16:25:40 -0400
committerJason Wray <jason@kulturny.com>2015-08-26 11:23:03 -0700
commit42dc5336db5179c50b8d1edeca0bef7a27c8cefd (patch)
tree601c4117661e9aaed3ffd17660946476cf7eb238
parentd8ed33c7d48dbabe7e6f5e48f148ea2bec823a07 (diff)
downloadqtlocation-mapboxgl-42dc5336db5179c50b8d1edeca0bef7a27c8cefd.tar.gz
iOS perspective gesture support
Drag two fingers upward to tilt the map. Implements #2116
-rw-r--r--include/mbgl/ios/MGLMapView.h21
-rw-r--r--include/mbgl/platform/darwin/settings_nsuserdefaults.hpp1
-rw-r--r--ios/app/MBXViewController.mm2
-rw-r--r--platform/darwin/settings_nsuserdefaults.mm3
-rw-r--r--platform/ios/MGLMapView.mm111
-rw-r--r--platform/ios/MGLMapboxEvents.h1
-rw-r--r--platform/ios/MGLMapboxEvents.m1
7 files changed, 136 insertions, 4 deletions
diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h
index 2b9a3d2913..fc70f3d904 100644
--- a/include/mbgl/ios/MGLMapView.h
+++ b/include/mbgl/ios/MGLMapView.h
@@ -74,6 +74,13 @@ IB_DESIGNABLE
* The default value of this property is `YES`. */
@property(nonatomic, getter=isRotateEnabled) BOOL rotateEnabled;
+/** A Boolean value that determines whether the user may change the pitch (tilt) of the map.
+ *
+ * This property controls only user interactions with the map. If you set the value of this property to `NO`, you may still change the pitch of the map programmatically.
+ *
+ * The default value of this property is `YES`. */
+@property(nonatomic, getter=isPitchEnabled) BOOL pitchEnabled;
+
/** The compass image view shown in the upper-right when the map is rotated. */
@property (nonatomic, readonly) UIImageView *compassView;
@@ -173,6 +180,20 @@ IB_DESIGNABLE
/** Resets the map rotation to a northern heading. */
- (IBAction)resetNorth;
+/** The pitch of the map (measured in degrees).
+ *
+ * The default value `0` shows a completely flat map. Maximum value is `60`. */
+@property (nonatomic) double pitch;
+
+/** Changes the pitch of the map.
+ * @param pitch The pitch of the map (measured in degrees) relative to top-down.
+ *
+ * Changing the pitch tilts the map without changing the current center coordinate or zoom level. */
+- (void)setPitch:(double)pitch;
+
+/** Resets the map pitch to head-on. */
+- (IBAction)resetPitch;
+
#pragma mark - Converting Map Coordinates
/** @name Converting Map Coordinates */
diff --git a/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp b/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp
index 6c91fd3029..615320ee55 100644
--- a/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp
+++ b/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp
@@ -17,6 +17,7 @@ public:
double latitude = 0;
double zoom = 0;
double bearing = 0;
+ double pitch = 0;
MGLUserTrackingMode userTrackingMode = MGLUserTrackingModeNone;
bool showsUserLocation = false;
diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm
index f7cbf0c042..99c207d24d 100644
--- a/ios/app/MBXViewController.mm
+++ b/ios/app/MBXViewController.mm
@@ -86,6 +86,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
settings->latitude = self.mapView.centerCoordinate.latitude;
settings->zoom = self.mapView.zoomLevel;
settings->bearing = self.mapView.direction;
+ settings->pitch = self.mapView.pitch;
settings->debug = self.mapView.isDebugActive;
settings->userTrackingMode = self.mapView.userTrackingMode;
settings->showsUserLocation = self.mapView.showsUserLocation;
@@ -99,6 +100,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
settings->load();
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(settings->latitude, settings->longitude) zoomLevel:settings->zoom animated:NO];
self.mapView.direction = settings->bearing;
+ self.mapView.pitch = settings->pitch;
self.mapView.userTrackingMode = settings->userTrackingMode;
self.mapView.showsUserLocation = settings->showsUserLocation;
[self.mapView setDebugActive:settings->debug];
diff --git a/platform/darwin/settings_nsuserdefaults.mm b/platform/darwin/settings_nsuserdefaults.mm
index f6f9bbeedb..5c943baff9 100644
--- a/platform/darwin/settings_nsuserdefaults.mm
+++ b/platform/darwin/settings_nsuserdefaults.mm
@@ -11,6 +11,7 @@ Settings_NSUserDefaults::Settings_NSUserDefaults()
@"latitude" : @(latitude),
@"zoom" : @(zoom),
@"bearing" : @(bearing),
+ @"pitch" : @(pitch),
@"userTrackingMode" : @(userTrackingMode),
@"showsUserLocation" : @(showsUserLocation),
@"debug" : @(debug),
@@ -26,6 +27,7 @@ void Settings_NSUserDefaults::load()
latitude = [settings[@"latitude"] doubleValue];
zoom = [settings[@"zoom"] doubleValue];
bearing = [settings[@"bearing"] doubleValue];
+ pitch = [settings[@"pitch"] doubleValue];
debug = [settings[@"debug"] boolValue];
unsigned uncheckedTrackingMode = [settings[@"trackingMode"] unsignedIntValue];
@@ -44,6 +46,7 @@ void Settings_NSUserDefaults::save()
@"latitude" : @(latitude),
@"zoom" : @(zoom),
@"bearing" : @(bearing),
+ @"pitch" : @(pitch),
@"userTrackingMode" : @(userTrackingMode),
@"showsUserLocation" : @(showsUserLocation),
@"debug" : @(debug),
diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm
index f12836b057..4f3e903074 100644
--- a/platform/ios/MGLMapView.mm
+++ b/platform/ios/MGLMapView.mm
@@ -85,6 +85,7 @@ CLLocationDegrees MGLDegreesFromRadians(CGFloat radians)
@property (nonatomic) UIPinchGestureRecognizer *pinch;
@property (nonatomic) UIRotationGestureRecognizer *rotate;
@property (nonatomic) UILongPressGestureRecognizer *quickZoom;
+@property (nonatomic) UIPanGestureRecognizer *twoFingerDrag;
@property (nonatomic) NSMapTable *annotationIDsByAnnotation;
@property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImages;
@property (nonatomic) std::vector<uint32_t> annotationsNearbyLastTap;
@@ -338,6 +339,15 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
[twoFingerTap requireGestureRecognizerToFail:_pinch];
[twoFingerTap requireGestureRecognizerToFail:_rotate];
[self addGestureRecognizer:twoFingerTap];
+
+ _twoFingerDrag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerDragGesture:)];
+ _twoFingerDrag.minimumNumberOfTouches = 2;
+ _twoFingerDrag.maximumNumberOfTouches = 2;
+ _twoFingerDrag.delegate = self;
+ [_twoFingerDrag requireGestureRecognizerToFail:twoFingerTap];
+ [_twoFingerDrag requireGestureRecognizerToFail:_pan];
+ [self addGestureRecognizer:_twoFingerDrag];
+ _pitchEnabled = YES;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
@@ -1326,6 +1336,59 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
}
}
+- (void)handleTwoFingerDragGesture:(UIPanGestureRecognizer *)twoFingerDrag
+{
+ if ( ! self.isPitchEnabled) return;
+
+ _mbglMap->cancelTransitions();
+
+ if (twoFingerDrag.state == UIGestureRecognizerStateBegan)
+ {
+ [self trackGestureEvent:MGLEventGesturePitchStart forRecognizer:twoFingerDrag];
+ }
+ else if (twoFingerDrag.state == UIGestureRecognizerStateBegan || twoFingerDrag.state == UIGestureRecognizerStateChanged)
+ {
+ CGFloat gestureDistance = CGPoint([twoFingerDrag translationInView:twoFingerDrag.view]).y;
+ double currentPitch = _mbglMap->getPitch();
+ double minPitch = 0;
+ double maxPitch = 60.0;
+ double slowdown = 20.0;
+
+ double pitchNew = fmax(fmin(currentPitch - (gestureDistance / slowdown), maxPitch), minPitch);
+
+ _mbglMap->setPitch(pitchNew);
+ }
+ else if (twoFingerDrag.state == UIGestureRecognizerStateEnded || twoFingerDrag.state == UIGestureRecognizerStateCancelled)
+ {
+ [self unrotateIfNeededAnimated:YES];
+
+ //[self notifyMapChange:(mbgl::MapChangeRegionDidChange)];
+ }
+
+}
+
+- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
+{
+ if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
+ {
+ UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer;
+
+ if (panGesture.minimumNumberOfTouches == 2)
+ {
+ CGPoint velocity = [panGesture velocityInView:panGesture.view];
+ double gestureAngle = MGLDegreesFromRadians(atan(velocity.y / velocity.x));
+ double horizontalToleranceDegrees = 20.0;
+
+ // cancel if gesture angle is not 90º±20º (more or less vertical)
+ if ( ! (fabs((fabs(gestureAngle) - 90.0)) < horizontalToleranceDegrees))
+ {
+ return NO;
+ }
+ }
+ }
+ return YES;
+}
+
- (void)handleCalloutAccessoryTapGesture:(UITapGestureRecognizer *)tap
{
if ([self.delegate respondsToSelector:@selector(mapView:annotation:calloutAccessoryControlTapped:)])
@@ -1419,6 +1482,11 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
return [NSSet setWithObject:@"allowsRotating"];
}
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingPitchEnabled
+{
+ return [NSSet setWithObject:@"allowsPitching"];
+}
+
- (void)setDebugActive:(BOOL)debugActive
{
_mbglMap->setDebug(debugActive);
@@ -1617,6 +1685,25 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord
[self setDirection:direction animated:NO];
}
+- (double)pitch
+{
+ return _mbglMap->getPitch();
+}
+
+- (void)setPitch:(double)pitch
+{
+ // constrain pitch to between 0º and 60º
+ //
+ _mbglMap->setPitch(fmax(fmin(pitch, 60), 0));
+
+ //[self notifyMapChange:(mbgl::MapChangeRegionDidChange)];
+}
+
+- (void)resetPitch
+{
+ [self setPitch:0];
+}
+
- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view
{
CGPoint convertedPoint = [self convertPoint:point fromView:view];
@@ -2630,10 +2717,11 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
{
[self updateCompass];
- if (self.pan.state == UIGestureRecognizerStateChanged ||
- self.pinch.state == UIGestureRecognizerStateChanged ||
- self.rotate.state == UIGestureRecognizerStateChanged ||
- self.quickZoom.state == UIGestureRecognizerStateChanged) return;
+ if (self.pan.state == UIGestureRecognizerStateChanged ||
+ self.pinch.state == UIGestureRecognizerStateChanged ||
+ self.rotate.state == UIGestureRecognizerStateChanged ||
+ self.quickZoom.state == UIGestureRecognizerStateChanged ||
+ self.twoFingerDrag.state == UIGestureRecognizerStateChanged) return;
if (self.isAnimatingGesture) return;
@@ -3062,6 +3150,21 @@ class MBGLView : public mbgl::View
self.rotateEnabled = allowsRotating;
}
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingAllowsPitching
+{
+ return [NSSet setWithObject:@"pitchEnabled"];
+}
+
+- (BOOL)allowsPitching
+{
+ return self.pitchEnabled;
+}
+
+- (void)setAllowsPitching:(BOOL)allowsPitching
+{
+ self.pitchEnabled = allowsPitching;
+}
+
- (void)didReceiveMemoryWarning
{
_mbglMap->onLowMemory();
diff --git a/platform/ios/MGLMapboxEvents.h b/platform/ios/MGLMapboxEvents.h
index 9efb53c5ea..45f8094954 100644
--- a/platform/ios/MGLMapboxEvents.h
+++ b/platform/ios/MGLMapboxEvents.h
@@ -30,6 +30,7 @@ extern NSString *const MGLEventGestureQuickZoom;
extern NSString *const MGLEventGesturePanStart;
extern NSString *const MGLEventGesturePinchStart;
extern NSString *const MGLEventGestureRotateStart;
+extern NSString *const MGLEventGesturePitchStart;
typedef NS_DICTIONARY_OF(NSString *, id) MGLMapboxEventAttributes;
typedef NS_MUTABLE_DICTIONARY_OF(NSString *, id) MGLMutableMapboxEventAttributes;
diff --git a/platform/ios/MGLMapboxEvents.m b/platform/ios/MGLMapboxEvents.m
index 314d50dd01..77e68ddde0 100644
--- a/platform/ios/MGLMapboxEvents.m
+++ b/platform/ios/MGLMapboxEvents.m
@@ -45,6 +45,7 @@ NSString *const MGLEventGestureQuickZoom = @"QuickZoom";
NSString *const MGLEventGesturePanStart = @"Pan";
NSString *const MGLEventGesturePinchStart = @"Pinch";
NSString *const MGLEventGestureRotateStart = @"Rotation";
+NSString *const MGLEventGesturePitchStart = @"Pitch";
const NSUInteger MGLMaximumEventsPerFlush = 20;
const NSTimeInterval MGLFlushInterval = 60;