Tap tab bar to scroll to top of UITableViewController

后端 未结 12 934
面向向阳花
面向向阳花 2020-12-23 10:15

Tapping the tab bar icon for the current navigation controller already returns the user to the root view, but if they are scrolled way down, if they tap it again I want it t

相关标签:
12条回答
  • 2020-12-23 10:38

    I tried the solution given by @jsanabria. This worked well on a fixed tableview, but it wouldn't work for my infinite scroll tableview. It only came up the table view about halfway after loading the new scrolling data.

    Swift 5.0+

    self.tableView.scrollToRow(at: IndexPath.init(row: 0, section: 0), at: UITableView.ScrollPosition(rawValue: 0)!, animated: true)
    
    0 讨论(0)
  • 2020-12-23 10:43

    I've implemented a plug & play UITabBarController that you can freely re-use in your projects. To enable the scroll-to-top functionality, you should just have to use the subclass, nothing else.

    Should work out of the box with Storyboards also.

    Code:

    /// 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
        }
    }
    

    Edit (09.08.2016):

    After attempting to compile with the default Release configuration (archiving) the compiler would not allow the possibility of creating a large number of closures that were captured in a recursive function, thus it would not compile. Changed out the code to return the first found UIScrollView with "scrollsToTop" set to true without using closures.

    0 讨论(0)
  • 2020-12-23 10:46

    I was using this View hierarchy.

    UITabBarController > UINavigationController > UIViewController


    I got a reference to the UITabBarController in the UIViewController

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

    Then I created a Bool that was called at the correct times, and a method that allows scrolling to top smoothly

    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)
    }
    

    Then in my UITabBarController Custom Class I called this

    func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
    
        // Allows scrolling to top on second tab bar click
        if (viewController.isKindOfClass(CustomNavigationBarClass) && tabBarController.selectedIndex == 0) {
            if (viewControllerRef!.canScrollToTop) {
                viewControllerRef!.scrollToTop()
            }
        }
    }
    

    The Result is identical to Instagram and Twitter's feed :)

    0 讨论(0)
  • 2020-12-23 10:47

    You can use shouldSelect rather than didSelect, which would omit the need for an external variable to keep track of the previous view controller.

    - (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
    {
        if ([viewController isEqual:self] && [tabBarController.selectedViewController isEqual:viewController]) {
            // Do custom stuff here
        }
        return YES;
    }
    
    0 讨论(0)
  • 2020-12-23 10:50
     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)
        }
    
    }
    

    This is my answer in Swift 3. It uses a helper function for recursive calls and it automatically scrolls to top on call. Tested on a UICollectionViewController embedded into a UINavigationController embedded in a UITabBarController

    0 讨论(0)
  • 2020-12-23 10:50

    Swift 3 approach::

    //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
    }
    

    A few notes here:: "shouldSelect" instead of "didSelect" because the latter is taking place after transition, meaning viewController local var already changed. 2. We need to handle the event before changing controller, in order to have the information of navigation's view controllers regarding scrolling (or not) action.

    Explanation:: We want to scroll to top, if current view is actually a List/Table view controller. If navigation has advanced and we tap same tab bar, desired action would be to just pop one step (default functionality) and not scroll to top. If navigation hasn't advanced meaning we are still in table/list controller then and only then we want to scroll to top when tapping again. (Same thing Facebook does when tapping "Feed" from a user's profile. It only goes back to feed without scrolling to top.

    0 讨论(0)
提交回复
热议问题