Skip to content

Commit 1b6ac43

Browse files
authored
Migrate to UIScene lifecycle (#16230)
2 parents b4147e3 + 29941d1 commit 1b6ac43

File tree

9 files changed

+253
-189
lines changed

9 files changed

+253
-189
lines changed

WooCommerce/Classes/AppDelegate.swift

Lines changed: 14 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import Combine
33
import Storage
44
import class Networking.UserAgent
55
import Experiments
6-
import class WidgetKit.WidgetCenter
76
import protocol WooFoundation.Analytics
87
import protocol Yosemite.StoresManager
98
import struct Yosemite.Site
@@ -31,34 +30,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
3130
return UIApplication.shared.delegate as! AppDelegate
3231
}
3332

34-
/// Main Window
35-
///
36-
var window: UIWindow?
37-
38-
/// Coordinates app navigation based on authentication state.
39-
///
40-
private var appCoordinator: AppCoordinator?
41-
42-
/// Initializes storage manager along with AppDelegate
43-
private let storageManager = ServiceLocator.storageManager
44-
4533
/// Tab Bar Controller
4634
///
4735
var tabBarController: MainTabBarController? {
48-
appCoordinator?.tabBarController
36+
return UIApplication.sceneDelegate?.tabBarController
4937
}
5038

5139
/// Coordinates the Jetpack setup flow for users authenticated without Jetpack.
5240
///
5341
private var jetpackSetupCoordinator: JetpackSetupCoordinator?
5442

55-
private var universalLinkRouter: UniversalLinkRouter?
56-
57-
private lazy var requirementsChecker = RequirementsChecker(baseViewController: tabBarController)
43+
private(set) lazy var requirementsChecker = RequirementsChecker(baseViewController: tabBarController)
5844

5945
/// Handles events to background refresh the app.
6046
///
61-
private let appRefreshHandler = BackgroundTaskRefreshDispatcher()
47+
let appRefreshHandler = BackgroundTaskRefreshDispatcher()
6248

6349
private var subscriptions: Set<AnyCancellable> = []
6450

@@ -104,46 +90,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
10490
checkForUpgrades()
10591

10692
// Cache onboarding state to speed IPP process
107-
refreshCardPresentPaymentsOnboardingIfNeeded(completion: reconnectToTapToPayReaderIfNeeded)
93+
refreshCardPresentPaymentsOnboardingIfNeeded()
10894

10995
return true
11096
}
11197

11298
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
113-
// Setup the Interface!
114-
setupMainWindow()
99+
// Global UIAppearance that doesn't require a WindowScene
115100
setupComponentsAppearance()
116-
setupNoticePresenter()
117-
setupUniversalLinkRouter()
118101
disableAnimationsIfNeeded()
119102

120103
// Don't track startup waiting time if user starts logged out
121104
if !ServiceLocator.stores.isAuthenticated {
122105
cancelStartupWaitingTimeTracker()
123106
}
124107

125-
// Start app navigation.
126-
appCoordinator?.start()
127-
128-
// Register for background app refresh events.
108+
// Register for background app refresh events (scene will schedule actual refreshes)
129109
appRefreshHandler.registerSystemTaskIdentifier()
130110

131111
return true
132112
}
133113

134-
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
135-
guard let rootViewController = window?.rootViewController else {
136-
fatalError()
137-
}
138-
139-
if let universalLinkRouter, universalLinkRouter.canHandle(url: url) {
140-
universalLinkRouter.handle(url: url)
141-
return true
142-
}
143-
144-
return handleAuthenticationUrl(url, options: options, rootViewController: rootViewController)
145-
}
146-
147114
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
148115
guard let defaultStoreID = ServiceLocator.stores.sessionManager.defaultStoreID else {
149116
return
@@ -164,99 +131,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
164131
await ServiceLocator.pushNotesManager.handleRemoteNotificationInTheBackground(userInfo: userInfo)
165132
}
166133

167-
func applicationWillResignActive(_ application: UIApplication) {
168-
// Simulate push notification for capturing snapshot.
169-
// This is supposed to be called only by the WooCommerceScreenshots target.
170-
if ProcessConfiguration.shouldSimulatePushNotification {
171-
let content = UNMutableNotificationContent()
172-
content.title = NSLocalizedString(
173-
"You have a new order! 🎉",
174-
comment: "Title for the mocked order notification needed for the AppStore listing screenshot"
175-
)
176-
content.body = NSLocalizedString(
177-
"New order for $13.98 on Your WooCommerce Store",
178-
comment: "Message for the mocked order notification needed for the AppStore listing screenshot. " +
179-
"'Your WooCommerce Store' is the name of the mocked store."
180-
)
181-
182-
// show this notification seconds from now
183-
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false)
184-
185-
// choose a random identifier
186-
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
187-
188-
// add our notification request
189-
UNUserNotificationCenter.current().add(request)
190-
}
191-
}
192-
193-
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
194-
guard let quickAction = QuickAction(rawValue: shortcutItem.type),
195-
let tabBarController else {
196-
completionHandler(false)
197-
return
198-
}
199-
switch quickAction {
200-
case QuickAction.addProduct:
201-
MainTabBarController.presentAddProductFlow()
202-
completionHandler(true)
203-
case QuickAction.addOrder:
204-
tabBarController.navigate(to: OrdersDestination.createOrder)
205-
completionHandler(true)
206-
case QuickAction.openOrders:
207-
tabBarController.navigate(to: OrdersDestination.orderList)
208-
completionHandler(true)
209-
case QuickAction.collectPayment:
210-
tabBarController.navigate(to: OrdersDestination.createOrder)
211-
completionHandler(true)
212-
}
213-
}
214-
215-
func applicationDidEnterBackground(_ application: UIApplication) {
216-
// Use this method to release shared resources, save user data, invalidate timers,
217-
// and store enough application state information to restore your application to its current state in case it is terminated later.
218-
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
219-
220-
// Don't track startup waiting time if app is backgrounded before everything is loaded
221-
cancelStartupWaitingTimeTracker()
222-
223-
// Schedule the background app refresh when sending the app to the background.
224-
// The OS is in charge of determining when these tasks will run based on app usage patterns.
225-
appRefreshHandler.scheduleAppRefresh()
226-
}
227-
228-
func applicationWillEnterForeground(_ application: UIApplication) {
229-
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
230-
231-
// Cache onboarding state to speed IPP process, then silently connect to Tap to Pay if previously connected, to speed up IPP
232-
refreshCardPresentPaymentsOnboardingIfNeeded(completion: reconnectToTapToPayReaderIfNeeded)
233-
}
234-
235-
func applicationDidBecomeActive(_ application: UIApplication) {
236-
// Restart any tasks that were paused (or not yet started) while the application was inactive.
237-
// If the application was previously in the background, optionally refresh the user interface.
238-
239-
requirementsChecker.checkEligibilityForDefaultStore()
240-
}
241-
242134
func applicationWillTerminate(_ application: UIApplication) {
243135
DDLogVerbose("👀 Application terminating...")
244136
NotificationCenter.default.post(name: .applicationTerminating, object: nil)
245137
}
246138

247-
func application(_ application: UIApplication,
248-
continue userActivity: NSUserActivity,
249-
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
250-
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
251-
handleWebActivity(userActivity)
252-
}
253-
254-
SpotlightManager.handleUserActivity(userActivity)
255-
trackWidgetTappedIfNeeded(userActivity: userActivity)
256-
257-
return true
258-
}
259-
260139
func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
261140
let size = os_proc_available_memory()
262141
DDLogDebug("Received memory warning: Available memory - \(size)")
@@ -266,17 +145,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
266145

267146
// MARK: - Initialization Methods
268147
//
269-
private extension AppDelegate {
270-
271-
/// Sets up the main UIWindow instance.
272-
///
273-
func setupMainWindow() {
274-
let window = UIWindow()
275-
window.makeKeyAndVisible()
276-
self.window = window
277-
278-
appCoordinator = AppCoordinator(window: window)
279-
}
148+
extension AppDelegate {
280149

281150
/// Sets up all of the component(s) Appearance.
282151
///
@@ -292,9 +161,6 @@ private extension AppDelegate {
292161
UINavigationBar.applyWooAppearance()
293162
UILabel.applyWooAppearance()
294163
UITabBar.applyWooAppearance()
295-
296-
// Take advantage of a bug in UIAlertController to style all UIAlertControllers with WC color
297-
window?.tintColor = .primary
298164
}
299165

300166
/// Sets up FancyAlert's UIAppearance.
@@ -368,15 +234,9 @@ private extension AppDelegate {
368234
CocoaLumberjackSwift.dynamicLogLevel = level
369235
}
370236

371-
/// Setup: Notice Presenter
372-
///
373-
func setupNoticePresenter() {
374-
var noticePresenter = ServiceLocator.noticePresenter
375-
noticePresenter.presentingViewController = appCoordinator?.tabBarController
376-
}
377-
378237
/// Push Notifications: Authorization + Registration!
379238
///
239+
// periphery: ignore - Fails when build on simulator
380240
func setupPushNotificationsManagerIfPossible(_ pushNotesManager: PushNotesManager, stores: StoresManager) {
381241
#if targetEnvironment(simulator)
382242
DDLogVerbose("👀 Push Notifications are not supported in the Simulator!")
@@ -391,11 +251,6 @@ private extension AppDelegate {
391251
UNUserNotificationCenter.current().delegate = self
392252
}
393253

394-
func setupUniversalLinkRouter() {
395-
guard let tabBarController = tabBarController else { return }
396-
universalLinkRouter = UniversalLinkRouter.defaultUniversalLinkRouter(tabBarController: tabBarController)
397-
}
398-
399254
/// Set up app review prompt
400255
///
401256
func setupAppRatingManager() {
@@ -445,7 +300,7 @@ private extension AppDelegate {
445300
}
446301

447302
if ProcessConfiguration.shouldUseScreenshotsNetworkLayer {
448-
ServiceLocator.setStores(ScreenshotStoresManager(storageManager: storageManager))
303+
ServiceLocator.setStores(ScreenshotStoresManager(storageManager: ServiceLocator.storageManager))
449304
}
450305

451306
if ProcessConfiguration.shouldSimulatePushNotification {
@@ -470,27 +325,15 @@ private extension AppDelegate {
470325
}
471326
}
472327

473-
func refreshCardPresentPaymentsOnboardingIfNeeded(completion: @escaping (() -> Void)) {
474-
ServiceLocator.cardPresentPaymentsOnboardingIPPUsersRefresher.refreshIPPUsersOnboardingState(completion: completion)
328+
func refreshCardPresentPaymentsOnboardingIfNeeded() {
329+
ServiceLocator
330+
.cardPresentPaymentsOnboardingIPPUsersRefresher
331+
.refreshIPPUsersOnboardingState(completion: reconnectToTapToPayReaderIfNeeded)
475332
}
476333

477334
func reconnectToTapToPayReaderIfNeeded() {
478335
ServiceLocator.tapToPayReconnectionController.reconnectIfNeeded()
479336
}
480-
481-
/// Tracks if the application was opened via a widget tap.
482-
///
483-
func trackWidgetTappedIfNeeded(userActivity: NSUserActivity) {
484-
switch userActivity.activityType {
485-
case WooConstants.storeInfoWidgetKind:
486-
let widgetFamily = userActivity.userInfo?[WidgetCenter.UserInfoKey.family] as? String
487-
ServiceLocator.analytics.track(event: .Widgets.widgetTapped(name: .todayStats, family: widgetFamily))
488-
case WooConstants.appLinkWidgetKind:
489-
ServiceLocator.analytics.track(event: .Widgets.widgetTapped(name: .appLink))
490-
default:
491-
break
492-
}
493-
}
494337
}
495338

496339

@@ -560,20 +403,8 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
560403
}
561404
}
562405

563-
// MARK: - Universal Links
564-
565-
private extension AppDelegate {
566-
func handleWebActivity(_ activity: NSUserActivity) {
567-
guard let linkURL = activity.webpageURL else {
568-
return
569-
}
570-
571-
universalLinkRouter?.handle(url: linkURL)
572-
}
573-
}
574-
575406
// MARK: - Magic link
576-
private extension AppDelegate {
407+
extension AppDelegate {
577408
func handleAuthenticationUrl(_ url: URL, options: [UIApplication.OpenURLOptionsKey: Any], rootViewController: UIViewController) -> Bool {
578409
return if ServiceLocator.stores.isAuthenticated {
579410
handleAuthenticationUrlForJetpackSetup(url, rootViewController: rootViewController)

WooCommerce/Classes/Extensions/UIApplication+Woo.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,25 @@ extension UIApplication.State {
2020
}
2121
}
2222
}
23+
24+
extension UIApplication {
25+
/// Returns the current key window from the connected window scenes.
26+
static var wooKeyWindow: UIWindow? {
27+
UIApplication.shared.connectedScenes
28+
.compactMap { $0 as? UIWindowScene }
29+
.flatMap { $0.windows }
30+
.first { $0.isKeyWindow }
31+
}
32+
33+
/// Returns the delegate of the first connected scene.
34+
///
35+
/// - Note: This approach is valid while the app supports only a single scene.
36+
/// If multi‑window support is introduced in the future,
37+
/// this will need to be revisited to handle multiple scenes safely.
38+
static var sceneDelegate: SceneDelegate? {
39+
UIApplication.shared.connectedScenes
40+
.compactMap({ $0 as? UIWindowScene })
41+
.compactMap({ $0.delegate as? SceneDelegate })
42+
.first
43+
}
44+
}

0 commit comments

Comments
 (0)