点击 UITabBarItem 像 Instagram 和 Twitter 一样滚动到顶部
Tap UITabBarItem To Scroll To Top Like Instagram and Twitter
我在使用此功能时遇到问题,我想获得一些帮助。
我的等级是 TabBarController
-> Navigation Controller
-> TableViewController
我想要的是,如果您在当前选项卡上并向下滚动,您将能够点击当前视图的 UITabBarItem
,然后您将滚动回到顶部,例如 Instagram 和 Twitter 所做的.
我在这里尝试了很多东西:
Older Question
但遗憾的是,没有一个答案适合我。
对于这种方式的任何帮助,我将不胜感激,
提前致谢!
这是我的 TableView`controller 代码:
import UIKit
class BarsViewController: UITableViewController,UISearchResultsUpdating,UISearchBarDelegate,UISearchDisplayDelegate,UITabBarControllerDelegate{
//TableView Data & non related stuff....
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.searchController.searchBar.resignFirstResponder()
self.searchController.searchBar.endEditing(true)
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
let indexPath = IndexPath(row: 0, section: 0)
let navigVC = viewController as? UINavigationController
let finalVC = navigVC?.viewControllers[0] as? BarsViewController
finalVC?.tableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
}
}
TabBarController.Swift 代码(代码无效):
import UIKit
class TabBarController: UITabBarController,UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let viewControllers = viewControllers else { return false }
if viewController == viewControllers[selectedIndex] {
if let nav = viewController as? UINavigationController {
guard let topController = nav.viewControllers.last else { return true }
if !topController.isScrolledToTop {
topController.scrollToTop()
return false
} else {
nav.popViewController(animated: true)
}
return true
}
}
return true
}
}
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
var isScrolledToTop: Bool {
for subView in view.subviews {
if let scrollView = subView as? UIScrollView {
return (scrollView.contentOffset.y == 0)
}
}
return true
}
}
在您的 TabViewController 中尝试此代码:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
let indexPath = NSIndexPath(row: 0, section: 0)
let navigVC = viewController as? UINavigationController
let finalVC = navigVC?.viewControllers[0] as? YourVC
finalVC?.tableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
}
此外,您的 TabViewController 应该继承自 UITabBarControllerDelegate
最终代码:
import UIKit
class tabViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
let indexPath = NSIndexPath(row: 0, section: 0)
let navigVC = viewController as? UINavigationController
let finalVC = navigVC?.viewControllers[0] as? YourVC
finalVC?.tableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
}
}
记得修改tabBarIndex,在viewDidLoad
中设置self.delegate = self
给你,这应该有效:
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let viewControllers = viewControllers else { return false }
if viewController == viewControllers[selectedIndex] {
if let nav = viewController as? ZBNavigationController {
guard let topController = nav.viewControllers.last else { return true }
if !topController.isScrolledToTop {
topController.scrollToTop()
return false
} else {
nav.popViewController(animated: true)
}
return true
}
}
return true
}
然后...
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
// Changed this
var isScrolledToTop: Bool {
if self is UITableViewController {
return (self as! UITableViewController).tableView.contentOffset.y == 0
}
for subView in view.subviews {
if let scrollView = subView as? UIScrollView {
return (scrollView.contentOffset.y == 0)
}
}
return true
}
}
这个函数有一些额外的东西,所以如果 UIViewController
已经在顶部,它将弹出到前一个控制器
我只需要实现它,并惊讶地发现它并不像我想象的那么容易。我是这样实现的:
关于我的应用程序设置的一些注意事项
- MainTabBarcontroller 是我们的根视图控制器,我们通常从 UISharedApplication 访问它。
- 我们的导航结构是 MainTabBarController -> Nav Controller -> Visible View Controller
我在实现这个过程中发现的主要问题是,如果我已经在标签栏的第一个屏幕上,我只想滚动到顶部,但是一旦你尝试从 tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController)
进行检查,你'在您点击它之前,您已经丢失了对可见视图控制器的引用。在切换选项卡时比较视图控制器也是一个问题,因为可见视图控制器可能位于另一个选项卡中。
所以我在我的 TabBar class 中创建了一个 属性 来保存对 previousTopVC
的引用,并创建了一个协议来帮助我从当前设置 属性可见 VC。
protocol TopScreenFindable {
func setVisibleViewController()
}
extension TopScreenFindable where Self: UIViewController {
func setVisibleViewController() {
guard let tabController = UIApplication.shared.keyWindow?.rootViewController as? MainTabBarController else { return }
tabController.previousTopVC = self
}
}
然后我从视图控制器的 viewDidAppear 中调用了 conformed to this protocol 和 setVisibleViewController,现在在任何屏幕出现时都有对先前可见 VC 的引用。
我为我的 MainTabBarController 创建了一个委托 class
protocol MainTabBarDelegate: class {
func firstScreenShouldScrollToTop()
}
然后从 UITabBarControllerDelegate 方法调用它
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let navController = viewController as? UINavigationController, let firstVC = navController.viewControllers.first, let previousVC = previousTopController else { return }
if firstVC == previousVC {
mainTabBarDelegate?.firstScreenShouldScrollToTop()
}
}
并且在第一个视图控制器中符合 MainTabBarDelegate
extension FirstViewController: MainTabBarDelegate {
func firstScreenShouldScrollToTop() {
collectionView.setContentOffset(CGPoint(x: 0, y: collectionView.contentInset.top), animated: true)
}
我在 FirstViewController 的 viewDidAppear 上设置了 tabBar.mainTabBarDelegate = self
,以便每次出现屏幕时都设置委托。如果我在 viewDidLoad 上执行它,则在切换选项卡时将不起作用。
有几点我不喜欢我的方法。
- 在我的视图控制器中引用选项卡栏,以便它可以将自己设置为它的委托。
- 使应用程序中的每个相关屏幕都符合
TopScreenFindable
协议
您只需使用以下代码创建文件 TabBarViewController
:
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let viewControllers = viewControllers else { return false }
if viewController == viewControllers[selectedIndex] {
if let nav = viewController as? UINavigationController {
guard let topController = nav.viewControllers.last else { return true }
if !topController.isScrolledToTop {
topController.scrollToTop()
return false
} else {
nav.popViewController(animated: true)
}
return true
}
}
return true
}
}
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
var isScrolledToTop: Bool {
if self is UITableViewController {
return (self as! UITableViewController).tableView.contentOffset.y == 0
}
for subView in view.subviews {
if let scrollView = subView as? UIScrollView {
return (scrollView.contentOffset.y == 0)
}
}
return true
}
}
然后在你的故事板中,像这样设置自定义 class TabBarController
:
很好的例子!我也尝试了一些东西,我想我们的委托的这段小代码就是我们所需要的:
extension AppDelegate: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// Scroll to top when corresponding view controller was already selected and no other viewcontrollers are pushed
guard tabBarController.viewControllers?[tabBarController.selectedIndex] == viewController,
let navigationController = viewController as? UINavigationController, navigationController.viewControllers.count == 1,
let topViewController = navigationController.viewControllers.first,
let scrollView = topViewController.view.subviews.first(where: { [=10=] is UIScrollView }) as? UIScrollView else {
return true
}
scrollView.scrollRectToVisible(CGRect(origin: .zero, size: CGSize(width: 1, height: 1)), animated: true)
return false
}
}
我在使用此功能时遇到问题,我想获得一些帮助。
我的等级是 TabBarController
-> Navigation Controller
-> TableViewController
我想要的是,如果您在当前选项卡上并向下滚动,您将能够点击当前视图的 UITabBarItem
,然后您将滚动回到顶部,例如 Instagram 和 Twitter 所做的.
我在这里尝试了很多东西:
Older Question
但遗憾的是,没有一个答案适合我。
对于这种方式的任何帮助,我将不胜感激, 提前致谢!
这是我的 TableView`controller 代码:
import UIKit
class BarsViewController: UITableViewController,UISearchResultsUpdating,UISearchBarDelegate,UISearchDisplayDelegate,UITabBarControllerDelegate{
//TableView Data & non related stuff....
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.searchController.searchBar.resignFirstResponder()
self.searchController.searchBar.endEditing(true)
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
let indexPath = IndexPath(row: 0, section: 0)
let navigVC = viewController as? UINavigationController
let finalVC = navigVC?.viewControllers[0] as? BarsViewController
finalVC?.tableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
}
}
TabBarController.Swift 代码(代码无效):
import UIKit
class TabBarController: UITabBarController,UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let viewControllers = viewControllers else { return false }
if viewController == viewControllers[selectedIndex] {
if let nav = viewController as? UINavigationController {
guard let topController = nav.viewControllers.last else { return true }
if !topController.isScrolledToTop {
topController.scrollToTop()
return false
} else {
nav.popViewController(animated: true)
}
return true
}
}
return true
}
}
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
var isScrolledToTop: Bool {
for subView in view.subviews {
if let scrollView = subView as? UIScrollView {
return (scrollView.contentOffset.y == 0)
}
}
return true
}
}
在您的 TabViewController 中尝试此代码:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
let indexPath = NSIndexPath(row: 0, section: 0)
let navigVC = viewController as? UINavigationController
let finalVC = navigVC?.viewControllers[0] as? YourVC
finalVC?.tableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
}
此外,您的 TabViewController 应该继承自 UITabBarControllerDelegate
最终代码:
import UIKit
class tabViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
let indexPath = NSIndexPath(row: 0, section: 0)
let navigVC = viewController as? UINavigationController
let finalVC = navigVC?.viewControllers[0] as? YourVC
finalVC?.tableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
}
}
记得修改tabBarIndex,在viewDidLoad
中设置self.delegate = self给你,这应该有效:
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let viewControllers = viewControllers else { return false }
if viewController == viewControllers[selectedIndex] {
if let nav = viewController as? ZBNavigationController {
guard let topController = nav.viewControllers.last else { return true }
if !topController.isScrolledToTop {
topController.scrollToTop()
return false
} else {
nav.popViewController(animated: true)
}
return true
}
}
return true
}
然后...
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
// Changed this
var isScrolledToTop: Bool {
if self is UITableViewController {
return (self as! UITableViewController).tableView.contentOffset.y == 0
}
for subView in view.subviews {
if let scrollView = subView as? UIScrollView {
return (scrollView.contentOffset.y == 0)
}
}
return true
}
}
这个函数有一些额外的东西,所以如果 UIViewController
已经在顶部,它将弹出到前一个控制器
我只需要实现它,并惊讶地发现它并不像我想象的那么容易。我是这样实现的:
关于我的应用程序设置的一些注意事项
- MainTabBarcontroller 是我们的根视图控制器,我们通常从 UISharedApplication 访问它。
- 我们的导航结构是 MainTabBarController -> Nav Controller -> Visible View Controller
我在实现这个过程中发现的主要问题是,如果我已经在标签栏的第一个屏幕上,我只想滚动到顶部,但是一旦你尝试从 tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController)
进行检查,你'在您点击它之前,您已经丢失了对可见视图控制器的引用。在切换选项卡时比较视图控制器也是一个问题,因为可见视图控制器可能位于另一个选项卡中。
所以我在我的 TabBar class 中创建了一个 属性 来保存对 previousTopVC
的引用,并创建了一个协议来帮助我从当前设置 属性可见 VC。
protocol TopScreenFindable {
func setVisibleViewController()
}
extension TopScreenFindable where Self: UIViewController {
func setVisibleViewController() {
guard let tabController = UIApplication.shared.keyWindow?.rootViewController as? MainTabBarController else { return }
tabController.previousTopVC = self
}
}
然后我从视图控制器的 viewDidAppear 中调用了 conformed to this protocol 和 setVisibleViewController,现在在任何屏幕出现时都有对先前可见 VC 的引用。
我为我的 MainTabBarController 创建了一个委托 class
protocol MainTabBarDelegate: class {
func firstScreenShouldScrollToTop()
}
然后从 UITabBarControllerDelegate 方法调用它
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let navController = viewController as? UINavigationController, let firstVC = navController.viewControllers.first, let previousVC = previousTopController else { return }
if firstVC == previousVC {
mainTabBarDelegate?.firstScreenShouldScrollToTop()
}
}
并且在第一个视图控制器中符合 MainTabBarDelegate
extension FirstViewController: MainTabBarDelegate {
func firstScreenShouldScrollToTop() {
collectionView.setContentOffset(CGPoint(x: 0, y: collectionView.contentInset.top), animated: true)
}
我在 FirstViewController 的 viewDidAppear 上设置了 tabBar.mainTabBarDelegate = self
,以便每次出现屏幕时都设置委托。如果我在 viewDidLoad 上执行它,则在切换选项卡时将不起作用。
有几点我不喜欢我的方法。
- 在我的视图控制器中引用选项卡栏,以便它可以将自己设置为它的委托。
- 使应用程序中的每个相关屏幕都符合
TopScreenFindable
协议
您只需使用以下代码创建文件 TabBarViewController
:
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let viewControllers = viewControllers else { return false }
if viewController == viewControllers[selectedIndex] {
if let nav = viewController as? UINavigationController {
guard let topController = nav.viewControllers.last else { return true }
if !topController.isScrolledToTop {
topController.scrollToTop()
return false
} else {
nav.popViewController(animated: true)
}
return true
}
}
return true
}
}
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
var isScrolledToTop: Bool {
if self is UITableViewController {
return (self as! UITableViewController).tableView.contentOffset.y == 0
}
for subView in view.subviews {
if let scrollView = subView as? UIScrollView {
return (scrollView.contentOffset.y == 0)
}
}
return true
}
}
然后在你的故事板中,像这样设置自定义 class TabBarController
:
很好的例子!我也尝试了一些东西,我想我们的委托的这段小代码就是我们所需要的:
extension AppDelegate: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// Scroll to top when corresponding view controller was already selected and no other viewcontrollers are pushed
guard tabBarController.viewControllers?[tabBarController.selectedIndex] == viewController,
let navigationController = viewController as? UINavigationController, navigationController.viewControllers.count == 1,
let topViewController = navigationController.viewControllers.first,
let scrollView = topViewController.view.subviews.first(where: { [=10=] is UIScrollView }) as? UIScrollView else {
return true
}
scrollView.scrollRectToVisible(CGRect(origin: .zero, size: CGSize(width: 1, height: 1)), animated: true)
return false
}
}