Tusker/Tusker/Screens/Utilities/SegmentedPageViewController...

133 lines
4.7 KiB
Swift

//
// SegmentedPageViewController.swift
// Tusker
//
// Created by Shadowfacts on 9/13/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
class SegmentedPageViewController<Page: Hashable>: UIPageViewController, UIPageViewControllerDelegate, TabbedPageViewController {
let pages: [Page]
let pageControllers: [UIViewController]
private var initialPage: Page
private var currentPage: Page
var currentIndex: Int {
pages.firstIndex(of: currentPage)!
}
let segmentedControl = ScrollingSegmentedControl<Page>()
init(pages: [(Page, String, UIViewController)]) {
precondition(!pages.isEmpty)
self.pages = pages.map(\.0)
self.pageControllers = pages.map(\.2)
initialPage = self.pages.first!
currentPage = self.pages.first!
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
// 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.0, name: $0.1)
}
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")
}
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 prevIndex = currentIndex
currentPage = page
let index = pages.firstIndex(of: page)!
let newController = pageControllers[index]
let direction: UIPageViewController.NavigationDirection = index - prevIndex > 0 ? .forward : .reverse
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 = pageControllers[currentIndex] as? TabBarScrollableViewController {
scrollableVC.tabBarScrollToTop()
}
}
}
extension SegmentedPageViewController: BackgroundableViewController {
func sceneDidEnterBackground() {
if let current = pageControllers[currentIndex] as? BackgroundableViewController {
current.sceneDidEnterBackground()
}
}
}
extension SegmentedPageViewController: StatusBarTappableViewController {
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
if let current = pageControllers[currentIndex] as? StatusBarTappableViewController {
return current.handleStatusBarTapped(xPosition: xPosition)
}
return .continue
}
}