点击标签栏以滚动到UITableViewController的顶部

时间:2014-03-13 22:16:18

标签: ios objective-c iphone uitableview uitabbarcontroller

点击当前导航控制器的标签栏图标已经将用户返回到根视图,但是如果它们向下滚动,如果再次点击它我希望它滚动到顶部(与点击状态相同的效果酒吧)。我该怎么做?

一个很好的例子是Instagram的Feed,向下滚动然后点击标签栏中的主页图标以滚动回到顶部。

滚动到顶部很容易,但将它连接到标签栏控制器是我所坚持的。

13 个答案:

答案 0 :(得分:34)

实施UITabBarControllerDelegate方法tabBarController:didSelectViewController:,以便在用户选择标签时收到通知。当再次点击相同的选项卡按钮时,也会调用此方法,即使该选项卡已被选中。

实施此delegate的好地方可能是您的AppDelegate。或者逻辑上拥有的对象"标签栏控制器。

我会声明并实现一个方法,可以在视图控制器上调用以滚动UICollectionView

- (void)tabBarController:(UITabBarController *)tabBarController 
 didSelectViewController:(UIViewController *)viewController
{
    static UIViewController *previousController = nil;
    if (previousController == viewController) {
        // the same tab was tapped a second time
        if ([viewController respondsToSelector:@selector(scrollToTop)]) {
            [viewController scrollToTop];
        }
    }
    previousController = viewController;
}

答案 1 :(得分:20)

SWIFT 3

这里有..

首先在类中实现UITabBarControllerDelegate并确保在viewDidLoad中设置委托

class DesignStoryStreamVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UITabBarControllerDelegate {

 @IBOutlet weak var collectionView: UICollectionView!

 override func viewDidLoad() {
        super.viewDidLoad()

        self.tabBarController?.delegate = self

        collectionView.delegate = self
        collectionView.dataSource = self
    }
}

接下来,将此委托函数放在您的类中的某个位置。

func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {

    let tabBarIndex = tabBarController.selectedIndex

    print(tabBarIndex)

    if tabBarIndex == 0 {
        self.collectionView.setContentOffset(CGPoint.zero, animated: true)
    }
}

确保在“if”语句中选择正确的索引。我包含了打印功能,因此您可以仔细检查。

答案 2 :(得分:5)

我正在使用此View层次结构。

rbindlist

我在[{1}}

中引用了UITabBarController > UINavigationController > UIViewController
UITabBarController

然后我创建了一个在正确的时间调用的UIViewController,以及一个允许滚动到顶部的方法

tabBarControllerRef = self.tabBarController as! CustomTabBarClass
tabBarControllerRef!.navigationControllerRef = self.navigationController as! CustomNavigationBarClass
tabBarControllerRef!.viewControllerRef = self

然后在我的Bool自定义类我调用了这个

var canScrollToTop:Bool = true

// Called when the view becomes available
override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    canScrollToTop = true
}

// Called when the view becomes unavailable
override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    canScrollToTop = false
}

// Scrolls to top nicely
func scrollToTop() {
    self.collectionView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
}

结果与Instagram和Twitter的Feed相同:)

答案 3 :(得分:4)

 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: self.view)
    }

}

这是我在Swift 3中的答案。它使用辅助函数进行递归调用,并在调用时自动滚动到顶部。在嵌入UITabBarController中嵌入的UINavigationController的UICollectionViewController上测试

答案 4 :(得分:4)

Swift 3方法::

//MARK: Properties
var previousController: UIViewController?

func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
    if self.previousController == viewController || self.previousController == nil {
        // the same tab was tapped a second time
        let nav = viewController as! UINavigationController

        // if in first level of navigation (table view) then and only then scroll to top
        if nav.viewControllers.count < 2 {
            let tableCont = nav.topViewController as! UITableViewController
            tableCont.tableView.setContentOffset(CGPoint(x: 0.0, y: -tableCont.tableView.contentInset.top), animated: true)
        }
    }
    self.previousController = viewController;
    return true
}

这里有几点注意事项:: “shouldSelect”而不是“didSelect”,因为后者是在转换后发生的,这意味着viewController本地var已经改变。 2.我们需要在更改控制器之前处理事件,以便获得有关滚动(或不)动作的导航视图控制器的信息。

说明::如果当前视图实际上是List / Table视图控制器,我们想要滚动到顶部。如果导航已经提前并且我们点击相同的标签栏,则所需的操作将只是弹出一步(默认功能)而不是滚动到顶部。如果导航没有提前意味着我们仍然在表/列表控制器然后只有我们想要再次点击时滚动到顶部。 (Facebook从用户的个人资料中点击“Feed”时也会这样做。它只会返回到Feed而不会滚动到顶部。

答案 5 :(得分:3)

您可以使用shouldSelect而非didSelect,这将省略外部变量以跟踪前一个视图控制器的需要。

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
    if ([viewController isEqual:self] && [tabBarController.selectedViewController isEqual:viewController]) {
        // Do custom stuff here
    }
    return YES;
}

答案 6 :(得分:1)

我有一个嵌入在导航控制器中的集合视图,在Swift中可行。

var previousController: UIViewController?

func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
    if previousController == viewController {
        if let navVC = viewController as? UINavigationController, vc = navVC.viewControllers.first as? UICollectionViewController {
            vc.collectionView?.setContentOffset(CGPointZero, animated: true)
        }
    }
    previousController = viewController;
}

答案 7 :(得分:1)

我已经实现了一个插件&amp;玩你可以在你的项目中自由重用的UITabBarController。要启用滚动到顶部功能,您只需使用子类,不需要其他任何内容。

也应与Storyboard一起开箱即用。

代码:

/// A UITabBarController subclass that allows "scroll-to-top" gestures via tapping
/// tab bar items. You enable the functionality by simply subclassing.
class ScrollToTopTabBarController: UITabBarController, UITabBarControllerDelegate {

    /// Determines whether the scrolling capability's enabled.
    var scrollEnabled: Bool = true

    private var previousIndex = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        delegate = self
    }

    /*
     Always call "super" if you're overriding this method in your subclass.
     */
    func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
        guard scrollEnabled else {
            return
        }

        guard let index = viewControllers?.indexOf(viewController) else {
            return
        }

        if index == previousIndex {

            dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), { [weak self] () in

                guard let scrollView = self?.iterateThroughSubviews(self?.view) else {
                    return
                }

                dispatch_async(dispatch_get_main_queue(), {
                    scrollView.setContentOffset(CGPointZero, animated: true)
                })
            })
        }

        previousIndex = index
    }

    /*
     Iterates through the view hierarchy in an attempt to locate a UIScrollView with "scrollsToTop" enabled.
     Since the functionality relies on "scrollsToTop", it plugs easily into existing architectures - you can
     control the behaviour by modifying "scrollsToTop" on your UIScrollViews.
     */
    private func iterateThroughSubviews(parentView: UIView?) -> UIScrollView? {
        guard let view = parentView else {
            return nil
        }

        for subview in view.subviews {
            if let scrollView = subview as? UIScrollView where scrollView.scrollsToTop == true {
                return scrollView
            }

            if let scrollView = iterateThroughSubviews(subview) {
                return scrollView
            }
        }

        return nil
    }
}

编辑(09.08.2016):

尝试使用默认的Release配置(归档)进行编译后,编译器不允许创建在递归函数中捕获的大量闭包,因此无法编译。更改了代码以返回第一个找到的UIScrollView,其中“scrollsToTop”设置为true而不使用闭包。

答案 8 :(得分:1)

在此实现中,您不需要静态变量和以前的视图控制器状态

如果您在UINavigationController中使用UITableViewController,则可以实现协议和功能:

protocol ScrollableToTop {
    func scrollToTop()
}

extension UIScrollView {
    func scrollToTop(_ animated: Bool) {
        var topContentOffset: CGPoint
        if #available(iOS 11.0, *) {
            topContentOffset = CGPoint(x: -safeAreaInsets.left, y: -safeAreaInsets.top)
        } else {
            topContentOffset = CGPoint(x: -contentInset.left, y: -contentInset.top)
        }
        setContentOffset(topContentOffset, animated: animated)
    }
}

然后在您的UITableViewController中:

class MyTableViewController: UITableViewController: ScrollableToTop {
   func scrollToTop() {
    if isViewLoaded {
        tableView.scrollToTop(true)
    }
   }
}

然后在UITabBarControllerDelegate中:

extension MyTabBarController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
        guard tabBarController.selectedViewController === viewController else { return true }
        guard let navigationController = viewController as? UINavigationController else {
            assertionFailure()
            return true
        }
        guard
            navigationController.viewControllers.count <= 1,
            let destinationViewController = navigationController.viewControllers.first as? ScrollableToTop
        else {
            return true
        }
        destinationViewController.scrollToTop()
        return false
    }
}

答案 9 :(得分:0)

我发现scrollRectToVisible方法比setContentOffset效果更好。

<强>夫特:

从代理中点击标签栏上的点击后,如下所示:

func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
 if (viewController.isKindOfClass(SomeControllerClass) && tabBarController.selectedIndex == 0) 
      {
        viewController.scrollToTop()
      }
 }

现在控制器内的scrollToTop功能:

func scrollToTop()
{
    self.tableView.scrollRectToVisible(CGRectMake(0,0,CGRectGetWidth(self.tableView.frame), CGRectGetHeight(self.tableView.frame)), animated: true)
} 

答案 10 :(得分:0)

快捷键5::无需在UITabBarController中存储属性。

MyTabBarController.swift 中,实现tabBarController(_:shouldSelect)以检测用户何时重新选择选项卡栏项:

protocol TabBarReselectHandling {
    func handleReselect()
}

class MyTabBarController: UITabBarController, UITabBarControllerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }

    func tabBarController(
        _ tabBarController: UITabBarController,
        shouldSelect viewController: UIViewController
    ) -> Bool {
        if tabBarController.selectedViewController === viewController,
            let handler = viewController as? TabBarReselectHandling {
            // NOTE: viewController in line above might be a UINavigationController,
            // in which case you need to access its contents
            handler.handleReselect()
        }

        return true
    }
}

MyTableViewController.swift 中,通过将表视图滚动到顶部来处理重新选择:

class MyTableViewController: UITableViewController, TabBarReselectHandling {
    func handleReselect() {
        tableView?.setContentOffset(.zero, animated: true)
    }
}

现在,您只需实施TabBarReselectHandling就可以轻松地将其扩展到其他标签。

答案 11 :(得分:0)

我尝试了@jsanabria提供的解决方案。这在固定的tableview上效果很好,但是对我的无限滚动tableview无效。在加载新的滚动数据之后,它仅在表视图的一半位置出现。

Swift 5.0 +

self.tableView.scrollToRow(at: IndexPath.init(row: 0, section: 0), at: UITableView.ScrollPosition(rawValue: 0)!, animated: true)

答案 12 :(得分:0)

已在 Swift 中测试的解决方案

第 1 步

在你的主 tabbarcontroller 类中声明

weak static var previousController: UIViewController?

第 2 步

在 viewdidLoad() 设置中

MainTabBarViewController.previousController = viewControllers?[0]

第 3 步

extension MainTabBarViewController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        if MainTabBarViewController.previousController == viewController {
         /// here comes your code
        }
        MainTabBarViewController.previousController = viewController
    }
}