import UIKit @MainActor final class NavigationSettingsViewController: UITableViewController { private let context: AppContext private var selected: [PrimaryDestination] init(context: AppContext) { self.context = context selected = context.primaryDestinations super.init(style: .insetGrouped) title = "Primary Navigation" navigationItem.rightBarButtonItem = editButtonItem } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } private var available: [PrimaryDestination] { PrimaryDestination.allCases.filter { !selected.contains($0) } } override func numberOfSections(in tableView: UITableView) -> Int { 2 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { section == 0 ? selected.count : available.count } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { section == 0 ? "Shown After Home" : "Available Destinations" } override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { section == 0 ? "Tap Edit to add, remove, or reorder up to four destinations. Home is always first." : "Destinations not in the tab bar remain available as buttons on Home." } override func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { let destination = indexPath.section == 0 ? selected[indexPath.row] : available[indexPath.row] let cell = UITableViewCell(style: .default, reuseIdentifier: nil) var content = cell.defaultContentConfiguration() content.text = destination.title content.image = UIImage(systemName: destination.symbolName) content.imageProperties.tintColor = .tintColor if indexPath.section == 1 && selected.count == 4 { content.textProperties.color = .secondaryLabel content.imageProperties.tintColor = .tertiaryLabel } cell.selectionStyle = .none cell.contentConfiguration = content return cell } override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { tableView.isEditing && (indexPath.section == 0 || selected.count < 4) } override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { tableView.isEditing && indexPath.section == 0 } override func tableView( _ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath ) { guard sourceIndexPath.section == 0, destinationIndexPath.section == 0 else { tableView.reloadData() return } let destination = selected.remove(at: sourceIndexPath.row) selected.insert(destination, at: destinationIndexPath.row) save() } override func tableView( _ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath ) -> IndexPath { guard proposedDestinationIndexPath.section == 0 else { return IndexPath(row: max(selected.count - 1, 0), section: 0) } return proposedDestinationIndexPath } override func tableView( _ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath ) -> UITableViewCell.EditingStyle { if indexPath.section == 0 { return .delete } return selected.count < 4 ? .insert : .none } override func tableView( _ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath ) { switch (editingStyle, indexPath.section) { case (.delete, 0): selected.remove(at: indexPath.row) case (.insert, 1) where selected.count < 4: selected.append(available[indexPath.row]) default: return } save() tableView.reloadData() } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) } private func save() { do { try context.core.setPrimaryDestinations(destinations: selected) context.applyPrimaryDestinations() } catch { selected = context.primaryDestinations tableView.reloadData() show(error: error) } } }