From 7362b28c20d0c9d4144955aceea67ddab1c42e17 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 2 Feb 2025 20:18:38 -0800 Subject: [PATCH] Create and use MainWindowController for iOS. --- iOS/AppDelegate.swift | 35 ++------ iOS/MainWindow/MainWindowController.swift | 88 ++++++++++++++++++++ iOS/MainWindow/RootSplitViewController.swift | 21 +++-- 3 files changed, 110 insertions(+), 34 deletions(-) create mode 100644 iOS/MainWindow/MainWindowController.swift diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index eb34287c2..eabaa174e 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -17,6 +17,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC var window: UIWindow? + private var mainWindowController: MainWindowController? private var coordinator: SceneCoordinator? private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Application") @@ -77,30 +78,10 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC ArticleStatusSyncTimer.shared.update() #endif - // Create window. - let window = UIWindow(frame: UIScreen.main.bounds) - self.window = window - - // Create UI and add it to window. - let rootSplitViewController = RootSplitViewController() - coordinator = SceneCoordinator(rootSplitViewController: rootSplitViewController) - rootSplitViewController.coordinator = coordinator - rootSplitViewController.delegate = coordinator - - window.rootViewController = rootSplitViewController - - window.tintColor = AppColor.accent - updateUserInterfaceStyle() - UINavigationBar.appearance().scrollEdgeAppearance = UINavigationBarAppearance() - - window.makeKeyAndVisible() + // Create window and UI + mainWindowController = MainWindowController() Task { @MainActor in - // Ensure Feeds view shows on first run on iPad — otherwise the UI is empty. - if UIDevice.current.userInterfaceIdiom == .pad && AppDefaults.isFirstRun { - rootSplitViewController.show(.primary) - } - self.unreadCount = AccountManager.shared.unreadCount } @@ -119,7 +100,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC func applicationWillEnterForeground(_ application: UIApplication) { prepareAccountsForForeground() - coordinator?.resetFocus() + mainWindowController?.resetFocus() } private func prepareAccountsForForeground() { @@ -167,7 +148,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC ArticleThemeDownloader.shared.cleanUp() CoalescingQueue.standard.performCallsImmediately() - coordinator?.suspend() + mainWindowController?.suspend() logger.info("Application processing suspended.") } } @@ -195,7 +176,7 @@ extension AppDelegate { return } - coordinator?.cleanUp(conditional: true) + mainWindowController?.cleanUp(conditional: true) AccountManager.shared.refreshAll(errorHandler: errorHandler) } @@ -225,7 +206,7 @@ extension AppDelegate { default: handle(response) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.coordinator?.dismissIfLaunchingFromExternalAction() + self.mainWindowController?.dismissIfLaunchingFromExternalAction() } } } @@ -329,6 +310,6 @@ private extension AppDelegate { func handle(_ response: UNNotificationResponse) { AccountManager.shared.resumeAllIfSuspended() - coordinator?.handle(response) + mainWindowController?.handle(response) } } diff --git a/iOS/MainWindow/MainWindowController.swift b/iOS/MainWindow/MainWindowController.swift new file mode 100644 index 000000000..7aba3fe91 --- /dev/null +++ b/iOS/MainWindow/MainWindowController.swift @@ -0,0 +1,88 @@ +// +// MainWindowController.swift +// NetNewsWire-iOS +// +// Created by Brent Simmons on 2/2/25. +// Copyright © 2025 Ranchero Software. All rights reserved. +// + +import Foundation +import UIKit + +final class MainWindowController { + + let window: UIWindow + + let rootSplitViewController: RootSplitViewController + let sidebarViewController = MainFeedViewController() + let timelineViewController = TimelineViewController() + let articleViewController = ArticleViewController() + + let coordinator: SceneCoordinator + + init() { + let window = UIWindow(frame: UIScreen.main.bounds) + self.window = window + + let rootSplitViewController = RootSplitViewController( + sidebarViewController: sidebarViewController, + timelineViewController: timelineViewController, + articleViewController: articleViewController + ) + + self.coordinator = SceneCoordinator(rootSplitViewController: rootSplitViewController) + rootSplitViewController.coordinator = coordinator + rootSplitViewController.delegate = coordinator + self.rootSplitViewController = rootSplitViewController + + window.rootViewController = rootSplitViewController + + window.tintColor = AppColor.accent + updateUserInterfaceStyle() + UINavigationBar.appearance().scrollEdgeAppearance = UINavigationBarAppearance() + + window.makeKeyAndVisible() + + Task { @MainActor in + // Ensure Feeds view shows on first run on iPad — otherwise the UI is empty. + if UIDevice.current.userInterfaceIdiom == .pad && AppDefaults.isFirstRun { + rootSplitViewController.show(.primary) + } + } + } + + // MARK: - API + + func resetFocus() { + coordinator.resetFocus() + } + + func suspend() { + coordinator.suspend() + } + + func cleanUp(conditional: Bool) { + coordinator.cleanUp(conditional: conditional) + } + + func dismissIfLaunchingFromExternalAction() { + coordinator.dismissIfLaunchingFromExternalAction() + } + + func handle(_ response: UNNotificationResponse) { + coordinator.handle(response) + } +} + +private extension MainWindowController { + + func updateUserInterfaceStyle() { + + assert(Thread.isMainThread) + + let updatedStyle = AppDefaults.userInterfaceColorPalette.uiUserInterfaceStyle + if window.overrideUserInterfaceStyle != updatedStyle { + window.overrideUserInterfaceStyle = updatedStyle + } + } +} diff --git a/iOS/MainWindow/RootSplitViewController.swift b/iOS/MainWindow/RootSplitViewController.swift index 87ceea10d..945168d97 100644 --- a/iOS/MainWindow/RootSplitViewController.swift +++ b/iOS/MainWindow/RootSplitViewController.swift @@ -19,16 +19,23 @@ final class RootSplitViewController: UISplitViewController { } } - private lazy var sidebarViewController = MainFeedViewController() - private lazy var timelineViewController = TimelineViewController() - private lazy var articleViewController = ArticleViewController() + private let sidebarViewController: MainFeedViewController + private let timelineViewController: TimelineViewController + private let articleViewController: ArticleViewController + + init(sidebarViewController: MainFeedViewController, + timelineViewController: TimelineViewController, + articleViewController: ArticleViewController) { + + self.sidebarViewController = sidebarViewController + self.timelineViewController = timelineViewController + self.articleViewController = articleViewController - init() { super.init(style: .tripleColumn) - setViewController(self.sidebarViewController, for: .primary) - setViewController(self.timelineViewController, for: .supplementary) - setViewController(self.articleViewController, for: .secondary) + setViewController(sidebarViewController, for: .primary) + setViewController(timelineViewController, for: .supplementary) + setViewController(articleViewController, for: .secondary) self.showsSecondaryOnlyButton = true self.preferredDisplayMode = .oneBesideSecondary