// // SegmentedPageViewController.swift // Tusker // // Created by Shadowfacts on 9/13/19. // Copyright © 2019 Shadowfacts. All rights reserved. // import UIKit protocol SegmentedPageViewControllerPage: Hashable { var segmentedControlTitle: String { get } } class SegmentedPageViewController: UIPageViewController, UIPageViewControllerDelegate, TabbedPageViewController { private(set) var pages: [Page]! private let pageProvider: (Page) -> UIViewController private var pageControllers = [Page: UIViewController]() private var initialPage: Page private var currentPage: Page var currentIndex: Int! { pages.firstIndex(of: currentPage) } var currentViewController: UIViewController { viewControllers!.first! } let segmentedControl = ScrollingSegmentedControl() init(pages: [Page], pageProvider: @escaping (Page) -> UIViewController) { precondition(!pages.isEmpty) self.pageProvider = pageProvider initialPage = pages.first! currentPage = pages.first! super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) setPages(pages, animated: false) segmentedControl.didSelectOption = { [unowned self] option in if let option { self.selectPage(option, animated: true) } } // TODO: the custom segmented control isn't treated as a group and I have no idea how to change that // the segemented control itself is only focusable when VoiceOver is in Group navigation mode, // so make it clear that to switch tabs the user needs to enter the group segmentedControl.accessibilityHint = "Enter group to select timeline" segmentedControl.setSelectedOption(segmentedControl.options.first!.value, animated: false) navigationItem.titleView = segmentedControl } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setPages(_ pages: [Page], animated: Bool) { precondition(!pages.isEmpty) self.pages = pages if !pages.contains(currentPage) { selectPage(pages.first!, animated: animated) } for key in pageControllers.keys where !pages.contains(key) { pageControllers.removeValue(forKey: key) } // this needs to happen in init because EnhancedNavigationViewController expects to be able to look at the titleView // before the view has necessarily loaded segmentedControl.options = pages.map { .init(value: $0, name: $0.segmentedControlTitle) } } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground selectPage(initialPage, animated: false) addKeyCommand(MenuController.prevSubTabCommand) addKeyCommand(MenuController.nextSubTabCommand) // disable the transparent nav bar because it gets messy with multiple pages at different scroll positions if let nav = navigationController { let appearance = UINavigationBarAppearance() appearance.configureWithDefaultBackground() nav.navigationBar.scrollEdgeAppearance = appearance } } func selectPage(_ page: Page, animated: Bool) { guard pages.contains(page) else { fatalError("invalid page \(page) that is not in SegmentedPageViewController.pages") } guard isViewLoaded else { initialPage = page return } let direction: UIPageViewController.NavigationDirection if let prevIndex = currentIndex { let index = pages.firstIndex(of: page)! direction = index - prevIndex > 0 ? .forward : .reverse } else { direction = .forward } currentPage = page let newController: UIViewController if let existing = pageControllers[page] { newController = existing } else { newController = pageProvider(page) pageControllers[page] = newController } setViewControllers([newController], direction: direction, animated: animated) navigationItem.title = newController.title segmentedControl.setSelectedOption(page, animated: animated) } // MARK: TabbedPageViewController func selectNextPage() { guard currentIndex < pageControllers.count - 1 else { return } selectPage(pages[currentIndex + 1], animated: true) } func selectPrevPage() { guard currentIndex > 0 else { return } selectPage(pages[currentIndex - 1], animated: true) } } extension SegmentedPageViewController: TabBarScrollableViewController { func tabBarScrollToTop() { if let scrollableVC = currentViewController as? TabBarScrollableViewController { scrollableVC.tabBarScrollToTop() } } } extension SegmentedPageViewController: BackgroundableViewController { func sceneDidEnterBackground() { if let current = currentViewController as? BackgroundableViewController { current.sceneDidEnterBackground() } } } extension SegmentedPageViewController: StatusBarTappableViewController { func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult { if let current = currentViewController as? StatusBarTappableViewController { return current.handleStatusBarTapped(xPosition: xPosition) } return .continue } }