Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pause rendering when entering background #1086

Merged
merged 13 commits into from
Feb 9, 2022
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Mapbox welcomes participation and contributions from everyone.

* Updated to MapboxCoreMaps 10.3.0 and MapboxCommon 21.1.0. ([#1078](https://github.com/mapbox/mapbox-maps-ios/pull/1078))
* Fix compass button regression introduced in rc.1. ([#1083](https://github.com/mapbox/mapbox-maps-ios/pull/1083))
* Prevent rendering in background by pausing/resuming display link in response to application or scene lifecycle events ([#1086](https://github.com/mapbox/mapbox-maps-ios/pull/1086))

## 10.3.0-rc.1 – January 26, 2022

Expand Down
1 change: 1 addition & 0 deletions Sources/MapboxMaps/Foundation/DisplayLinkProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ internal protocol DisplayLinkProtocol: AnyObject {
@available(iOS 15.0, *)
var preferredFrameRateRange: CAFrameRateRange { get set }
#endif
var isPaused: Bool { get set }
func add(to runloop: RunLoop, forMode mode: RunLoop.Mode)
func invalidate()
}
Expand Down
16 changes: 16 additions & 0 deletions Sources/MapboxMaps/Foundation/Extensions/UIWindow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import UIKit
macdrevx marked this conversation as resolved.
Show resolved Hide resolved

@available(iOS 13.0, *)
extension UIWindow {
private static let templateApplicationSceneSelector = Selector(("templateApplicationScene"))
macdrevx marked this conversation as resolved.
Show resolved Hide resolved

internal var parentScene: UIScene? {
var carPlayScene: UIScene? {
guard self.responds(to: UIWindow.templateApplicationSceneSelector) else {
return nil
}
return self.value(forKey: UIWindow.templateApplicationSceneSelector.description) as? UIScene
}
return windowScene ?? carPlayScene
}
evil159 marked this conversation as resolved.
Show resolved Hide resolved
}
89 changes: 89 additions & 0 deletions Sources/MapboxMaps/Foundation/MapView+Lifecycle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import Foundation
import UIKit

@available(iOSApplicationExtension, unavailable)
extension MapView {
macdrevx marked this conversation as resolved.
Show resolved Hide resolved
private static let UIApplicationSceneManifestKey = "UIApplicationSceneManifest"

internal func subscribeToMemoryWarningNotification() {
NotificationCenter.default.addObserver(self,
selector: #selector(didReceiveMemoryWarning),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil)
}

internal func subscribeToLifecycleNotifications() {
if Bundle.main.object(forInfoDictionaryKey: MapView.UIApplicationSceneManifestKey) != nil {
if #available(iOS 13.0, *) {
macdrevx marked this conversation as resolved.
Show resolved Hide resolved
subscribeToSceneLifecycleNotifications()
}
} else {
subscribeToApplicationsLifecycleNotifications()
}
}

internal func unsubscribeFromLifecycleNotifications() {
if Bundle.main.object(forInfoDictionaryKey: MapView.UIApplicationSceneManifestKey) != nil {
if #available(iOS 13.0, *) {
NotificationCenter.default.removeObserver(self, name: UIScene.willEnterForegroundNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIScene.didEnterBackgroundNotification, object: nil)
}
} else {
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil)
}
macdrevx marked this conversation as resolved.
Show resolved Hide resolved
}

@objc internal func didReceiveMemoryWarning() {
mapboxMap.reduceMemoryUse()
}

@objc internal func willEnterForeground() {
resumeDisplayLink()
}

@objc internal func didEnterBackground() {
mapboxMap.reduceMemoryUse()
pauseDisplayLink()
}

@available(iOS 13.0, *)
@objc private func didReceiveSceneLifecycleNotification(_ notification: Notification) {
// making sure the scene is the correct one, as the scene may not be available when subscribing
guard let scene = notification.object as? UIScene, scene == window?.parentScene else {
return
}

switch notification.name {
case UIScene.willEnterForegroundNotification:
willEnterForeground()
case UIScene.didEnterBackgroundNotification:
didEnterBackground()
default:
break
}
}
macdrevx marked this conversation as resolved.
Show resolved Hide resolved

@available(iOS 13.0, *)
private func subscribeToSceneLifecycleNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(willEnterForeground),
name: UIScene.willEnterForegroundNotification,
object: window?.parentScene)
NotificationCenter.default.addObserver(self,
selector: #selector(didEnterBackground),
name: UIScene.didEnterBackgroundNotification,
object: window?.parentScene)
}

private func subscribeToApplicationsLifecycleNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(willEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(didEnterBackground),
name: UIApplication.didEnterBackgroundNotification,
object: nil)
}
}
17 changes: 10 additions & 7 deletions Sources/MapboxMaps/Foundation/MapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,6 @@ open class MapView: UIView {
mapClient.delegate = self
mapboxMap = MapboxMap(mapClient: mapClient, mapInitOptions: resolvedMapInitOptions)

NotificationCenter.default.addObserver(self,
selector: #selector(didReceiveMemoryWarning),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil)

// Use the overriding style URI if provided (currently from IB)
if let initialStyleURI = overridingStyleURI,
let styleURI = StyleURI(url: initialStyleURI) {
Expand Down Expand Up @@ -274,6 +269,8 @@ open class MapView: UIView {

// Set up managers
setupManagers()

subscribeToMemoryWarningNotification()
macdrevx marked this conversation as resolved.
Show resolved Hide resolved
}

internal func setupManagers() {
Expand Down Expand Up @@ -430,7 +427,9 @@ open class MapView: UIView {

if window != nil {
validateDisplayLink()
subscribeToLifecycleNotifications()
} else {
unsubscribeFromLifecycleNotifications()
// TODO: Fix this up correctly.
macdrevx marked this conversation as resolved.
Show resolved Hide resolved
displayLink?.invalidate()
displayLink = nil
Expand All @@ -442,8 +441,12 @@ open class MapView: UIView {
super.didMoveToSuperview()
}

@objc func didReceiveMemoryWarning() {
mapboxMap.reduceMemoryUse()
internal func resumeDisplayLink() {
displayLink?.isPaused = false
}

internal func pauseDisplayLink() {
displayLink?.isPaused = true
}

// MARK: Location
Expand Down
11 changes: 11 additions & 0 deletions Tests/MapboxMapsTests/Foundation/MapViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,15 @@ final class MapViewTests: XCTestCase {
mapView.didReceiveMemoryWarning()
}

func testWillEnterForeground() {
mapView.willEnterForeground()

XCTAssertFalse(displayLink.isPaused)
}

func testDidEnterBackground() {
mapView.didEnterBackground()

XCTAssertTrue(displayLink.isPaused)
}
macdrevx marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ final class MockDisplayLink: DisplayLinkProtocol {

var preferredFramesPerSecond: Int = 0

var isPaused: Bool = false

// Checking Swift version as a proxy for iOS SDK version to enable
// building with iOS SDKs < 15
#if swift(>=5.5)
Expand Down