Remove catchNSException and instead swizzle things in Objective C
Apparently any Swift stack frames between throwing/catching an NSException can break things.
This commit is contained in:
parent
57e21176f0
commit
84ed9e92ee
@ -113,7 +113,6 @@
|
||||
D63CC702290EC0B8000E19DE /* Sentry in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = D63CC701290EC0B8000E19DE /* Sentry */; };
|
||||
D63CC70C2910AADB000E19DE /* TuskerSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC70B2910AADB000E19DE /* TuskerSceneDelegate.swift */; };
|
||||
D63CC7102911F1E4000E19DE /* UIScrollView+Top.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC70F2911F1E4000E19DE /* UIScrollView+Top.swift */; };
|
||||
D63CC7122911F57C000E19DE /* StatusBarTappableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC7112911F57C000E19DE /* StatusBarTappableViewController.swift */; };
|
||||
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63D8DF32850FE7A008D95E1 /* ViewTags.swift */; };
|
||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */ = {isa = PBXBuildFile; fileRef = D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */; };
|
||||
D6412B0324AFF6A600F5412E /* TabBarScrollableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6412B0224AFF6A600F5412E /* TabBarScrollableViewController.swift */; };
|
||||
@ -162,6 +161,7 @@
|
||||
D65B4B8B297879E900DABDFB /* AccountFollowsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B4B8A297879E900DABDFB /* AccountFollowsViewController.swift */; };
|
||||
D65C6BF525478A9C00A6E89C /* BackgroundableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65C6BF425478A9C00A6E89C /* BackgroundableViewController.swift */; };
|
||||
D65F613423AEAB6600F3CFD3 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65F613323AEAB6600F3CFD3 /* OnboardingTests.swift */; };
|
||||
D6606A102D761BA3004BBEF4 /* Swizzler.m in Sources */ = {isa = PBXBuildFile; fileRef = D6606A0F2D761BA3004BBEF4 /* Swizzler.m */; };
|
||||
D6620ACE2511A0ED00312CA0 /* StatusStateResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */; };
|
||||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; };
|
||||
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; };
|
||||
@ -542,7 +542,6 @@
|
||||
D63CC703290EC472000E19DE /* Dist.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Dist.xcconfig; sourceTree = "<group>"; };
|
||||
D63CC70B2910AADB000E19DE /* TuskerSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerSceneDelegate.swift; sourceTree = "<group>"; };
|
||||
D63CC70F2911F1E4000E19DE /* UIScrollView+Top.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Top.swift"; sourceTree = "<group>"; };
|
||||
D63CC7112911F57C000E19DE /* StatusBarTappableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarTappableViewController.swift; sourceTree = "<group>"; };
|
||||
D63D8DF32850FE7A008D95E1 /* ViewTags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTags.swift; sourceTree = "<group>"; };
|
||||
D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = DomainBlocks.plist; sourceTree = "<group>"; };
|
||||
D6412B0224AFF6A600F5412E /* TabBarScrollableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarScrollableViewController.swift; sourceTree = "<group>"; };
|
||||
@ -595,6 +594,8 @@
|
||||
D65F613323AEAB6600F3CFD3 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = "<group>"; };
|
||||
D65F613523AFD65900F3CFD3 /* Ambassador.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Ambassador.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D65F613723AFD65D00F3CFD3 /* Embassy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Embassy.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D6606A0E2D761BA3004BBEF4 /* Swizzler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Swizzler.h; sourceTree = "<group>"; };
|
||||
D6606A0F2D761BA3004BBEF4 /* Swizzler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Swizzler.m; sourceTree = "<group>"; };
|
||||
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusStateResolver.swift; sourceTree = "<group>"; };
|
||||
D663626B21361C6700C9CBA2 /* Account+Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Preferences.swift"; sourceTree = "<group>"; };
|
||||
D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppShortcutItems.swift; sourceTree = "<group>"; };
|
||||
@ -1542,7 +1543,6 @@
|
||||
D6B81F3B2560365300F6E31D /* RefreshableViewController.swift */,
|
||||
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */,
|
||||
D691771429A6FCAB0054D7EF /* StateRestorableViewController.swift */,
|
||||
D63CC7112911F57C000E19DE /* StatusBarTappableViewController.swift */,
|
||||
D6412B0224AFF6A600F5412E /* TabBarScrollableViewController.swift */,
|
||||
D6B22A0E2560D52D004D82EF /* TabbedPageViewController.swift */,
|
||||
D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */,
|
||||
@ -1606,6 +1606,8 @@
|
||||
D60088F02980D938005B4D00 /* Tusker.storekit */,
|
||||
D691296D2BA75ACF005C58ED /* PrivacyInfo.xcprivacy */,
|
||||
D671A6BE299DA96100A81FEA /* Tusker-Bridging-Header.h */,
|
||||
D6606A0E2D761BA3004BBEF4 /* Swizzler.h */,
|
||||
D6606A0F2D761BA3004BBEF4 /* Swizzler.m */,
|
||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||
D6EEDE922C3CF21800E10E51 /* AudioSessionCoordinator.swift */,
|
||||
D6D79F582A13293200AB2315 /* BackgroundManager.swift */,
|
||||
@ -2157,7 +2159,6 @@
|
||||
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
|
||||
D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */,
|
||||
D61F75942936F0DA00C0B37F /* FollowedHashtag.swift in Sources */,
|
||||
D63CC7122911F57C000E19DE /* StatusBarTappableViewController.swift in Sources */,
|
||||
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */,
|
||||
D6B22A0F2560D52D004D82EF /* TabbedPageViewController.swift in Sources */,
|
||||
D6895DC228D65274006341DA /* CustomAlertController.swift in Sources */,
|
||||
@ -2215,6 +2216,7 @@
|
||||
D61DC84B28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift in Sources */,
|
||||
D61A45E828DF477D002BE511 /* LoadingCollectionViewCell.swift in Sources */,
|
||||
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */,
|
||||
D6606A102D761BA3004BBEF4 /* Swizzler.m in Sources */,
|
||||
D68A76EC295369A8001DA1B3 /* AboutView.swift in Sources */,
|
||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
||||
D68245122BCA1F4000AFB38B /* NotificationLoadingViewController.swift in Sources */,
|
||||
|
@ -30,7 +30,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
configureSentry()
|
||||
#endif
|
||||
#if !os(visionOS)
|
||||
swizzleStatusBar()
|
||||
let swizzledStatusBar = Swizzler.swizzleStatusBarManager(exceptionHandler: {
|
||||
SentrySDK.capture(exception: $0)
|
||||
})
|
||||
if !swizzledStatusBar {
|
||||
Logging.general.error("Unable to swizzle status bar manager")
|
||||
}
|
||||
swizzlePresentationController()
|
||||
#endif
|
||||
|
||||
@ -202,35 +207,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
}
|
||||
|
||||
#if !os(visionOS)
|
||||
private func swizzleStatusBar() {
|
||||
let selector = Selector(("handleTapAction:"))
|
||||
var originalIMP: IMP?
|
||||
let imp = imp_implementationWithBlock({ (self: UIStatusBarManager, sender: AnyObject) in
|
||||
let original = unsafeBitCast(originalIMP!, to: (@convention(c) (UIStatusBarManager, Selector, AnyObject) -> Void).self)
|
||||
let exception = catchNSException {
|
||||
guard let windowScene = self.perform(Selector(("windowScene"))).takeUnretainedValue() as? UIWindowScene,
|
||||
let xPosition = sender.value(forKey: "xPosition") as? CGFloat,
|
||||
let delegate = windowScene.delegate as? TuskerSceneDelegate else {
|
||||
original(self, selector, sender)
|
||||
return
|
||||
}
|
||||
switch delegate.handleStatusBarTapped(xPosition: xPosition) {
|
||||
case .stop:
|
||||
return
|
||||
case .continue:
|
||||
original(self, selector, sender)
|
||||
}
|
||||
}
|
||||
if let exception {
|
||||
SentrySDK.capture(exception: exception)
|
||||
}
|
||||
} as @convention(block) (UIStatusBarManager, AnyObject) -> Void)
|
||||
originalIMP = class_replaceMethod(UIStatusBarManager.self, selector, imp, "v@:@")
|
||||
if originalIMP == nil {
|
||||
Logging.general.error("Unable to swizzle status bar manager")
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS, obsoleted: 17.0)
|
||||
private func swizzlePresentationController() {
|
||||
guard #unavailable(iOS 17.0) else {
|
||||
|
@ -130,4 +130,8 @@ class AuxiliarySceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDel
|
||||
@objc private func themePrefChanged() {
|
||||
applyAppearancePreferences()
|
||||
}
|
||||
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
doHandleStatusBarTapped(at: xPosition)
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,10 @@ class ComposeSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDeleg
|
||||
applyAppearancePreferences()
|
||||
}
|
||||
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
doHandleStatusBarTapped(at: xPosition)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeSceneDelegate: ComposeHostingControllerDelegate {
|
||||
|
@ -310,6 +310,10 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
|
||||
preferencesVC?.navigationState.showNotificationPreferences = true
|
||||
}
|
||||
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
doHandleStatusBarTapped(at: xPosition)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MainSceneDelegate: OnboardingViewControllerDelegate {
|
||||
|
@ -12,21 +12,16 @@ import Sentry
|
||||
#endif
|
||||
|
||||
@MainActor
|
||||
protocol TuskerSceneDelegate: UISceneDelegate {
|
||||
protocol TuskerSceneDelegate: UISceneDelegate, StatusBarTapHandling {
|
||||
var window: UIWindow? { get }
|
||||
var rootViewController: TuskerRootViewController? { get }
|
||||
}
|
||||
|
||||
enum StatusBarTapActionResult {
|
||||
case `continue`
|
||||
case stop
|
||||
}
|
||||
|
||||
extension TuskerSceneDelegate {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
func doHandleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
if let rootViewController {
|
||||
let converted = rootViewController.view.convert(CGPoint(x: xPosition, y: 0), from: nil)
|
||||
return rootViewController.handleStatusBarTapped(xPosition: converted.x)
|
||||
return rootViewController.handleStatusBarTapped(at: converted.x)
|
||||
}
|
||||
return .continue
|
||||
}
|
||||
@ -41,16 +36,11 @@ extension TuskerSceneDelegate {
|
||||
if #available(iOS 17.0, *) {
|
||||
window.traitOverrides.pureBlackDarkMode = Preferences.shared.pureBlackDarkMode
|
||||
} else {
|
||||
let exception = catchNSException {
|
||||
let key = ["Controller", "Presentation", "root", "_"].reversed().joined()
|
||||
if let rootPresentationController = window.value(forKey: key) as? UIPresentationController {
|
||||
rootPresentationController.overrideTraitCollection = UITraitCollection(pureBlackDarkMode: Preferences.shared.pureBlackDarkMode)
|
||||
}
|
||||
}
|
||||
if let exception {
|
||||
SentrySDK.capture(exception: exception)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -297,8 +297,8 @@ extension AccountFollowsListViewController: MenuActionProvider {
|
||||
extension AccountFollowsListViewController: ToastableViewController {
|
||||
}
|
||||
|
||||
extension AccountFollowsListViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension AccountFollowsListViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
collectionView.scrollToTop()
|
||||
return .stop
|
||||
}
|
||||
|
@ -132,8 +132,8 @@ extension AccountListViewController: TuskerNavigationDelegate {
|
||||
extension AccountListViewController: MenuActionProvider {
|
||||
}
|
||||
|
||||
extension AccountListViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension AccountListViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
collectionView.scrollToTop()
|
||||
return .stop
|
||||
}
|
||||
|
@ -462,8 +462,8 @@ extension ConversationCollectionViewController: TabBarScrollableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension ConversationCollectionViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension ConversationCollectionViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
collectionView.scrollToTop()
|
||||
return .stop
|
||||
}
|
||||
|
@ -430,10 +430,10 @@ extension ConversationViewController: ToastableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension ConversationViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension ConversationViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
if case .displaying(let vc) = state {
|
||||
return vc.handleStatusBarTapped(xPosition: xPosition)
|
||||
return vc.handleStatusBarTapped(at: xPosition)
|
||||
} else {
|
||||
return .continue
|
||||
}
|
||||
|
@ -267,8 +267,8 @@ extension TrendingStatusesViewController: StatusCollectionViewCellDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
extension TrendingStatusesViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension TrendingStatusesViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
collectionView.scrollToTop()
|
||||
return .stop
|
||||
}
|
||||
|
@ -446,8 +446,8 @@ extension LocalPredicateStatusesViewController: TabBarScrollableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalPredicateStatusesViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension LocalPredicateStatusesViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
collectionView.scrollToTop()
|
||||
return .stop
|
||||
}
|
||||
|
@ -184,12 +184,12 @@ extension AccountSwitchingContainerViewController: TuskerRootViewController {
|
||||
return root.presentPreferences(completion: completion)
|
||||
}
|
||||
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
loadViewIfNeeded()
|
||||
if root.isFastAccountSwitcherActive {
|
||||
return .stop
|
||||
} else {
|
||||
return root.handleStatusBarTapped(xPosition: xPosition)
|
||||
return root.handleStatusBarTapped(at: xPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,12 +198,12 @@ extension BaseMainTabBarViewController: BackgroundableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension BaseMainTabBarViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension BaseMainTabBarViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
guard presentedViewController == nil,
|
||||
let vc = selectedViewController as? StatusBarTappableViewController else {
|
||||
let vc = selectedViewController as? StatusBarTapHandling else {
|
||||
return .continue
|
||||
}
|
||||
return vc.handleStatusBarTapped(xPosition: xPosition)
|
||||
return vc.handleStatusBarTapped(at: xPosition)
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,13 @@ import UIKit
|
||||
import Duckable
|
||||
import ComposeUI
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
extension DuckableContainerViewController: @retroactive StatusBarTapHandling {
|
||||
public func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
(child as? TuskerRootViewController)?.handleStatusBarTapped(at: xPosition) ?? .continue
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
extension DuckableContainerViewController: AccountSwitchableViewController {
|
||||
func stateRestorationActivity() -> NSUserActivity? {
|
||||
@ -48,10 +55,6 @@ extension DuckableContainerViewController: AccountSwitchableViewController {
|
||||
(child as? TuskerRootViewController)?.presentPreferences(completion: completion)
|
||||
}
|
||||
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
(child as? TuskerRootViewController)?.handleStatusBarTapped(xPosition: xPosition) ?? .continue
|
||||
}
|
||||
|
||||
var isFastAccountSwitcherActive: Bool {
|
||||
(child as? AccountSwitchableViewController)?.isFastAccountSwitcherActive ?? false
|
||||
}
|
||||
|
@ -648,17 +648,17 @@ extension MainSplitViewController: TuskerRootViewController {
|
||||
return vc
|
||||
}
|
||||
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
guard presentedViewController == nil else {
|
||||
return .continue
|
||||
}
|
||||
if traitCollection.horizontalSizeClass == .compact {
|
||||
return tabBarViewController.handleStatusBarTapped(xPosition: xPosition)
|
||||
return tabBarViewController.handleStatusBarTapped(at: xPosition)
|
||||
} else {
|
||||
let pointInSecondary = secondaryNavController.view.convert(CGPoint(x: xPosition, y: 0), from: view)
|
||||
if secondaryNavController.view.bounds.contains(pointInSecondary),
|
||||
let statusBarTappable = secondaryNavController as? StatusBarTappableViewController {
|
||||
return statusBarTappable.handleStatusBarTapped(xPosition: pointInSecondary.x)
|
||||
let statusBarTappable = secondaryNavController as? StatusBarTapHandling {
|
||||
return statusBarTappable.handleStatusBarTapped(at: pointInSecondary.x)
|
||||
} else {
|
||||
return .continue
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import UIKit
|
||||
import ComposeUI
|
||||
|
||||
@MainActor
|
||||
protocol TuskerRootViewController: UIViewController, StateRestorableViewController, StatusBarTappableViewController {
|
||||
protocol TuskerRootViewController: UIViewController, StateRestorableViewController, StatusBarTapHandling {
|
||||
func compose(editing draft: Draft?, animated: Bool, isDucked: Bool, completion: (() -> Void)?)
|
||||
func select(route: TuskerRoute, animated: Bool, completion: (() -> Void)?)
|
||||
func getNavigationDelegate() -> TuskerNavigationDelegate?
|
||||
|
@ -136,8 +136,8 @@ extension FollowRequestNotificationViewController: TuskerNavigationDelegate {
|
||||
extension FollowRequestNotificationViewController: MenuActionProvider {
|
||||
}
|
||||
|
||||
extension FollowRequestNotificationViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension FollowRequestNotificationViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
collectionView.scrollToTop()
|
||||
return .stop
|
||||
}
|
||||
|
@ -803,8 +803,8 @@ extension NotificationsCollectionViewController: TabBarScrollableViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationsCollectionViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension NotificationsCollectionViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
collectionView.scrollToTop()
|
||||
return .stop
|
||||
}
|
||||
|
@ -690,8 +690,8 @@ extension ProfileStatusesViewController: TabBarScrollableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension ProfileStatusesViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension ProfileStatusesViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
collectionView.scrollToTop()
|
||||
return .stop
|
||||
}
|
||||
|
@ -390,10 +390,10 @@ extension ProfileViewController: TabBarScrollableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension ProfileViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension ProfileViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
guard isViewLoaded else { return .stop }
|
||||
return currentViewController.handleStatusBarTapped(xPosition: xPosition)
|
||||
return currentViewController.handleStatusBarTapped(at: xPosition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -425,8 +425,8 @@ extension StatusActionAccountListCollectionViewController: StatusCollectionViewC
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusActionAccountListCollectionViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension StatusActionAccountListCollectionViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
collectionView.scrollToTop()
|
||||
return .stop
|
||||
}
|
||||
|
@ -1437,8 +1437,8 @@ extension TimelineViewController: TabBarScrollableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension TimelineViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension TimelineViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
collectionView.scrollToTop()
|
||||
return .stop
|
||||
}
|
||||
|
@ -160,8 +160,8 @@ extension AdaptableNavigationController: BackgroundableViewController {
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
extension AdaptableNavigationController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
(topViewController as? StatusBarTappableViewController)?.handleStatusBarTapped(xPosition: xPosition) ?? .continue
|
||||
extension AdaptableNavigationController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
(topViewController as? StatusBarTapHandling)?.handleStatusBarTapped(at: xPosition) ?? .continue
|
||||
}
|
||||
}
|
||||
|
@ -259,10 +259,10 @@ extension EnhancedNavigationViewController: BackgroundableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension EnhancedNavigationViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
if let topVC = topViewController as? StatusBarTappableViewController {
|
||||
return topVC.handleStatusBarTapped(xPosition: xPosition)
|
||||
extension EnhancedNavigationViewController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
if let topVC = topViewController as? StatusBarTapHandling {
|
||||
return topVC.handleStatusBarTapped(at: xPosition)
|
||||
}
|
||||
return .continue
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ protocol SegmentedPageViewControllerPage: Hashable {
|
||||
var segmentedControlTitle: String { get }
|
||||
}
|
||||
|
||||
class SegmentedPageViewController<Page: SegmentedPageViewControllerPage>: UIViewController, UIPageViewControllerDelegate, TabbedPageViewController {
|
||||
class SegmentedPageViewController<Page: SegmentedPageViewControllerPage>: UIViewController, UIPageViewControllerDelegate, TabbedPageViewController, StatusBarTapHandling {
|
||||
|
||||
private(set) var pages: [Page]!
|
||||
private let pageProvider: (Page) -> UIViewController
|
||||
@ -178,6 +178,14 @@ class SegmentedPageViewController<Page: SegmentedPageViewControllerPage>: UIView
|
||||
guard currentIndex > 0 else { return }
|
||||
selectPage(pages[currentIndex - 1], animated: true)
|
||||
}
|
||||
|
||||
// MARK: StatusBarTapHandling
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
if let current = currentViewController as? StatusBarTapHandling {
|
||||
return current.handleStatusBarTapped(at: xPosition)
|
||||
}
|
||||
return .continue
|
||||
}
|
||||
}
|
||||
|
||||
extension SegmentedPageViewController {
|
||||
@ -204,15 +212,6 @@ extension SegmentedPageViewController: BackgroundableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension SegmentedPageViewController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
if let current = currentViewController as? StatusBarTappableViewController {
|
||||
return current.handleStatusBarTapped(xPosition: xPosition)
|
||||
}
|
||||
return .continue
|
||||
}
|
||||
}
|
||||
|
||||
extension SegmentedPageViewController: NestedResponderProvider {
|
||||
var innerResponder: UIResponder? {
|
||||
currentViewController
|
||||
|
@ -270,18 +270,18 @@ extension SplitNavigationController: TabBarScrollableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension SplitNavigationController: StatusBarTappableViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
|
||||
extension SplitNavigationController: StatusBarTapHandling {
|
||||
func handleStatusBarTapped(at xPosition: CGFloat) -> StatusBarTapResult {
|
||||
let vcs = viewControllers
|
||||
if !canShowSecondaryNav || vcs.count < 2 {
|
||||
return (vcs.last! as? StatusBarTappableViewController)?.handleStatusBarTapped(xPosition: xPosition) ?? .continue
|
||||
return (vcs.last! as? StatusBarTapHandling)?.handleStatusBarTapped(at: xPosition) ?? .continue
|
||||
} else {
|
||||
let positionInRoot = rootNav.view.convert(CGPoint(x: xPosition, y: 0), from: view)
|
||||
let positionInSecondary = secondaryNav.view.convert(CGPoint(x: xPosition, y: 0), from: view)
|
||||
if rootNav.view.bounds.contains(positionInRoot) {
|
||||
return (rootNav.topViewController as? StatusBarTappableViewController)?.handleStatusBarTapped(xPosition: positionInRoot.x) ?? .continue
|
||||
return (rootNav.topViewController as? StatusBarTapHandling)?.handleStatusBarTapped(at: positionInRoot.x) ?? .continue
|
||||
} else if secondaryNav.view.bounds.contains(positionInSecondary) {
|
||||
return (secondaryNav.topViewController as? StatusBarTappableViewController)?.handleStatusBarTapped(xPosition: positionInRoot.x) ?? .continue
|
||||
return (secondaryNav.topViewController as? StatusBarTapHandling)?.handleStatusBarTapped(at: positionInRoot.x) ?? .continue
|
||||
}
|
||||
}
|
||||
return .continue
|
||||
|
@ -1,14 +0,0 @@
|
||||
//
|
||||
// StatusBarTappableViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 11/1/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
protocol StatusBarTappableViewController: UIViewController {
|
||||
func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult
|
||||
}
|
42
Tusker/Swizzler.h
Normal file
42
Tusker/Swizzler.h
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// Swizzler.h
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 3/3/25.
|
||||
// Copyright © 2025 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#if !TARGET_OS_VISION
|
||||
@interface Swizzler : NSObject
|
||||
|
||||
+ (BOOL)swizzleStatusBarManagerWithExceptionHandler:(void (^)(NSException *))captureException;
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, StatusBarTapResult) {
|
||||
StatusBarTapResultContinue,
|
||||
StatusBarTapResultStop,
|
||||
} NS_SWIFT_NAME(StatusBarTapResult);
|
||||
|
||||
NS_SWIFT_UI_ACTOR
|
||||
@protocol StatusBarTapHandling <NSObject>
|
||||
|
||||
- (StatusBarTapResult)handleStatusBarTappedAtXPosition:(CGFloat)xPosition NS_SWIFT_NAME(handleStatusBarTapped(at:));
|
||||
|
||||
@end
|
||||
|
||||
@interface UIStatusBarManager (TuskerSwizzling)
|
||||
|
||||
@property (nonatomic, readonly) UIWindowScene * _Nullable windowScene;
|
||||
|
||||
- (void)handleTapAction:(id)sender;
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
37
Tusker/Swizzler.m
Normal file
37
Tusker/Swizzler.m
Normal file
@ -0,0 +1,37 @@
|
||||
//
|
||||
// Swizzler.m
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 3/3/25.
|
||||
// Copyright © 2025 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Swizzler.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation Swizzler
|
||||
|
||||
+ (BOOL)swizzleStatusBarManagerWithExceptionHandler:(void (^)(NSException *))captureException {
|
||||
__block IMP original;
|
||||
IMP new = imp_implementationWithBlock(^void (UIStatusBarManager *self, id sender) {
|
||||
if ([self.windowScene.delegate conformsToProtocol:@protocol(StatusBarTapHandling)]) {
|
||||
CGFloat xPosition;
|
||||
@try {
|
||||
xPosition = [(NSNumber *)[sender valueForKey:@"xPosition"] doubleValue];
|
||||
} @catch (NSException *exception) {
|
||||
captureException(exception);
|
||||
return;
|
||||
}
|
||||
id<StatusBarTapHandling> delegate = (id<StatusBarTapHandling>)self.windowScene.delegate;
|
||||
StatusBarTapResult result = [delegate handleStatusBarTappedAtXPosition:xPosition];
|
||||
if (result == StatusBarTapResultStop) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
((void (*)(UIStatusBarManager *, SEL, id))original)(self, @selector(handleTapAction:), sender);
|
||||
});
|
||||
original = class_replaceMethod(UIStatusBarManager.class, @selector(handleTapAction:), new, "v@:@");
|
||||
return original != nil;
|
||||
}
|
||||
|
||||
@end
|
@ -4,16 +4,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_INLINE NSException * _Nullable catchNSException(void(^_Nonnull tryBlock)(void)) {
|
||||
@try {
|
||||
tryBlock();
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
return exception;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
#import "Swizzler.h"
|
||||
|
||||
// Define this private method so we can override it from MultiColumnCollectionViewLayout.
|
||||
@interface UICollectionViewLayout (Tusker_Hacks)
|
||||
|
Loading…
x
Reference in New Issue
Block a user