Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Prevent map view on external display from sleeping in background #13701

Merged
merged 2 commits into from
Jan 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion platform/ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
* Fixed a bug where the `animated` parameter to `-[MGLMapView selectAnnotation:animated:]` was being ignored. ([#13689](https://github.com/mapbox/mapbox-gl-native/pull/13689))
* Reinstates version 11 as the default Mapbox Streets style (as introduced in 4.7.0). ([#13690](https://github.com/mapbox/mapbox-gl-native/pull/13690))
* Added the `-[MGLShapeSource leavesOfCluster:offset:limit:]`, `-[MGLShapeSource childrenOfCluster:]`, `-[MGLShapeSource zoomLevelForExpandingCluster:]` methods for inspecting a cluster in an `MGLShapeSource`s created with the `MGLShapeSourceOptionClustered` option. Feature querying now returns clusters represented by `MGLPointFeatureCluster` objects (that conform to the `MGLCluster` protocol). ([#12952](https://github.com/mapbox/mapbox-gl-native/pull/12952)

* `MGLMapView` no longer freezes on external displays connected through AirPlay or CarPlay when the main device’s screen goes to sleep or the user manually locks the screen. ([#13701](https://github.com/mapbox/mapbox-gl-native/pull/13701))

## 4.7.1 - December 21, 2018

Expand Down
36 changes: 36 additions & 0 deletions platform/ios/app/MBXViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ @interface MBXViewController () <UITableViewDelegate,
@property (nonatomic) BOOL frameTimeGraphEnabled;
@property (nonatomic) BOOL shouldLimitCameraChanges;
@property (nonatomic) BOOL randomWalk;
@property (nonatomic) NSMutableArray<UIWindow *> *helperWindows;

@end

Expand Down Expand Up @@ -289,6 +290,32 @@ - (void)viewDidLoad
}
}
[self.mapView addGestureRecognizer:singleTap];

// Display a secondary map on any connected external display.
// https://developer.apple.com/documentation/uikit/windows_and_screens/displaying_content_on_a_connected_screen?language=objc
self.helperWindows = [NSMutableArray array];
[[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidConnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
UIScreen *helperScreen = note.object;
UIWindow *helperWindow = [[UIWindow alloc] initWithFrame:helperScreen.bounds];
helperWindow.screen = helperScreen;
UIViewController *helperViewController = [[UIViewController alloc] init];
MGLMapView *helperMapView = [[MGLMapView alloc] initWithFrame:helperWindow.bounds styleURL:MGLStyle.satelliteStreetsStyleURL];
helperMapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
helperMapView.camera = self.mapView.camera;
helperMapView.compassView.hidden = YES;
helperViewController.view = helperMapView;
helperWindow.rootViewController = helperViewController;
helperWindow.hidden = NO;
[self.helperWindows addObject:helperWindow];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidDisconnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
UIScreen *helperScreen = note.object;
for (UIWindow *window in self.helperWindows) {
if (window.screen == helperScreen) {
[self.helperWindows removeObject:window];
}
}
}];
}

- (void)saveState:(__unused NSNotification *)notification
Expand Down Expand Up @@ -2214,6 +2241,7 @@ - (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)old
- (void)mapViewRegionIsChanging:(MGLMapView *)mapView
{
[self updateHUD];
[self updateHelperMapViews];
}

- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated
Expand All @@ -2223,12 +2251,20 @@ - (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChange
}

[self updateHUD];
[self updateHelperMapViews];
}

- (void)mapView:(MGLMapView *)mapView didUpdateUserLocation:(MGLUserLocation *)userLocation {
[self updateHUD];
}

- (void)updateHelperMapViews {
for (UIWindow *window in self.helperWindows) {
MGLMapView *mapView = (MGLMapView *)window.rootViewController.view;
mapView.camera = self.mapView.camera;
}
}

- (void)updateHUD {
if (!self.reuseQueueStatsEnabled && !self.mapInfoHUDEnabled) return;

Expand Down
12 changes: 11 additions & 1 deletion platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1222,7 +1222,7 @@ - (void)validateDisplayLink
self.mbglMap.setConstrainMode(mbgl::ConstrainMode::HeightOnly);
}

_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateFromDisplayLink)];
_displayLink = [self.window.screen displayLinkWithTarget:self selector:@selector(updateFromDisplayLink)];
[self updateDisplayLinkPreferredFramesPerSecond];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
_needsDisplayRefresh = YES;
Expand Down Expand Up @@ -1301,6 +1301,16 @@ - (void)deviceOrientationDidChange:(__unused NSNotification *)notification

- (void)sleepGL:(__unused NSNotification *)notification
{
// If this view targets an external display, such as AirPlay or CarPlay, we
// can safely continue to render OpenGL content without tripping
// gpus_ReturnNotPermittedKillClient in libGPUSupportMercury, because the
// external connection keeps the application from truly receding to the
// background.
if (self.window.screen != [UIScreen mainScreen])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you consider adding a flag along the lines of enableExternalDisplay or enableSecondaryExternalDisplay? Although I don't have an specific use case in mind, it may be good let the developers control this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m leaning towards not making this behavior configurable. Note that this PR only affects whether external displays can continue to render after the main display falls asleep; I’m pretty certain map views can already render on external displays.

I think an enableExternalDisplay option would lead to surprising behavior. Suppose an application has non-mirroring support for external displays, such as what this PR adds to iosapp, and the external display’s window contains not only a map view but other views. Perhaps these other views are programmatically synchronized to the map view, with dynamic text based on the current camera. If enableExternalDisplay is disabled, then the UI will appear to work normally, but as soon as the main display falls asleep, the map view portion of the UI will halt and eventually go black. All the while, the rest of the UI will continue to update, because the map view’s model continues to function even while rendering is disabled.

That said, I’m open to adding an explicit option if we find that this PR regresses the crash fix and we can’t figure out a way around it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d agree with @1ec5 here — the developer should probably be responsible for handling how mirroring/other-screens are managed (and we should try to transparently support those without configuration).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@1ec5 @friedbunny that makes sense, thank you for explaining your thoughts. I got a final question: If a map is being displayed on a second screen; would it be useful have delegates that accounts for that case?
e.g.
mapViewWillRenderSecondScreen/mapViewWillStopRenderingSecondScreen

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☝🏼discard my comment above.

{
return;
}

MGLLogInfo(@"Entering background.");
MGLAssertIsMainThread();

Expand Down