summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Wray <friedbunny@users.noreply.github.com>2018-07-31 15:20:05 -0400
committerGitHub <noreply@github.com>2018-07-31 15:20:05 -0400
commit885f6e3c02138398d094e49243817a83349b4d50 (patch)
tree5e29d15219589dc0d58d2a8d6d590f1ca5938096
parentc77aa79f51702179032b681fef2df31399fcec4f (diff)
downloadqtlocation-mapboxgl-885f6e3c02138398d094e49243817a83349b4d50.tar.gz
[ios] Add preferred FPS setting; vary maximum FPS by device capability
- Add `MGLMapView.preferredFramesPerSecond`, which can be set with the provided `MGLMapViewPreferredFramesPerSecond` enum values or directly with an integer. - Adaptively set the preferred FPS based on the capabilities of the device: the oldest and least powerful devices are now capped at 30 FPS, which results in a more consistent/smoother experience.
-rw-r--r--platform/ios/CHANGELOG.md4
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj12
-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
6 files changed, 156 insertions, 3 deletions
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index d7ac9500d1..1e606702c3 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -4,6 +4,10 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
## 4.3.0
+### Styles and rendering
+
+* Added an `MGLMapView.preferredFramesPerSecond` property that controls the rate at which the map view is rendered. The default rate now adapts to device capabilities to provide a smoother experience. ([#12501](https://github.com/mapbox/mapbox-gl-native/issues/12501))
+
### Other changes
* Fixed a crash that occurred when the user started a gesture before the drift animation for a previous gesture was complete. ([#12148](https://github.com/mapbox/mapbox-gl-native/pull/12148))
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index 2a9c9b5761..6b883a3def 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -322,6 +322,10 @@
966FCF531F3C322400F2B6DE /* MGLUserLocationHeadingArrowLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 966FCF501F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.h */; };
966FCF541F3C323300F2B6DE /* MGLUserLocationHeadingArrowLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 966FCF511F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.m */; };
966FCF551F3C323500F2B6DE /* MGLUserLocationHeadingArrowLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 966FCF511F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.m */; };
+ 967C864B210A9D3C004DF794 /* UIDevice+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 967C8649210A9D3C004DF794 /* UIDevice+MGLAdditions.h */; };
+ 967C864C210A9D3C004DF794 /* UIDevice+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 967C8649210A9D3C004DF794 /* UIDevice+MGLAdditions.h */; };
+ 967C864D210A9D3C004DF794 /* UIDevice+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 967C864A210A9D3C004DF794 /* UIDevice+MGLAdditions.m */; };
+ 967C864E210A9D3C004DF794 /* UIDevice+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 967C864A210A9D3C004DF794 /* UIDevice+MGLAdditions.m */; };
968F36B51E4D128D003A5522 /* MGLDistanceFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3557F7AE1E1D27D300CCA5E6 /* MGLDistanceFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; };
96E027231E57C76E004B8E66 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E027251E57C76E004B8E66 /* Localizable.strings */; };
96E516DC2000547000A02306 /* MGLPolyline_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9654C1251FFC1AB900DB6A19 /* MGLPolyline_Private.h */; };
@@ -992,6 +996,8 @@
966FCF4B1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLUserLocationHeadingBeamLayer.m; sourceTree = "<group>"; };
966FCF501F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingArrowLayer.h; sourceTree = "<group>"; };
966FCF511F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLUserLocationHeadingArrowLayer.m; sourceTree = "<group>"; };
+ 967C8649210A9D3C004DF794 /* UIDevice+MGLAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIDevice+MGLAdditions.h"; sourceTree = "<group>"; };
+ 967C864A210A9D3C004DF794 /* UIDevice+MGLAdditions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIDevice+MGLAdditions.m"; sourceTree = "<group>"; };
968F36B41E4D0FC6003A5522 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
96E027241E57C76E004B8E66 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
96E027271E57C77A004B8E66 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -1526,6 +1532,8 @@
96036A00200565C700510F3D /* NSOrthography+MGLAdditions.m */,
35CE61801D4165D9004F2359 /* UIColor+MGLAdditions.h */,
35CE61811D4165D9004F2359 /* UIColor+MGLAdditions.mm */,
+ 967C8649210A9D3C004DF794 /* UIDevice+MGLAdditions.h */,
+ 967C864A210A9D3C004DF794 /* UIDevice+MGLAdditions.m */,
30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */,
30E578121DAA7D690050F07E /* UIImage+MGLAdditions.mm */,
DD9BE4F51EB263C50079A3AF /* UIViewController+MGLAdditions.h */,
@@ -2258,6 +2266,7 @@
35D3A1E61E9BE7EB002B38EE /* MGLScaleBar.h in Headers */,
0778DD431F67556700A73B34 /* MGLComputedShapeSource.h in Headers */,
DA8848311CBAFA6200AB86E3 /* NSString+MGLAdditions.h in Headers */,
+ 967C864B210A9D3C004DF794 /* UIDevice+MGLAdditions.h in Headers */,
1FCAE2A220B872A400C577DD /* MGLLocationManager.h in Headers */,
DACA86262019218600E9693A /* MGLRasterDEMSource.h in Headers */,
353933F81D3FB79F003F57D7 /* MGLLineStyleLayer.h in Headers */,
@@ -2356,6 +2365,7 @@
4049C29E1DB6CD6C00B3F799 /* MGLPointCollection.h in Headers */,
3566C7671D4A77BA008152BC /* MGLShapeSource.h in Headers */,
DA35A29F1CC9E94C00E826B2 /* MGLCoordinateFormatter.h in Headers */,
+ 967C864C210A9D3C004DF794 /* UIDevice+MGLAdditions.h in Headers */,
404C26E31D89B877000AA13D /* MGLTileSource.h in Headers */,
96E516F6200059EC00A02306 /* MGLRendererFrontend.h in Headers */,
071BBB041EE76147001FB02A /* MGLImageSource.h in Headers */,
@@ -2949,6 +2959,7 @@
40834BF71FE05E1800C1BD0D /* MMEUniqueIdentifier.m in Sources */,
3566C7681D4A77BA008152BC /* MGLShapeSource.mm in Sources */,
40834C4A1FE05F7500C1BD0D /* TSKPinningValidator.m in Sources */,
+ 967C864D210A9D3C004DF794 /* UIDevice+MGLAdditions.m in Sources */,
400533021DB0862B0069F638 /* NSArray+MGLAdditions.mm in Sources */,
96036A03200565C700510F3D /* NSOrthography+MGLAdditions.m in Sources */,
40834BF31FE05E1800C1BD0D /* MMETimerManager.m in Sources */,
@@ -3076,6 +3087,7 @@
400533031DB086490069F638 /* NSArray+MGLAdditions.mm in Sources */,
40834C571FE05F7600C1BD0D /* TSKPinningValidator.m in Sources */,
35136D431D42274500C20EFD /* MGLRasterStyleLayer.mm in Sources */,
+ 967C864E210A9D3C004DF794 /* UIDevice+MGLAdditions.m in Sources */,
96036A04200565C700510F3D /* NSOrthography+MGLAdditions.m in Sources */,
40834C071FE05E1800C1BD0D /* MMETimerManager.m in Sources */,
3538AA201D542239008EC33D /* MGLForegroundStyleLayer.mm in Sources */,
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