From 3bc1a3de39fc521bf87458c6434bd460121092f5 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 28 Sep 2021 19:58:14 +0800 Subject: [PATCH] feat: add scroll position record and shortcut bar --- Mastodon.xcodeproj/project.pbxproj | 16 ++- .../Scene/Compose/ComposeViewController.swift | 32 +++++- .../Compose/View/ComposeToolbarView.swift | 35 ++++++ .../HomeTimelineViewController.swift | 105 +++++++++++++++++- .../HomeTimeline/HomeTimelineViewModel.swift | 11 ++ Mastodon/Supporting Files/SceneDelegate.swift | 16 --- Mastodon/Vender/HandleTapAction.swift | 16 +++ Mastodon/Vender/Mastodon-Bridging-Header.h | 4 + .../UIStatusBarManager+HandleTapAction.m | 38 +++++++ 9 files changed, 245 insertions(+), 28 deletions(-) create mode 100644 Mastodon/Vender/HandleTapAction.swift create mode 100644 Mastodon/Vender/Mastodon-Bridging-Header.h create mode 100644 Mastodon/Vender/UIStatusBarManager+HandleTapAction.m diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index ab4afaa6..50d73c11 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -573,6 +573,8 @@ DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; DBE54ACC2636C8FD004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; DBF156DF2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF156DE2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift */; }; + DBF156E22702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m in Sources */ = {isa = PBXBuildFile; fileRef = DBF156E12702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m */; }; + DBF156E42702DB3F00EC00B7 /* HandleTapAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF156E32702DB3F00EC00B7 /* HandleTapAction.swift */; }; DBF1D24E269DAF5D00C1C08A /* SearchDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF1D24D269DAF5D00C1C08A /* SearchDetailViewController.swift */; }; DBF1D251269DB01200C1C08A /* SearchHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF1D250269DB01200C1C08A /* SearchHistoryViewController.swift */; }; DBF1D257269DBAC600C1C08A /* SearchDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF1D256269DBAC600C1C08A /* SearchDetailViewModel.swift */; }; @@ -1355,6 +1357,9 @@ DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPreference.swift; sourceTree = ""; }; DBF156DD27006F5D00EC00B7 /* CoreData 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "CoreData 2.xcdatamodel"; sourceTree = ""; }; DBF156DE2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarAddAccountCollectionViewCell.swift; sourceTree = ""; }; + DBF156E02702DA6800EC00B7 /* Mastodon-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Mastodon-Bridging-Header.h"; sourceTree = ""; }; + DBF156E12702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIStatusBarManager+HandleTapAction.m"; sourceTree = ""; }; + DBF156E32702DB3F00EC00B7 /* HandleTapAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleTapAction.swift; sourceTree = ""; }; DBF1D24D269DAF5D00C1C08A /* SearchDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchDetailViewController.swift; sourceTree = ""; }; DBF1D250269DB01200C1C08A /* SearchHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHistoryViewController.swift; sourceTree = ""; }; DBF1D256269DBAC600C1C08A /* SearchDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchDetailViewModel.swift; sourceTree = ""; }; @@ -1737,6 +1742,9 @@ DB6180EC26391C6C0018D199 /* TransitioningMath.swift */, DB75BF1D263C1C1B00EDBF1F /* CustomScheduler.swift */, DBAC649A267DF8C8007FE9FD /* ActivityIndicatorNode.swift */, + DBF156E32702DB3F00EC00B7 /* HandleTapAction.swift */, + DBF156E12702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m */, + DBF156E02702DA6800EC00B7 /* Mastodon-Bridging-Header.h */, ); path = Vender; sourceTree = ""; @@ -3437,7 +3445,7 @@ TargetAttributes = { DB427DD125BAA00100D1B89D = { CreatedOnToolsVersion = 12.4; - LastSwiftMigration = 1220; + LastSwiftMigration = 1300; }; DB427DE725BAA00100D1B89D = { CreatedOnToolsVersion = 12.4; @@ -4067,6 +4075,7 @@ DB3667A1268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift in Sources */, DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */, DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */, + DBF156E42702DB3F00EC00B7 /* HandleTapAction.swift in Sources */, DB023295267F0AB800031745 /* ASMetaEditableTextNode.swift in Sources */, 2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */, DB4F096C269EFA2000D62E92 /* SearchResultViewController+StatusProvider.swift in Sources */, @@ -4114,6 +4123,7 @@ 2DAC9E46262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift in Sources */, DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */, DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */, + DBF156E22702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m in Sources */, DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */, 2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */, 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */, @@ -4758,6 +4768,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Mastodon/Vender/Mastodon-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -4786,6 +4797,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Mastodon/Vender/Mastodon-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -5316,6 +5328,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Mastodon/Vender/Mastodon-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -5552,6 +5565,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Mastodon/Vender/Mastodon-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index 9be0e4f7..460d12ca 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -18,7 +18,7 @@ import MastodonUI final class ComposeViewController: UIViewController, NeedsDependency { static let minAutoCompleteVisibleHeight: CGFloat = 100 - + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } @@ -137,6 +137,16 @@ extension ComposeViewController { override func viewDidLoad() { super.viewDidLoad() + let groups = [UIBarButtonItemGroup(barButtonItems: [ + composeToolbarView.mediaBarButtonItem, + composeToolbarView.pollBarButtonItem, + composeToolbarView.contentWarningBarButtonItem, + composeToolbarView.visibilityBarButtonItem, + ], representativeItem: nil)] + + tableView.inputAssistantItem.trailingBarButtonGroups = groups + textEditorView()?.textView.inputAssistantItem.trailingBarButtonGroups = groups + viewModel.title .receive(on: DispatchQueue.main) .sink { [weak self] title in @@ -330,13 +340,21 @@ extension ComposeViewController { // bind media button toolbar state viewModel.isMediaToolbarButtonEnabled .receive(on: DispatchQueue.main) - .assign(to: \.isEnabled, on: composeToolbarView.mediaButton) + .sink { [weak self] isMediaToolbarButtonEnabled in + guard let self = self else { return } +// self.composeToolbarView.mediaBarButtonItem.isEnabled = isMediaToolbarButtonEnabled + self.composeToolbarView.mediaButton.isEnabled = isMediaToolbarButtonEnabled + } .store(in: &disposeBag) // bind poll button toolbar state viewModel.isPollToolbarButtonEnabled .receive(on: DispatchQueue.main) - .assign(to: \.isEnabled, on: composeToolbarView.pollButton) + .sink { [weak self] isPollToolbarButtonEnabled in + guard let self = self else { return } +// self.composeToolbarView.pollBarButtonItem.isEnabled = isPollToolbarButtonEnabled + self.composeToolbarView.pollButton.isEnabled = isPollToolbarButtonEnabled + } .store(in: &disposeBag) Publishers.CombineLatest( @@ -347,10 +365,14 @@ extension ComposeViewController { .sink { [weak self] isPollComposing, isPollToolbarButtonEnabled in guard let self = self else { return } guard isPollToolbarButtonEnabled else { - self.composeToolbarView.pollButton.accessibilityLabel = L10n.Scene.Compose.Accessibility.appendPoll + let accessibilityLabel = L10n.Scene.Compose.Accessibility.appendPoll +// self.composeToolbarView.pollBarButtonItem.accessibilityLabel = accessibilityLabel + self.composeToolbarView.pollButton.accessibilityLabel = accessibilityLabel return } - self.composeToolbarView.pollButton.accessibilityLabel = isPollComposing ? L10n.Scene.Compose.Accessibility.removePoll : L10n.Scene.Compose.Accessibility.appendPoll + let accessibilityLabel = isPollComposing ? L10n.Scene.Compose.Accessibility.removePoll : L10n.Scene.Compose.Accessibility.appendPoll +// self.composeToolbarView.pollBarButtonItem.accessibilityLabel = accessibilityLabel + self.composeToolbarView.pollButton.accessibilityLabel = accessibilityLabel } .store(in: &disposeBag) diff --git a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift index 99fe88ce..a8ceeee3 100644 --- a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift +++ b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift @@ -27,6 +27,41 @@ final class ComposeToolbarView: UIView { weak var delegate: ComposeToolbarViewDelegate? + // barButtonItem + let mediaBarButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "photo"), for: .normal) + return button + }() + private(set) lazy var mediaBarButtonItem: UIBarButtonItem = { + let barButtonItem = UIBarButtonItem(customView: mediaBarButton) + barButtonItem.accessibilityLabel = L10n.Scene.Compose.Accessibility.appendAttachment + return barButtonItem + }() + + let pollBarButtonItem: UIBarButtonItem = { + let barButtonItem = UIBarButtonItem() + barButtonItem.image = UIImage(systemName: "list.bullet") + barButtonItem.accessibilityLabel = L10n.Scene.Compose.Accessibility.appendPoll + return barButtonItem + }() + + let contentWarningBarButtonItem: UIBarButtonItem = { + let barButtonItem = UIBarButtonItem() + barButtonItem.image = UIImage(systemName: "exclamationmark.shield") + barButtonItem.accessibilityLabel = L10n.Scene.Compose.Accessibility.enableContentWarning + return barButtonItem + }() + + let visibilityBarButtonItem: UIBarButtonItem = { + let barButtonItem = UIBarButtonItem() + barButtonItem.image = UIImage(systemName: "person.3") + barButtonItem.accessibilityLabel = L10n.Scene.Compose.Accessibility.postVisibilityMenu + return barButtonItem + }() + + // button + let mediaButton: UIButton = { let button = HighlightDimmableButton() ComposeToolbarView.configureToolbarButtonAppearance(button: button) diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index e815ba8c..b496c76b 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -17,6 +17,8 @@ import AlamofireImage final class HomeTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + let logger = Logger(subsystem: "HomeTimelineViewController", category: "UI") + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } @@ -218,6 +220,31 @@ extension HomeTimelineViewController { } } .store(in: &disposeBag) + + NotificationCenter.default + .publisher(for: .statusBarTapped, object: nil) + .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: false) + .sink { [weak self] notification in + guard let self = self else { return } + guard let _ = self.view.window else { return } // displaying + + // https://developer.limneos.net/index.php?ios=13.1.3&framework=UIKitCore.framework&header=UIStatusBarTapAction.h + guard let action = notification.object as AnyObject?, + let xPosition = action.value(forKey: "xPosition") as? Double + else { return } + + let viewFrameInWindow = self.view.convert(self.view.frame, to: nil) + guard xPosition >= viewFrameInWindow.minX && xPosition <= viewFrameInWindow.maxX else { return } + + // works on iOS 14 + self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): receive notification \(xPosition)") + + // check if scroll to top + guard self.shouldRestoreScrollPosition() else { return } + self.restorePositionWhenScrollToTop() + } + .store(in: &disposeBag) + } override func viewWillAppear(_ animated: Bool) { @@ -234,9 +261,15 @@ extension HomeTimelineViewController { viewModel.viewDidAppear.send() - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in - guard let self = self else { return } - // always try to refresh timeline after appear + if let timestamp = viewModel.lastAutomaticFetchTimestamp.value { + let now = Date() + if now.timeIntervalSince(timestamp) > 60 { + self.viewModel.lastAutomaticFetchTimestamp.value = now + self.viewModel.homeTimelineNeedRefresh.send() + } else { + // do nothing + } + } else { self.viewModel.homeTimelineNeedRefresh.send() } } @@ -394,9 +427,62 @@ extension HomeTimelineViewController: TableViewCellHeightCacheableContainer { // MARK: - UIScrollViewDelegate extension HomeTimelineViewController { func scrollViewDidScroll(_ scrollView: UIScrollView) { + switch scrollView { + case tableView: + aspectScrollViewDidScroll(scrollView) + viewModel.homeTimelineNavigationBarTitleViewModel.handleScrollViewDidScroll(scrollView) + default: + break + } + } + + func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { + switch scrollView { + case tableView: + // handle scrollToTop + savePositionBeforeScrollToTop() + return true + default: + assertionFailure() + return true + } + } + + private func savePositionBeforeScrollToTop() { + guard let diffableDataSource = viewModel.diffableDataSource else { return } + guard let anchorIndexPaths = tableView.indexPathsForVisibleRows?.sorted() else { return } + guard !anchorIndexPaths.isEmpty else { return } + let anchorIndexPath = anchorIndexPaths[anchorIndexPaths.count / 2] + guard let anchorItem = diffableDataSource.itemIdentifier(for: anchorIndexPath) else { return } - aspectScrollViewDidScroll(scrollView) - viewModel.homeTimelineNavigationBarTitleViewModel.handleScrollViewDidScroll(scrollView) + let offset: CGFloat = { + guard let anchorCell = tableView.cellForRow(at: anchorIndexPath) else { return 0 } + let cellFrameInView = tableView.convert(anchorCell.frame, to: view) + return cellFrameInView.origin.y + }() + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): save position record for \(anchorIndexPath) with offset: \(offset)") + viewModel.scrollPositionRecord.value = HomeTimelineViewModel.ScrollPositionRecord( + item: anchorItem, + offset: offset, + timestamp: Date() + ) + } + + private func shouldRestoreScrollPosition() -> Bool { + // check if scroll to top + guard self.tableView.safeAreaInsets.top > 0 else { return false } + let zeroOffset = -self.tableView.safeAreaInsets.top + return abs(self.tableView.contentOffset.y - zeroOffset) < 2.0 + } + + private func restorePositionWhenScrollToTop() { + guard let diffableDataSource = self.viewModel.diffableDataSource else { return } + guard let record = self.viewModel.scrollPositionRecord.value, + let indexPath = diffableDataSource.indexPath(for: record.item) + else { return } + + self.tableView.scrollToRow(at: indexPath, at: .middle, animated: true) + self.viewModel.scrollPositionRecord.value = nil } } @@ -544,6 +630,8 @@ extension HomeTimelineViewController: ScrollViewContainer { } else { let indexPath = IndexPath(row: 0, section: 0) guard viewModel.diffableDataSource?.itemIdentifier(for: indexPath) != nil else { return } + // save position + savePositionBeforeScrollToTop() tableView.scrollToRow(at: indexPath, at: .top, animated: true) } } @@ -572,7 +660,12 @@ extension HomeTimelineViewController: StatusTableViewCellDelegate { // MARK: - HomeTimelineNavigationBarTitleViewDelegate extension HomeTimelineViewController: HomeTimelineNavigationBarTitleViewDelegate { func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, logoButtonDidPressed sender: UIButton) { - scrollToTop(animated: true) + if shouldRestoreScrollPosition() { + restorePositionWhenScrollToTop() + } else { + savePositionBeforeScrollToTop() + scrollToTop(animated: true) + } } func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, buttonDidPressed sender: UIButton) { diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift index cf0b69b9..a3fbcbd7 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift @@ -28,6 +28,8 @@ final class HomeTimelineViewModel: NSObject { let isFetchingLatestTimeline = CurrentValueSubject(false) let viewDidAppear = PassthroughSubject() let homeTimelineNavigationBarTitleViewModel: HomeTimelineNavigationBarTitleViewModel + let lastAutomaticFetchTimestamp = CurrentValueSubject(nil) + let scrollPositionRecord = CurrentValueSubject(nil) weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate? weak var tableView: UITableView? @@ -153,3 +155,12 @@ final class HomeTimelineViewModel: NSObject { } extension HomeTimelineViewModel: SuggestionAccountViewModelDelegate { } + + +extension HomeTimelineViewModel { + struct ScrollPositionRecord { + let item: Item + let offset: CGFloat + let timestamp: Date + } +} diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index f5cc269b..6c5752c4 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -155,19 +155,3 @@ extension SceneDelegate { return true } } - -#if DEBUG -class TestWindow: UIWindow { - - override func sendEvent(_ event: UIEvent) { - event.allTouches?.forEach({ (touch) in - let location = touch.location(in: self) - let view = hitTest(location, with: event) - print(view.debugDescription) - }) - - super.sendEvent(event) - } -} -#endif - diff --git a/Mastodon/Vender/HandleTapAction.swift b/Mastodon/Vender/HandleTapAction.swift new file mode 100644 index 00000000..2ebdd920 --- /dev/null +++ b/Mastodon/Vender/HandleTapAction.swift @@ -0,0 +1,16 @@ +// +// HandleTapAction.swift +// Mastodon +// +// Created by Cirno MainasuK on 2021-9-28. +// + +import Foundation + +@objc class HandleTapAction: NSObject { + @objc static let statusBarTappedNotification = Notification(name: .statusBarTapped) +} + +extension Notification.Name { + static let statusBarTapped = Notification.Name(rawValue: "org.joinmastodon.app.statusBarTapped") +} diff --git a/Mastodon/Vender/Mastodon-Bridging-Header.h b/Mastodon/Vender/Mastodon-Bridging-Header.h new file mode 100644 index 00000000..1b2cb5d6 --- /dev/null +++ b/Mastodon/Vender/Mastodon-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/Mastodon/Vender/UIStatusBarManager+HandleTapAction.m b/Mastodon/Vender/UIStatusBarManager+HandleTapAction.m new file mode 100644 index 00000000..3fbb7622 --- /dev/null +++ b/Mastodon/Vender/UIStatusBarManager+HandleTapAction.m @@ -0,0 +1,38 @@ +#import +#import +#import +#import + +@implementation UIStatusBarManager (CAPHandleTapAction) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class class = [self class]; + SEL originalSelector = NSSelectorFromString(@"handleTapAction:"); + SEL swizzledSelector = @selector(custom_handleTapAction:); + + Method originalMethod = class_getInstanceMethod(self, originalSelector); + Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector); + + BOOL didAddMethod = class_addMethod(class, + originalSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)); + if (didAddMethod) { + class_replaceMethod(class, + swizzledSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + } else { + method_exchangeImplementations(originalMethod, swizzledMethod); + } + }); +} + +-(void)custom_handleTapAction:(id)sender { + [[NSNotificationCenter defaultCenter] postNotificationName:@"org.joinmastodon.app.statusBarTapped" object:sender]; + [self custom_handleTapAction:sender]; +} + +@end