Use browser-style navigation bars on iPad
This commit is contained in:
parent
f702df2f15
commit
08b7cf013b
|
@ -88,6 +88,7 @@
|
|||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; };
|
||||
D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6370B9A24421FF30092A7FF /* Tusker.xcdatamodeld */; };
|
||||
D63A8D0B2561C27F00D9DFFF /* ProfileStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63A8D0A2561C27F00D9DFFF /* ProfileStatusesViewController.swift */; };
|
||||
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63D8DF32850FE7A008D95E1 /* ViewTags.swift */; };
|
||||
D63F9C6E241D2D85004C03CF /* CompositionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63F9C6D241D2D85004C03CF /* CompositionAttachment.swift */; };
|
||||
D6403CC224A6B72D00E81C55 /* VisualEffectImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6403CC124A6B72D00E81C55 /* VisualEffectImageButton.swift */; };
|
||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */ = {isa = PBXBuildFile; fileRef = D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */; };
|
||||
|
@ -434,6 +435,7 @@
|
|||
D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesNavigationController.swift; sourceTree = "<group>"; };
|
||||
D6370B9B24421FF30092A7FF /* Tusker.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Tusker.xcdatamodel; sourceTree = "<group>"; };
|
||||
D63A8D0A2561C27F00D9DFFF /* ProfileStatusesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileStatusesViewController.swift; sourceTree = "<group>"; };
|
||||
D63D8DF32850FE7A008D95E1 /* ViewTags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTags.swift; sourceTree = "<group>"; };
|
||||
D63F9C6D241D2D85004C03CF /* CompositionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachment.swift; sourceTree = "<group>"; };
|
||||
D6403CC124A6B72D00E81C55 /* VisualEffectImageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectImageButton.swift; sourceTree = "<group>"; };
|
||||
D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = DomainBlocks.plist; sourceTree = "<group>"; };
|
||||
|
@ -1372,6 +1374,7 @@
|
|||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
|
||||
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */,
|
||||
D62E9988279DB2D100C26176 /* InstanceFeatures.swift */,
|
||||
D63D8DF32850FE7A008D95E1 /* ViewTags.swift */,
|
||||
D6D4DDD6212518A200E1C4BB /* Assets.xcassets */,
|
||||
D6AEBB3F2321640F00E5038B /* Activities */,
|
||||
D6F1F84E2193B9BE00F5FE67 /* Caching */,
|
||||
|
@ -1821,6 +1824,7 @@
|
|||
D681E4D5246E2BC30053414F /* UnmuteConversationActivity.swift in Sources */,
|
||||
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameLabel.swift in Sources */,
|
||||
D6C143E025354E34007DC240 /* EmojiPickerCollectionViewController.swift in Sources */,
|
||||
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */,
|
||||
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */,
|
||||
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */,
|
||||
D62275A024F1677200B82A16 /* ComposeHostingController.swift in Sources */,
|
||||
|
|
|
@ -15,9 +15,6 @@ protocol ComposeHostingControllerDelegate: AnyObject {
|
|||
func dismissCompose(mode: ComposeUIState.DismissMode) -> Bool
|
||||
}
|
||||
|
||||
private let VISIBILITY_BAR_BUTTON_TAG = 42001
|
||||
private let LOCAL_ONLY_BAR_BUTTON_TAG = 42002
|
||||
|
||||
class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
||||
|
||||
weak var delegate: ComposeHostingControllerDelegate?
|
||||
|
@ -135,12 +132,12 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
items.append(UIBarButtonItem(title: "CW", style: .plain, target: self, action: #selector(cwButtonPressed)))
|
||||
|
||||
let visibilityItem = UIBarButtonItem(image: nil, style: .plain, target: nil, action: nil)
|
||||
visibilityItem.tag = VISIBILITY_BAR_BUTTON_TAG
|
||||
visibilityItem.tag = ViewTags.composeVisibilityBarButton
|
||||
items.append(visibilityItem)
|
||||
|
||||
if mastodonController.instanceFeatures.localOnlyPosts {
|
||||
let item = UIBarButtonItem(image: nil, style: .plain, target: nil, action: nil)
|
||||
item.tag = LOCAL_ONLY_BAR_BUTTON_TAG
|
||||
item.tag = ViewTags.composeLocalOnlyBarButton
|
||||
items.append(item)
|
||||
localOnlyChanged(draft.localOnly)
|
||||
}
|
||||
|
@ -243,7 +240,7 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
|
||||
private func visibilityChanged(_ newVisibility: Status.Visibility) {
|
||||
for toolbar in [mainToolbar, inputAccessoryToolbar] {
|
||||
guard let item = toolbar?.items?.first(where: { $0.tag == VISIBILITY_BAR_BUTTON_TAG }) else {
|
||||
guard let item = toolbar?.items?.first(where: { $0.tag == ViewTags.composeVisibilityBarButton }) else {
|
||||
continue
|
||||
}
|
||||
item.image = UIImage(systemName: newVisibility.imageName)
|
||||
|
@ -260,7 +257,7 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
|
||||
private func localOnlyChanged(_ localOnly: Bool) {
|
||||
for toolbar in [mainToolbar, inputAccessoryToolbar] {
|
||||
guard let item = toolbar?.items?.first(where: { $0.tag == LOCAL_ONLY_BAR_BUTTON_TAG }) else {
|
||||
guard let item = toolbar?.items?.first(where: { $0.tag == ViewTags.composeLocalOnlyBarButton }) else {
|
||||
continue
|
||||
}
|
||||
if localOnly {
|
||||
|
|
|
@ -46,7 +46,9 @@ class MainSplitViewController: UISplitViewController {
|
|||
setViewController(sidebar, for: .primary)
|
||||
primaryBackgroundStyle = .sidebar
|
||||
|
||||
setViewController(EnhancedNavigationViewController(), for: .secondary)
|
||||
let secondaryNav = EnhancedNavigationViewController()
|
||||
secondaryNav.useBrowserStyleNavigation = true
|
||||
setViewController(secondaryNav, for: .secondary)
|
||||
// don't unnecesarily construct a content VC unless the we're in actually split mode
|
||||
// when we change from compact -> split for the first time, the VC will be transferred anyways
|
||||
if traitCollection.horizontalSizeClass != .compact {
|
||||
|
|
|
@ -141,7 +141,9 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
|||
if let vc = vc as? UINavigationController {
|
||||
return vc
|
||||
} else {
|
||||
return EnhancedNavigationViewController(rootViewController: vc)
|
||||
let nav = EnhancedNavigationViewController(rootViewController: vc)
|
||||
nav.useBrowserStyleNavigation = true
|
||||
return nav
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,43 +9,69 @@
|
|||
import UIKit
|
||||
|
||||
class EnhancedNavigationViewController: UINavigationController {
|
||||
|
||||
|
||||
var useBrowserStyleNavigation = false
|
||||
|
||||
var poppedViewControllers = [UIViewController]()
|
||||
var skipResetPoppedOnNextPush = false
|
||||
|
||||
private var interactivePushTransition: InteractivePushTransition!
|
||||
|
||||
override var viewControllers: [UIViewController] {
|
||||
didSet {
|
||||
poppedViewControllers = []
|
||||
for vc in viewControllers {
|
||||
configureNavItem(vc.navigationItem)
|
||||
}
|
||||
updateTopNavItemState()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.interactivePushTransition = InteractivePushTransition(navigationController: self)
|
||||
|
||||
if let topViewController {
|
||||
configureNavItem(topViewController.navigationItem)
|
||||
updateTopNavItemState()
|
||||
}
|
||||
}
|
||||
|
||||
override func popViewController(animated: Bool) -> UIViewController? {
|
||||
if let popped = super.popViewController(animated: animated) {
|
||||
let popped = performAfterAnimating(block: {
|
||||
super.popViewController(animated: animated)
|
||||
}, after: {
|
||||
self.updateTopNavItemState()
|
||||
}, animated: animated)
|
||||
if let popped {
|
||||
poppedViewControllers.insert(popped, at: 0)
|
||||
return popped
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return popped
|
||||
}
|
||||
|
||||
override func popToRootViewController(animated: Bool) -> [UIViewController]? {
|
||||
if let popped = super.popToRootViewController(animated: animated) {
|
||||
let popped = performAfterAnimating(block: {
|
||||
super.popToRootViewController(animated: animated)
|
||||
}, after: {
|
||||
self.updateTopNavItemState()
|
||||
}, animated: animated)
|
||||
if let popped {
|
||||
poppedViewControllers = popped
|
||||
return popped
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return popped
|
||||
}
|
||||
|
||||
override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? {
|
||||
if let popped = super.popToViewController(viewController, animated: animated) {
|
||||
let popped = performAfterAnimating(block: {
|
||||
super.popToViewController(viewController, animated: animated)
|
||||
}, after: {
|
||||
self.updateTopNavItemState()
|
||||
}, animated: animated)
|
||||
if let popped {
|
||||
poppedViewControllers.insert(contentsOf: popped, at: 0)
|
||||
return popped
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return popped
|
||||
}
|
||||
|
||||
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
|
||||
|
@ -54,7 +80,43 @@ class EnhancedNavigationViewController: UINavigationController {
|
|||
} else {
|
||||
self.poppedViewControllers = []
|
||||
}
|
||||
|
||||
configureNavItem(viewController.navigationItem)
|
||||
|
||||
super.pushViewController(viewController, animated: animated)
|
||||
|
||||
updateTopNavItemState()
|
||||
}
|
||||
|
||||
func pushPoppedViewController() {
|
||||
guard !poppedViewControllers.isEmpty else {
|
||||
return
|
||||
}
|
||||
skipResetPoppedOnNextPush = true
|
||||
pushViewController(poppedViewControllers.removeFirst(), animated: true)
|
||||
}
|
||||
|
||||
func pushToPoppedViewController(_ target: UIViewController) {
|
||||
guard poppedViewControllers.contains(target) else {
|
||||
return
|
||||
}
|
||||
var toInsert: [UIViewController] = []
|
||||
while true {
|
||||
let vc = poppedViewControllers.removeFirst()
|
||||
if vc == target {
|
||||
break
|
||||
} else {
|
||||
toInsert.append(vc)
|
||||
}
|
||||
}
|
||||
// match the system behavior when popping multiple by animated-ly pushing the final destination one,
|
||||
// and then intersiting the intermediary ones before it, as if they'd all been pushed together
|
||||
performAfterAnimating(block: {
|
||||
pushViewController(target, animated: true)
|
||||
}, after: {
|
||||
self.viewControllers.insert(contentsOf: toInsert, at: self.viewControllers.count - 1)
|
||||
self.updateTopNavItemState()
|
||||
}, animated: true)
|
||||
}
|
||||
|
||||
func onWillShow() {
|
||||
|
@ -72,6 +134,92 @@ class EnhancedNavigationViewController: UINavigationController {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func configureNavItem(_ navItem: UINavigationItem) {
|
||||
guard useBrowserStyleNavigation,
|
||||
UIDevice.current.userInterfaceIdiom != .phone else {
|
||||
return
|
||||
}
|
||||
navItem.style = .browser
|
||||
navItem.hidesBackButton = true
|
||||
if let titleView = navItem.titleView,
|
||||
titleView.tag != ViewTags.navEmptyTitleView {
|
||||
// blergh, i don't like changing this out from under some other view controller
|
||||
// we use an empty view because otherwise the title label displays in addition to the new title view bar button item
|
||||
navItem.titleView = UIView()
|
||||
navItem.titleView?.tag = ViewTags.navEmptyTitleView
|
||||
// TODO: centerItemGroups don't animate out during nav transitions, the just (dis)appear abruptly
|
||||
navItem.centerItemGroups = [
|
||||
.fixedGroup(items: [UIBarButtonItem(customView: titleView)])
|
||||
]
|
||||
}
|
||||
let back = UIBarButtonItem(image: UIImage(systemName: "chevron.backward"), style: .plain, target: self, action: #selector(goBack))
|
||||
back.tag = ViewTags.navBackBarButton
|
||||
back.menu = UIMenu(children: [
|
||||
UIDeferredMenuElement({ [unowned self] completion in
|
||||
completion(self.viewControllers.dropLast(1).reversed().map { vc in
|
||||
UIAction(title: vc.navigationItem.title ?? "Back") { [weak self] _ in
|
||||
_ = self?.popToViewController(vc, animated: true)
|
||||
}
|
||||
})
|
||||
})
|
||||
])
|
||||
let forward = UIBarButtonItem(image: UIImage(systemName: "chevron.forward"), style: .plain, target: self, action: #selector(goForward))
|
||||
forward.tag = ViewTags.navForwardBarButton
|
||||
forward.menu = UIMenu(children: [
|
||||
UIDeferredMenuElement.uncached({ [unowned self] completion in
|
||||
completion(poppedViewControllers.map { vc in
|
||||
UIAction(title: vc.navigationItem.title ?? "Forward") { [weak self] _ in
|
||||
self?.pushToPoppedViewController(vc)
|
||||
}
|
||||
})
|
||||
})
|
||||
])
|
||||
navItem.leadingItemGroups = [
|
||||
.fixedGroup(items: [
|
||||
back,
|
||||
forward,
|
||||
])
|
||||
]
|
||||
}
|
||||
|
||||
private func updateTopNavItemState() {
|
||||
guard useBrowserStyleNavigation,
|
||||
UIDevice.current.userInterfaceIdiom != .phone,
|
||||
let vc = topViewController,
|
||||
let group = vc.navigationItem.leadingItemGroups.first,
|
||||
group.barButtonItems.count == 2,
|
||||
group.barButtonItems[0].tag == ViewTags.navBackBarButton,
|
||||
group.barButtonItems[1].tag == ViewTags.navForwardBarButton else {
|
||||
return
|
||||
}
|
||||
group.barButtonItems[0].isEnabled = viewControllers.count > 1
|
||||
group.barButtonItems[1].isEnabled = !poppedViewControllers.isEmpty
|
||||
}
|
||||
|
||||
private func performAfterAnimating<R>(block: () -> R, after: @escaping () -> Void, animated: Bool) -> R {
|
||||
if animated {
|
||||
CATransaction.begin()
|
||||
let result = block()
|
||||
CATransaction.setCompletionBlock {
|
||||
after()
|
||||
}
|
||||
CATransaction.commit()
|
||||
return result
|
||||
} else {
|
||||
let result = block()
|
||||
after()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func goBack() {
|
||||
_ = popViewController(animated: true)
|
||||
}
|
||||
|
||||
@objc private func goForward() {
|
||||
pushPoppedViewController()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,12 @@ class SegmentedPageViewController: UIPageViewController, UIPageViewControllerDel
|
|||
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
|
||||
|
||||
self.delegate = self
|
||||
|
||||
// this needs to happen in init because EnhancedNavigationViewController expects to be able to look at the titleView
|
||||
// before the view has necessarily loaded
|
||||
segmentedControl = UISegmentedControl(items: titles)
|
||||
segmentedControl.addTarget(self, action: #selector(segmentedControlChanged), for: .valueChanged)
|
||||
navigationItem.titleView = segmentedControl
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
@ -35,10 +41,6 @@ class SegmentedPageViewController: UIPageViewController, UIPageViewControllerDel
|
|||
|
||||
view.backgroundColor = .systemBackground
|
||||
|
||||
segmentedControl = UISegmentedControl(items: titles)
|
||||
segmentedControl.addTarget(self, action: #selector(segmentedControlChanged), for: .valueChanged)
|
||||
navigationItem.titleView = segmentedControl
|
||||
|
||||
segmentedControl.selectedSegmentIndex = 0
|
||||
selectPage(at: 0, animated: false)
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// ViewTags.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 6/8/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct ViewTags {
|
||||
private init() {}
|
||||
|
||||
static let composeVisibilityBarButton = 42001
|
||||
static let composeLocalOnlyBarButton = 42002
|
||||
static let navBackBarButton = 42003
|
||||
static let navForwardBarButton = 42004
|
||||
static let navEmptyTitleView = 42005
|
||||
}
|
Loading…
Reference in New Issue