Fast account switching on iPad

This commit is contained in:
Shadowfacts 2022-05-01 11:53:12 -04:00
parent a22059a1a1
commit 514e569bd5
8 changed files with 144 additions and 51 deletions

View File

@ -9,6 +9,8 @@
import UIKit import UIKit
protocol FastAccountSwitcherViewControllerDelegate: AnyObject { protocol FastAccountSwitcherViewControllerDelegate: AnyObject {
func fastAccountSwitcherAddToViewHierarchy(_ fastAccountSwitcher: FastAccountSwitcherViewController)
/// - Parameter point: In the coordinate space of the view to which the pan gesture recognizer is attached.
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool
} }
@ -20,11 +22,13 @@ class FastAccountSwitcherViewController: UIViewController {
@IBOutlet weak var blurContentView: UIView! @IBOutlet weak var blurContentView: UIView!
@IBOutlet weak var accountsStack: UIStackView! @IBOutlet weak var accountsStack: UIStackView!
private var accountViews: [FastSwitchingAccountView] = [] private(set) var accountViews: [FastSwitchingAccountView] = []
private var lastSelectedAccountViewIndex: Int? private var lastSelectedAccountViewIndex: Int?
private var selectionChangedFeedbackGenerator: UIImpactFeedbackGenerator? private var selectionChangedFeedbackGenerator: UIImpactFeedbackGenerator?
private var touchBeganFeedbackWorkItem: DispatchWorkItem? private var touchBeganFeedbackWorkItem: DispatchWorkItem?
var itemOrientation: ItemOrientation = .iconsTrailing
init() { init() {
super.init(nibName: "FastAccountSwitcherViewController", bundle: .main) super.init(nibName: "FastAccountSwitcherViewController", bundle: .main)
} }
@ -51,6 +55,15 @@ class FastAccountSwitcherViewController: UIViewController {
func show() { func show() {
createAccountViews() createAccountViews()
// add after creating account views so that the presenter can align based on them
delegate?.fastAccountSwitcherAddToViewHierarchy(self)
switch itemOrientation {
case .iconsLeading:
accountsStack.alignment = .leading
case .iconsTrailing:
accountsStack.alignment = .trailing
}
view.isHidden = false view.isHidden = false
@ -87,22 +100,27 @@ class FastAccountSwitcherViewController: UIViewController {
} }
func hide(completion: (() -> Void)? = nil) { func hide(completion: (() -> Void)? = nil) {
guard view.superview != nil else {
return
}
lastSelectedAccountViewIndex = nil lastSelectedAccountViewIndex = nil
selectionChangedFeedbackGenerator = nil selectionChangedFeedbackGenerator = nil
UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseInOut) { UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseInOut) {
self.view.alpha = 0 self.view.alpha = 0
} completion: { (_) in } completion: { (_) in
// todo: probably remove these two lines
self.view.alpha = 1 self.view.alpha = 1
self.view.isHidden = true self.view.isHidden = true
completion?() completion?()
self.view.removeFromSuperview()
} }
} }
private func createAccountViews() { private func createAccountViews() {
accountsStack.arrangedSubviews.forEach { $0.removeFromSuperview() } accountsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
let addAccountPlaceholder = FastSwitchingAccountView() let addAccountPlaceholder = FastSwitchingAccountView(orientation: itemOrientation)
accountsStack.addArrangedSubview(addAccountPlaceholder) accountsStack.addArrangedSubview(addAccountPlaceholder)
accountViews = [ accountViews = [
@ -110,7 +128,7 @@ class FastAccountSwitcherViewController: UIViewController {
] ]
for account in LocalData.shared.accounts { for account in LocalData.shared.accounts {
let accountView = FastSwitchingAccountView(account: account) let accountView = FastSwitchingAccountView(account: account, orientation: itemOrientation)
accountView.isCurrent = account.id == LocalData.shared.mostRecentAccountID accountView.isCurrent = account.id == LocalData.shared.mostRecentAccountID
accountsStack.addArrangedSubview(accountView) accountsStack.addArrangedSubview(accountView)
accountViews.append(accountView) accountViews.append(accountView)
@ -172,10 +190,9 @@ class FastAccountSwitcherViewController: UIViewController {
handleGestureMoved(to: location) handleGestureMoved(to: location)
case .ended: case .ended:
let location = recognizer.location(in: view)
if let index = lastSelectedAccountViewIndex { if let index = lastSelectedAccountViewIndex {
switchAccount(newIndex: index) switchAccount(newIndex: index)
} else if !(delegate?.fastAccountSwitcher(self, triggerZoneContains: location) ?? false) { } else if !(delegate?.fastAccountSwitcher(self, triggerZoneContains: recognizer.location(in: recognizer.view)) ?? false) {
hide() hide()
} }
@ -257,9 +274,16 @@ class FastAccountSwitcherViewController: UIViewController {
} }
extension FastAccountSwitcherViewController {
enum ItemOrientation {
case iconsLeading
case iconsTrailing
}
}
extension FastAccountSwitcherViewController: UIGestureRecognizerDelegate { extension FastAccountSwitcherViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
let point = gestureRecognizer.location(in: view) let point = gestureRecognizer.location(in: gestureRecognizer.view)
return delegate?.fastAccountSwitcher(self, triggerZoneContains: point) ?? false return delegate?.fastAccountSwitcher(self, triggerZoneContains: point) ?? false
} }
} }

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17504.1"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -12,7 +12,6 @@
<connections> <connections>
<outlet property="accountsStack" destination="lYU-Bb-3Wi" id="Dxs-ta-ORu"/> <outlet property="accountsStack" destination="lYU-Bb-3Wi" id="Dxs-ta-ORu"/>
<outlet property="blurContentView" destination="1Gd-Da-Vab" id="JqT-uq-1o2"/> <outlet property="blurContentView" destination="1Gd-Da-Vab" id="JqT-uq-1o2"/>
<outlet property="dimmingView" destination="Lul-oI-bZ7" id="JhP-ZX-8fb"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/> <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections> </connections>
</placeholder> </placeholder>
@ -21,10 +20,6 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<view alpha="0.25" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Lul-oI-bZ7">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5fd-Ni-Owc"> <visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5fd-Ni-Owc">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="1Gd-Da-Vab"> <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="1Gd-Da-Vab">
@ -38,7 +33,7 @@
<constraints> <constraints>
<constraint firstItem="lYU-Bb-3Wi" firstAttribute="top" secondItem="1Gd-Da-Vab" secondAttribute="topMargin" placeholder="YES" id="KQs-d5-U3f"/> <constraint firstItem="lYU-Bb-3Wi" firstAttribute="top" secondItem="1Gd-Da-Vab" secondAttribute="topMargin" placeholder="YES" id="KQs-d5-U3f"/>
<constraint firstAttribute="trailing" secondItem="lYU-Bb-3Wi" secondAttribute="trailingMargin" constant="8" id="UZh-xR-XVt"/> <constraint firstAttribute="trailing" secondItem="lYU-Bb-3Wi" secondAttribute="trailingMargin" constant="8" id="UZh-xR-XVt"/>
<constraint firstAttribute="bottomMargin" secondItem="lYU-Bb-3Wi" secondAttribute="bottom" id="j6f-r5-NNI"/> <constraint firstAttribute="bottomMargin" secondItem="lYU-Bb-3Wi" secondAttribute="bottom" placeholder="YES" id="j6f-r5-NNI"/>
<constraint firstItem="lYU-Bb-3Wi" firstAttribute="leading" secondItem="1Gd-Da-Vab" secondAttribute="leading" constant="8" id="sae-ga-MGE"/> <constraint firstItem="lYU-Bb-3Wi" firstAttribute="leading" secondItem="1Gd-Da-Vab" secondAttribute="leading" constant="8" id="sae-ga-MGE"/>
</constraints> </constraints>
</view> </view>
@ -48,20 +43,11 @@
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/> <viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
<gestureRecognizers/> <gestureRecognizers/>
<constraints> <constraints>
<constraint firstAttribute="trailing" secondItem="Lul-oI-bZ7" secondAttribute="trailing" id="9Fp-IG-O9W"/>
<constraint firstAttribute="trailing" secondItem="5fd-Ni-Owc" secondAttribute="trailing" id="c27-P9-lLK"/> <constraint firstAttribute="trailing" secondItem="5fd-Ni-Owc" secondAttribute="trailing" id="c27-P9-lLK"/>
<constraint firstAttribute="bottom" secondItem="Lul-oI-bZ7" secondAttribute="bottom" id="o6y-tG-MwH"/>
<constraint firstItem="5fd-Ni-Owc" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="phf-PC-bdH"/> <constraint firstItem="5fd-Ni-Owc" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="phf-PC-bdH"/>
<constraint firstItem="5fd-Ni-Owc" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="rz7-cQ-PIC"/> <constraint firstItem="5fd-Ni-Owc" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="rz7-cQ-PIC"/>
<constraint firstAttribute="bottom" secondItem="5fd-Ni-Owc" secondAttribute="bottom" id="sHl-iD-kGi"/> <constraint firstAttribute="bottom" secondItem="5fd-Ni-Owc" secondAttribute="bottom" id="sHl-iD-kGi"/>
<constraint firstItem="Lul-oI-bZ7" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="tfE-Xr-YBo"/>
<constraint firstItem="Lul-oI-bZ7" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="ua7-DO-kdp"/>
</constraints> </constraints>
<variation key="default">
<mask key="subviews">
<exclude reference="Lul-oI-bZ7"/>
</mask>
</variation>
<point key="canvasLocation" x="140.57971014492756" y="144.64285714285714"/> <point key="canvasLocation" x="140.57971014492756" y="144.64285714285714"/>
</view> </view>
</objects> </objects>

View File

@ -35,19 +35,23 @@ class FastSwitchingAccountView: UIView {
} }
} }
private let orientation: FastAccountSwitcherViewController.ItemOrientation
private let usernameLabel = UILabel() private let usernameLabel = UILabel()
private let instanceLabel = UILabel() private let instanceLabel = UILabel()
private let avatarImageView = UIImageView() private let avatarImageView = UIImageView()
private var avatarRequest: ImageCache.Request? private var avatarRequest: ImageCache.Request?
init(account: LocalData.UserAccountInfo) { init(account: LocalData.UserAccountInfo, orientation: FastAccountSwitcherViewController.ItemOrientation) {
self.orientation = orientation
super.init(frame: .zero) super.init(frame: .zero)
commonInit() commonInit()
setupAccount(account: account) setupAccount(account: account)
} }
init() { init(orientation: FastAccountSwitcherViewController.ItemOrientation) {
self.orientation = orientation
super.init(frame: .zero) super.init(frame: .zero)
commonInit() commonInit()
setupPlaceholder() setupPlaceholder()
@ -70,7 +74,6 @@ class FastSwitchingAccountView: UIView {
]) ])
stackView.translatesAutoresizingMaskIntoConstraints = false stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical stackView.axis = .vertical
stackView.alignment = .trailing
addSubview(stackView) addSubview(stackView)
avatarImageView.translatesAutoresizingMaskIntoConstraints = false avatarImageView.translatesAutoresizingMaskIntoConstraints = false
@ -83,15 +86,30 @@ class FastSwitchingAccountView: UIView {
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
avatarImageView.topAnchor.constraint(equalTo: topAnchor, constant: 8), avatarImageView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
avatarImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8), avatarImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
avatarImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
avatarImageView.widthAnchor.constraint(equalToConstant: 40), avatarImageView.widthAnchor.constraint(equalToConstant: 40),
avatarImageView.heightAnchor.constraint(equalTo: avatarImageView.widthAnchor), avatarImageView.heightAnchor.constraint(equalTo: avatarImageView.widthAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
stackView.trailingAnchor.constraint(equalTo: avatarImageView.leadingAnchor, constant: -8),
stackView.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor), stackView.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor),
]) ])
switch orientation {
case .iconsLeading:
stackView.alignment = .leading
NSLayoutConstraint.activate([
avatarImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 8),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
])
case .iconsTrailing:
stackView.alignment = .trailing
NSLayoutConstraint.activate([
avatarImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
stackView.trailingAnchor.constraint(equalTo: avatarImageView.leadingAnchor, constant: -8),
])
}
updateLabelColors() updateLabelColors()
} }

View File

@ -40,6 +40,14 @@ class MainSidebarMyProfileCollectionViewCell: UICollectionViewListCell {
config.text = item.title config.text = item.title
config.image = UIImage(systemName: item.imageName!) config.image = UIImage(systemName: item.imageName!)
self.contentConfiguration = config self.contentConfiguration = config
if UIDevice.current.userInterfaceIdiom != .mac {
let indicator = FastAccountSwitcherIndicatorView()
// need to explicitly set the frame to get it vertically centered
indicator.frame = CGRect(origin: .zero, size: indicator.intrinsicContentSize)
accessories = [
.customView(configuration: .init(customView: indicator, placement: .trailing()))
]
}
let mastodonController = MastodonController.getForAccount(account) let mastodonController = MastodonController.getForAccount(account)
guard let account = try? await mastodonController.getOwnAccount(), guard let account = try? await mastodonController.getOwnAccount(),

View File

@ -330,6 +330,14 @@ class MainSidebarViewController: UIViewController {
} }
} }
func myProfileCell() -> UICollectionViewCell? {
guard let indexPath = dataSource.indexPath(for: .tab(.myProfile)),
let item = collectionView.cellForItem(at: indexPath) else {
return nil
}
return item
}
} }
extension MainSidebarViewController { extension MainSidebarViewController {
@ -514,6 +522,11 @@ extension MainSidebarViewController: UICollectionViewDelegate {
let activity = userActivityForItem(item) else { let activity = userActivityForItem(item) else {
return nil return nil
} }
if case .tab(.myProfile) = item,
// only disable context menu on long-press, to allow fast account switching
collectionView.contextMenuInteraction?.menuAppearance == .rich {
return nil
}
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (_) in return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (_) in
return UIMenu(children: [ return UIMenu(children: [
UIWindowScene.ActivationAction({ action in UIWindowScene.ActivationAction({ action in
@ -530,6 +543,9 @@ extension MainSidebarViewController: UICollectionViewDragDelegate {
let activity = userActivityForItem(item) else { let activity = userActivityForItem(item) else {
return [] return []
} }
if case .tab(.myProfile) = item {
return []
}
let provider = NSItemProvider(object: activity) let provider = NSItemProvider(object: activity)
return [UIDragItem(itemProvider: provider)] return [UIDragItem(itemProvider: provider)]

View File

@ -13,6 +13,7 @@ class MainSplitViewController: UISplitViewController {
weak var mastodonController: MastodonController! weak var mastodonController: MastodonController!
private var sidebar: MainSidebarViewController! private var sidebar: MainSidebarViewController!
private var fastAccountSwitcher: FastAccountSwitcherViewController?
// Keep track of navigation stacks per-item so that we can only ever use a single navigation controller // Keep track of navigation stacks per-item so that we can only ever use a single navigation controller
private var navigationStacks: [MainSidebarViewController.Item: [UIViewController]] = [:] private var navigationStacks: [MainSidebarViewController.Item: [UIViewController]] = [:]
@ -23,14 +24,6 @@ class MainSplitViewController: UISplitViewController {
viewController(for: .secondary) as? UINavigationController viewController(for: .secondary) as? UINavigationController
} }
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .portrait
} else {
return .all
}
}
init(mastodonController: MastodonController) { init(mastodonController: MastodonController) {
self.mastodonController = mastodonController self.mastodonController = mastodonController
@ -60,6 +53,18 @@ class MainSplitViewController: UISplitViewController {
select(item: .tab(.timelines)) select(item: .tab(.timelines))
} }
if UIDevice.current.userInterfaceIdiom != .mac {
let switcher = FastAccountSwitcherViewController()
fastAccountSwitcher = switcher
switcher.itemOrientation = .iconsLeading
switcher.view.translatesAutoresizingMaskIntoConstraints = false
switcher.delegate = self
sidebar.view.addGestureRecognizer(switcher.createSwitcherGesture())
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(sidebarTapped))
tapRecognizer.cancelsTouchesInView = false
sidebar.view.addGestureRecognizer(tapRecognizer)
}
tabBarViewController = MainTabBarViewController(mastodonController: mastodonController) tabBarViewController = MainTabBarViewController(mastodonController: mastodonController)
setViewController(tabBarViewController, for: .compact) setViewController(tabBarViewController, for: .compact)
@ -100,6 +105,10 @@ class MainSplitViewController: UISplitViewController {
sidebar.select(item: item, animated: false) sidebar.select(item: item, animated: false)
select(item: item) select(item: item)
} }
@objc private func sidebarTapped() {
fastAccountSwitcher?.hide()
}
} }
@ -459,3 +468,27 @@ extension MainSplitViewController: BackgroundableViewController {
} }
} }
} }
extension MainSplitViewController: FastAccountSwitcherViewControllerDelegate {
func fastAccountSwitcherAddToViewHierarchy(_ fastAccountSwitcher: FastAccountSwitcherViewController) {
view.addSubview(fastAccountSwitcher.view)
let currentAccount = fastAccountSwitcher.accountViews.first(where: \.isCurrent)!
let myProfileCell = sidebar.myProfileCell()!
NSLayoutConstraint.activate([
currentAccount.centerYAnchor.constraint(equalTo: myProfileCell.centerYAnchor),
fastAccountSwitcher.view.leadingAnchor.constraint(equalTo: sidebar.view.trailingAnchor),
fastAccountSwitcher.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
fastAccountSwitcher.view.topAnchor.constraint(equalTo: view.topAnchor),
fastAccountSwitcher.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool {
guard !isCollapsed,
let cell = sidebar.myProfileCell() else {
return false
}
let cellRect = cell.convert(cell.bounds, to: sidebar.view)
return cellRect.contains(point)
}
}

View File

@ -60,13 +60,6 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
fastAccountSwitcher = FastAccountSwitcherViewController() fastAccountSwitcher = FastAccountSwitcherViewController()
fastAccountSwitcher.delegate = self fastAccountSwitcher.delegate = self
fastAccountSwitcher.view.translatesAutoresizingMaskIntoConstraints = false fastAccountSwitcher.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(fastAccountSwitcher.view)
NSLayoutConstraint.activate([
fastAccountSwitcher.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
fastAccountSwitcher.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
fastAccountSwitcher.view.topAnchor.constraint(equalTo: view.topAnchor),
fastAccountSwitcher.view.bottomAnchor.constraint(equalTo: tabBar.topAnchor),
])
tabBar.addGestureRecognizer(fastAccountSwitcher.createSwitcherGesture()) tabBar.addGestureRecognizer(fastAccountSwitcher.createSwitcherGesture())
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tabBarTapped)) let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tabBarTapped))
@ -77,18 +70,17 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
fastSwitcherIndicator = FastAccountSwitcherIndicatorView() fastSwitcherIndicator = FastAccountSwitcherIndicatorView()
fastSwitcherIndicator.translatesAutoresizingMaskIntoConstraints = false fastSwitcherIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(fastSwitcherIndicator) view.addSubview(fastSwitcherIndicator)
NSLayoutConstraint.activate([
fastSwitcherIndicator.widthAnchor.constraint(equalToConstant: 10),
fastSwitcherIndicator.heightAnchor.constraint(equalToConstant: 12),
])
} }
tabBar.isSpringLoaded = true tabBar.isSpringLoaded = true
} }
override func viewWillAppear(_ animated: Bool) { override func viewDidLayoutSubviews() {
super.viewWillAppear(animated) super.viewDidLayoutSubviews()
// i hate that we have to do this so often :S
// but doing it only in viewWillAppear makes it not appear initially
// doing it in viewWillAppear inside a DispatchQueue.main.async works initially but then it disappears when long-pressed
repositionFastSwitcherIndicator() repositionFastSwitcherIndicator()
} }
@ -201,11 +193,23 @@ extension MainTabBarViewController {
} }
extension MainTabBarViewController: FastAccountSwitcherViewControllerDelegate { extension MainTabBarViewController: FastAccountSwitcherViewControllerDelegate {
func fastAccountSwitcherAddToViewHierarchy(_ fastAccountSwitcher: FastAccountSwitcherViewController) {
view.addSubview(fastAccountSwitcher.view)
NSLayoutConstraint.activate([
fastAccountSwitcher.accountsStack.bottomAnchor.constraint(equalTo: fastAccountSwitcher.view.bottomAnchor),
fastAccountSwitcher.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
fastAccountSwitcher.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
fastAccountSwitcher.view.topAnchor.constraint(equalTo: view.topAnchor),
fastAccountSwitcher.view.bottomAnchor.constraint(equalTo: tabBar.topAnchor),
])
}
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool { func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool {
guard let myProfileButton = findMyProfileTabBarButton() else { guard let myProfileButton = findMyProfileTabBarButton() else {
return false return false
} }
let locationInButton = myProfileButton.convert(point, from: fastAccountSwitcher.view) let locationInButton = myProfileButton.convert(point, from: tabBar)
return myProfileButton.bounds.contains(locationInButton) return myProfileButton.bounds.contains(locationInButton)
} }
} }

View File

@ -10,6 +10,10 @@ import UIKit
class FastAccountSwitcherIndicatorView: UIView { class FastAccountSwitcherIndicatorView: UIView {
override var intrinsicContentSize: CGSize {
CGSize(width: 10, height: 12)
}
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)