From 843f35910444d9cca7b2e95a83385d6f8a18d356 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Wed, 26 Jun 2024 21:40:13 -0500 Subject: [PATCH 1/3] Screens: Show and hide New Post button on scroll The button hides when the user is scrolling to view older posts, and reappears when the user is scrolling to view newer posts. Ref: #582 --- .../HomeScreen/NewsFeedViewController.swift | 15 +++++++++++++++ Mammoth/Screens/TabBarViewController.swift | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift b/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift index f967f528e..0e065d76d 100644 --- a/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift +++ b/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift @@ -71,6 +71,7 @@ class NewsFeedViewController: UIViewController, UIScrollViewDelegate, UITableVie private var isInsertingContent: Bool = false private var isScrollingProgrammatically: Bool = false private var disableFeedUpdates: Bool = false + private var lastScrollPos: CGPoint = CGPointZero // switchingAccounts is set to true in the period between // willSwitchAccount and didSwitchAccount, when currentAccount @@ -460,6 +461,9 @@ class NewsFeedViewController: UIViewController, UIScrollViewDelegate, UITableVie cell.willDisplay() } }) + + // Ensure that the New Post button show/hide code knows where we are. + self.lastScrollPos = self.tableView.contentOffset // user just opened the app, assume an outdated feed if they've been out for > 10 seconds. if !self.viewModel.didViewRecently { @@ -804,6 +808,17 @@ extension NewsFeedViewController { } } } + + // Don't manipulate the new post button when we are pulling to refresh. + if !self.isScrollingProgrammatically && scrollView.contentOffset.y >= 0 { + let scrollingUp = lastScrollPos.y > scrollView.contentOffset.y + let showNewPostButton = GlobalStruct.feedReadDirection == .bottomUp ? scrollingUp : !scrollingUp + let notificationName = showNewPostButton ? "showNewPostButton" : "hideNewPostButton" + + NotificationCenter.default.post(name: Notification.Name(notificationName), object: nil) + + lastScrollPos = scrollView.contentOffset + } } func scrollViewDidScrollToTop(_ scrollView: UIScrollView) { diff --git a/Mammoth/Screens/TabBarViewController.swift b/Mammoth/Screens/TabBarViewController.swift index ded7303a0..a51d3ef04 100644 --- a/Mammoth/Screens/TabBarViewController.swift +++ b/Mammoth/Screens/TabBarViewController.swift @@ -189,6 +189,14 @@ class TabBarViewController: AnimateTabController, UIGestureRecognizerDelegate, U @objc func hideNewContent2() { self.newContent2.alpha = 0 } + + @objc func showNewPostButton() { + self.view.bringSubviewToFront(self.newPostButton) + } + + @objc func hideNewPostButton() { + self.view.sendSubviewToBack(self.newPostButton) + } @objc func showComposer() { // show composer @@ -249,6 +257,8 @@ class TabBarViewController: AnimateTabController, UIGestureRecognizerDelegate, U NotificationCenter.default.addObserver(self, selector: #selector(self.hideNewContent1), name: NSNotification.Name(rawValue: "hideNewContent1"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.showNewContent2), name: NSNotification.Name(rawValue: "showNewContent2"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.hideNewContent2), name: NSNotification.Name(rawValue: "hideNewContent2"), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.showNewPostButton), name: NSNotification.Name(rawValue: "showNewPostButton"), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.hideNewPostButton), name: NSNotification.Name(rawValue: "hideNewPostButton"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.fetchPost0), name: NSNotification.Name(rawValue: "fetchPost"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.fetchUser0), name: NSNotification.Name(rawValue: "fetchUser"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.restoreFromDrafts2), name: NSNotification.Name(rawValue: "restoreFromDrafts2"), object: nil) From c6e590a55c4e1a9a7f43c064a46b52a73bc6f8ff Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Fri, 28 Jun 2024 21:05:05 -0500 Subject: [PATCH 2/3] Screens: Animate post button state change * Animation effect when the post button is shown or hidden based on scrolling. The button seems to "slide" in or out, and fades with alpha blending. The animation is also rate-limited to once per second, to prevent 'jerking' when a user is fidgeting around. * Use tableView:willDisplay:forRowAt: instead of scrollViewDidScroll: to change the post button state. This ensures that small adjustments to scroll don't make the post button change state, which can be distracting. * Update the shown index path when the view model updates. --- .../HomeScreen/NewsFeedViewController.swift | 22 ++++----- Mammoth/Screens/TabBarViewController.swift | 48 ++++++++++++++++++- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift b/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift index 0e065d76d..0acf24ca6 100644 --- a/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift +++ b/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift @@ -643,7 +643,14 @@ extension NewsFeedViewController { } } } - + + if (self.displayingIndexPath != nil) && !self.isScrollingProgrammatically && !self.isInsertingContent { + let scrollingUp = self.displayingIndexPath! > indexPath + let showNewPostButton = GlobalStruct.feedReadDirection == .bottomUp ? scrollingUp : !scrollingUp + let notificationName = showNewPostButton ? "showNewPostButton" : "hideNewPostButton" + + NotificationCenter.default.post(name: Notification.Name(notificationName), object: nil) + } self.displayingIndexPath = indexPath if self.isActiveFeed && self.viewModel.type.shouldSyncItems { @@ -808,17 +815,6 @@ extension NewsFeedViewController { } } } - - // Don't manipulate the new post button when we are pulling to refresh. - if !self.isScrollingProgrammatically && scrollView.contentOffset.y >= 0 { - let scrollingUp = lastScrollPos.y > scrollView.contentOffset.y - let showNewPostButton = GlobalStruct.feedReadDirection == .bottomUp ? scrollingUp : !scrollingUp - let notificationName = showNewPostButton ? "showNewPostButton" : "hideNewPostButton" - - NotificationCenter.default.post(name: Notification.Name(notificationName), object: nil) - - lastScrollPos = scrollView.contentOffset - } } func scrollViewDidScrollToTop(_ scrollView: UIScrollView) { @@ -1225,6 +1221,7 @@ private extension NewsFeedViewController { tableView.contentOffset.y = yOffset - self.view.safeAreaInsets.top } } + self.displayingIndexPath = indexPath } else { log.error("#scrollToPosition1: no indexpath found") } @@ -1253,6 +1250,7 @@ private extension NewsFeedViewController { } UIView.setAnimationsEnabled(true) } + self.displayingIndexPath = indexPath } else { log.error("#scrollToPosition2: no indexpath found") } diff --git a/Mammoth/Screens/TabBarViewController.swift b/Mammoth/Screens/TabBarViewController.swift index a51d3ef04..e6881b6b5 100644 --- a/Mammoth/Screens/TabBarViewController.swift +++ b/Mammoth/Screens/TabBarViewController.swift @@ -38,6 +38,9 @@ class TabBarViewController: AnimateTabController, UIGestureRecognizerDelegate, U let indActivity2 = UIImageView() // adds a small dot indicator under the messages tab when new direct messages come in let counter = UIButton() var timer = Timer() + var postButtonShown: Bool = true + var postButtonAnimator: UIViewPropertyAnimator! + var lastPostButtonAnimation: CFAbsoluteTime = 0 var customTabsImagesUnselected2: [String] = ["heart.text.square", "bell", "tray.full", "binoculars", "heart", "bookmark", "line.3.horizontal.decrease.circle", "gear"] var customTabsImages2: [String] = ["heart.text.square.fill", "bell.fill", "tray.full.fill", "binoculars.fill", "heart.fill", "bookmark.fill", "line.horizontal.3.decrease.circle.fill", "gear"] @@ -190,12 +193,51 @@ class TabBarViewController: AnimateTabController, UIGestureRecognizerDelegate, U self.newContent2.alpha = 0 } + func _handleToggleNewPostButton(show: Bool) { + let alpha = show ? 1.0 : 0.0 + let durationFactor = 1.0 - postButtonAnimator.fractionComplete + + postButtonAnimator.stopAnimation(false) + postButtonAnimator.finishAnimation(at: .end) + + postButtonAnimator.addAnimations { + self.newPostButton.alpha = alpha + if show { + self.newPostButton.frame.origin.y -= 20 + } else { + self.newPostButton.frame.origin.y += 20 + } + } + + postButtonAnimator.startAnimation() + postButtonAnimator.pauseAnimation() + postButtonAnimator.continueAnimation(withTimingParameters: nil, durationFactor: durationFactor) + } + @objc func showNewPostButton() { - self.view.bringSubviewToFront(self.newPostButton) + if lastPostButtonAnimation >= CFAbsoluteTimeGetCurrent() - 1.0 { + // Don't allow more than one animation per second if user is quickly 'flicking'. + return + } + + if !postButtonShown { + postButtonShown = true + lastPostButtonAnimation = CFAbsoluteTimeGetCurrent() + _handleToggleNewPostButton(show: true) + } } @objc func hideNewPostButton() { - self.view.sendSubviewToBack(self.newPostButton) + if lastPostButtonAnimation >= CFAbsoluteTimeGetCurrent() - 1.0 { + // Don't allow more than one animation per second if user is quickly 'flicking'. + return + } + + if postButtonShown { + postButtonShown = false + lastPostButtonAnimation = CFAbsoluteTimeGetCurrent() + _handleToggleNewPostButton(show: false) + } } @objc func showComposer() { @@ -235,6 +277,8 @@ class TabBarViewController: AnimateTabController, UIGestureRecognizerDelegate, U let dropInteraction = UIDropInteraction(delegate: self) self.view.addInteraction(dropInteraction) + postButtonAnimator = UIViewPropertyAnimator(duration: 0.75, curve: UIView.AnimationCurve.easeIn) + NotificationCenter.default.addObserver(self, selector: #selector(self.gotoH), name: NSNotification.Name(rawValue: "gotoH"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.gotoC), name: NSNotification.Name(rawValue: "gotoC"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.gotoE), name: NSNotification.Name(rawValue: "gotoE"), object: nil) From 4d85997ff015fd1e1677298aa8bf9887efc9a46e Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Fri, 28 Jun 2024 21:13:32 -0500 Subject: [PATCH 3/3] NewsFeedVC: Remove unneeded variable We no longer use the contentOffset to toggle the post button state. --- Mammoth/Screens/HomeScreen/NewsFeedViewController.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift b/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift index 0acf24ca6..4d33a2af7 100644 --- a/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift +++ b/Mammoth/Screens/HomeScreen/NewsFeedViewController.swift @@ -71,7 +71,6 @@ class NewsFeedViewController: UIViewController, UIScrollViewDelegate, UITableVie private var isInsertingContent: Bool = false private var isScrollingProgrammatically: Bool = false private var disableFeedUpdates: Bool = false - private var lastScrollPos: CGPoint = CGPointZero // switchingAccounts is set to true in the period between // willSwitchAccount and didSwitchAccount, when currentAccount @@ -461,9 +460,6 @@ class NewsFeedViewController: UIViewController, UIScrollViewDelegate, UITableVie cell.willDisplay() } }) - - // Ensure that the New Post button show/hide code knows where we are. - self.lastScrollPos = self.tableView.contentOffset // user just opened the app, assume an outdated feed if they've been out for > 10 seconds. if !self.viewModel.didViewRecently {