summaryrefslogtreecommitdiff
path: root/platform/ios/src
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/src')
-rw-r--r--platform/ios/src/MGLMapView.h30
-rw-r--r--platform/ios/src/MGLMapView.mm55
-rw-r--r--platform/ios/src/UIDevice+MGLAdditions.h7
-rw-r--r--platform/ios/src/UIDevice+MGLAdditions.m51
4 files changed, 140 insertions, 3 deletions
diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h
index bfd0946cdc..e1520401a7 100644
--- a/platform/ios/src/MGLMapView.h
+++ b/platform/ios/src/MGLMapView.h
@@ -79,6 +79,21 @@ typedef NS_ENUM(NSUInteger, MGLUserTrackingMode) {
MGLUserTrackingModeFollowWithCourse,
};
+/** Options for `MGLMapView.preferredFramesPerSecond`. */
+typedef NSInteger MGLMapViewPreferredFramesPerSecond NS_TYPED_EXTENSIBLE_ENUM;
+
+/**
+ The default frame rate. This can be either 30 FPS or 60 FPS, depending on
+ device capabilities.
+ */
+FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondDefault;
+
+/** A conservative frame rate; typically 30 FPS. */
+FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondLowPower;
+
+/** The maximum supported frame rate; typically 60 FPS. */
+FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondMaximum;
+
/**
An interactive, customizable map view with an interface similar to the one
provided by Apple’s MapKit.
@@ -286,6 +301,21 @@ MGL_EXPORT IB_DESIGNABLE
*/
- (IBAction)showAttribution:(id)sender;
+/**
+ The preferred frame rate at which the map view is rendered.
+
+ The default value for this property is
+ `MGLMapViewPreferredFramesPerSecondDefault`, which will adaptively set the
+ preferred frame rate based on the capability of the user’s device to maintain
+ a smooth experience.
+
+ In addition to the provided `MGLMapViewPreferredFramesPerSecond` options, this
+ property can be set to arbitrary integer values.
+
+ @see `CADisplayLink.preferredFramesPerSecond`
+ */
+@property (nonatomic, assign) MGLMapViewPreferredFramesPerSecond preferredFramesPerSecond;
+
@property (nonatomic) NSArray<NSString *> *styleClasses __attribute__((unavailable("Support for style classes has been removed.")));
- (BOOL)hasStyleClass:(NSString *)styleClass __attribute__((unavailable("Support for style classes has been removed.")));
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index cdacfb462b..918506067c 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -53,6 +53,7 @@
#import "NSProcessInfo+MGLAdditions.h"
#import "NSString+MGLAdditions.h"
#import "NSURL+MGLAdditions.h"
+#import "UIDevice+MGLAdditions.h"
#import "UIImage+MGLAdditions.h"
#import "UIViewController+MGLAdditions.h"
@@ -87,6 +88,10 @@ const CGFloat MGLMapViewDecelerationRateNormal = UIScrollViewDecelerationRateNor
const CGFloat MGLMapViewDecelerationRateFast = UIScrollViewDecelerationRateFast;
const CGFloat MGLMapViewDecelerationRateImmediate = 0.0;
+const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondDefault = -1;
+const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondLowPower = 30;
+const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondMaximum = 60;
+
/// Indicates the manner in which the map view is tracking the user location.
typedef NS_ENUM(NSUInteger, MGLUserTrackingState) {
/// The map view is not yet tracking the user location.
@@ -118,8 +123,6 @@ const double MGLMinimumZoomLevelForUserTracking = 10.5;
/// Initial zoom level when entering user tracking mode from a low zoom level.
const double MGLDefaultZoomLevelForUserTracking = 14.0;
-const NSUInteger MGLTargetFrameInterval = 1; // Target FPS will be 60 divided by this value
-
/// Tolerance for snapping to true north, measured in degrees in either direction.
const CLLocationDirection MGLToleranceForSnappingToNorth = 7;
@@ -403,6 +406,9 @@ public:
self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = YES;
if (@available(iOS 11.0, *)) { self.accessibilityIgnoresInvertColors = YES; }
+
+ self.preferredFramesPerSecond = MGLMapViewPreferredFramesPerSecondDefault;
+
// setup mbgl view
_mbglView = new MBGLView(self);
@@ -1125,7 +1131,7 @@ public:
}
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateFromDisplayLink)];
- _displayLink.frameInterval = MGLTargetFrameInterval;
+ [self updateDisplayLinkPreferredFramesPerSecond];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
_needsDisplayRefresh = YES;
[self updateFromDisplayLink];
@@ -1137,6 +1143,49 @@ public:
}
}
+- (void)updateDisplayLinkPreferredFramesPerSecond
+{
+ if (!_displayLink)
+ {
+ return;
+ }
+
+ NSInteger newFrameRate;
+ if (_preferredFramesPerSecond == MGLMapViewPreferredFramesPerSecondDefault)
+ {
+ // On legacy devices that cannot maintain a reasonable frame rate, set
+ // a lower limit to avoid jank.
+ newFrameRate = UIDevice.currentDevice.mgl_isLegacyDevice ? MGLMapViewPreferredFramesPerSecondLowPower : MGLMapViewPreferredFramesPerSecondMaximum;
+ }
+ else
+ {
+ newFrameRate = _preferredFramesPerSecond;
+ }
+
+ if (@available(iOS 10.0, *))
+ {
+ _displayLink.preferredFramesPerSecond = newFrameRate;
+ }
+ else
+ {
+ // CADisplayLink.frameInterval does not support more than 60 FPS (and
+ // no device that supports >60 FPS ever supported iOS 9).
+ NSInteger maximumFrameRate = 60;
+ _displayLink.frameInterval = maximumFrameRate / MIN(newFrameRate, maximumFrameRate);
+ }
+}
+
+- (void)setPreferredFramesPerSecond:(MGLMapViewPreferredFramesPerSecond)preferredFramesPerSecond
+{
+ if (_preferredFramesPerSecond == preferredFramesPerSecond)
+ {
+ return;
+ }
+
+ _preferredFramesPerSecond = preferredFramesPerSecond;
+ [self updateDisplayLinkPreferredFramesPerSecond];
+}
+
- (void)didMoveToWindow
{
[self validateDisplayLink];
diff --git a/platform/ios/src/UIDevice+MGLAdditions.h b/platform/ios/src/UIDevice+MGLAdditions.h
new file mode 100644
index 0000000000..a61aedf2db
--- /dev/null
+++ b/platform/ios/src/UIDevice+MGLAdditions.h
@@ -0,0 +1,7 @@
+#import <UIKit/UIKit.h>
+
+@interface UIDevice (MGLAdditions)
+
+@property (nonatomic, readonly) BOOL mgl_isLegacyDevice;
+
+@end
diff --git a/platform/ios/src/UIDevice+MGLAdditions.m b/platform/ios/src/UIDevice+MGLAdditions.m
new file mode 100644
index 0000000000..e9da77adda
--- /dev/null
+++ b/platform/ios/src/UIDevice+MGLAdditions.m
@@ -0,0 +1,51 @@
+#import "UIDevice+MGLAdditions.h"
+#include <sys/sysctl.h>
+
+@implementation UIDevice (MGLAdditions)
+
+- (NSString *)modelString {
+ char *typeSpecifier = "hw.machine";
+
+ size_t size;
+ sysctlbyname(typeSpecifier, NULL, &size, NULL, 0);
+
+ char *answer = malloc(size);
+ sysctlbyname(typeSpecifier, answer, &size, NULL, 0);
+
+ NSString *results = [NSString stringWithCString:answer encoding:NSUTF8StringEncoding];
+
+ free(answer);
+ return results;
+}
+
+- (BOOL)mgl_isLegacyDevice {
+ // This is a list of supported devices that cannot maintain a reasonable frame
+ // rate under typical load. For brevity, unsupported devices are not included.
+ NSSet *blacklist = [NSSet setWithObjects:
+ @"iPhone4", // iPhone 4s
+ @"iPhone5", // iPhone 5, 5c
+ @"iPhone6", // iPhone 5s
+
+ @"iPad2", // iPad 2, Mini
+ @"iPad3", // iPad 3
+ @"iPad4", // iPad Air, Mini 2, Mini 3
+
+ @"iPod5", // iPod Touch 5
+
+ nil
+ ];
+
+ NSString *model = [self modelString];
+
+ for (NSString *blacklistedModel in blacklist) {
+ if ([model hasPrefix:[blacklistedModel stringByAppendingString:@","]]) {
+ return YES;
+ }
+ }
+
+ // TODO: Also handle simulator using something like `ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"]`.
+
+ return NO;
+}
+
+@end