import UIKit @main final class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? private var context: AppContext? func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { let window = UIWindow(frame: UIScreen.main.bounds) let context = AppContext() self.context = context window.rootViewController = context.makeRootController() window.makeKeyAndVisible() self.window = window return true } } @MainActor private final class AppContext: NSObject, UITabBarControllerDelegate { private let tabs = UITabBarController() private var navigationControllers: [UINavigationController] = [] private var restoreTask: Task? func makeRootController() -> UIViewController { let shell = mobileShellFixture(state: .loading) navigationControllers = shell.pages.map { page in let root = ShellViewController(page: page) let navigation = UINavigationController(rootViewController: root) navigation.navigationBar.prefersLargeTitles = true navigation.tabBarItem = UITabBarItem( title: page.title, image: UIImage(systemName: page.systemImage), selectedImage: UIImage(systemName: page.selectedSystemImage) ) return navigation } tabs.viewControllers = navigationControllers tabs.delegate = self restoreSelectedTab() return tabs } func tabBarController( _ tabBarController: UITabBarController, didSelect viewController: UIViewController ) { guard let navigation = viewController as? UINavigationController, let root = navigation.viewControllers.first as? ShellViewController else { return } do { try setSelectedMobileTab(tab: root.shellTab) } catch { let alert = UIAlertController( title: "Selection Was Not Saved", message: error.localizedDescription, preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "OK", style: .default)) tabs.present(alert, animated: true) } } private func restoreSelectedTab() { restoreTask?.cancel() restoreTask = Task { [weak self] in let shell = await Task.detached(priority: .userInitiated) { mobileShell() }.value guard !Task.isCancelled, let self else { return } if let index = shell.pages.firstIndex(where: { $0.tab == shell.selectedTab }) { tabs.selectedIndex = index } } } } @MainActor private final class ShellViewController: UITableViewController { fileprivate let shellTab: MobileTab private var page: MobilePage private var loadTask: Task? private var loadGeneration = 0 init(page: MobilePage) { shellTab = page.tab self.page = page super.init(style: .insetGrouped) title = page.title navigationItem.largeTitleDisplayMode = .always refreshControl = UIRefreshControl() refreshControl?.addTarget(self, action: #selector(refreshRequested), for: .valueChanged) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } deinit { loadTask?.cancel() } override func viewDidLoad() { super.viewDidLoad() apply(page) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) reloadShell() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) loadTask?.cancel() } override func numberOfSections(in tableView: UITableView) -> Int { page.state == .ready ? 1 : 0 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 1 } override func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil) var content = cell.defaultContentConfiguration() content.image = UIImage(systemName: page.systemImage) content.text = page.stateTitle content.secondaryText = page.stateDetail content.secondaryTextProperties.numberOfLines = 0 cell.contentConfiguration = content cell.selectionStyle = .none cell.accessibilityLabel = "\(page.stateTitle). \(page.stateDetail)" return cell } @objc private func refreshRequested() { reloadShell() } private func reloadShell() { loadGeneration += 1 let generation = loadGeneration loadTask?.cancel() loadTask = Task { [weak self] in let shell = await Task.detached(priority: .userInitiated) { mobileShell() }.value guard !Task.isCancelled, let self, generation == loadGeneration, let page = shell.pages.first(where: { $0.tab == self.shellTab }) else { return } apply(page) } } private func apply(_ page: MobilePage) { self.page = page title = page.title refreshControl?.endRefreshing() tableView.reloadData() guard page.state != .ready else { contentUnavailableConfiguration = nil return } var configuration = page.state == .loading ? UIContentUnavailableConfiguration.loading() : UIContentUnavailableConfiguration.empty() configuration.text = page.stateTitle configuration.secondaryText = page.stateDetail configuration.image = UIImage(systemName: stateImage(page.state)) contentUnavailableConfiguration = configuration } private func stateImage(_ state: MobileShellState) -> String { switch state { case .loading: "hourglass" case .empty: "lock.shield" case .ready: page.systemImage case .locked: "lock.fill" case .error: "exclamationmark.triangle" } } }