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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22392151/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 23:44:06  来源:igfitidea点击:

Tap tab bar to scroll to top of UITableViewController

iosobjective-ciphoneuitableviewuitabbarcontroller

提问by Jonathan Sutherland

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 to scroll to the top (same effect as tapping the status bar). How would I do this?

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

A good example is Instagram's feed, scroll down then tap the home icon in the tab bar to scroll back to top.

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

The scrolling back to the top is easy, but connecting it to the tab bar controller is what I'm stuck on.

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

回答by DrummerB

Implement the UITabBarControllerDelegatemethod tabBarController:didSelectViewController:to be notified when the user selects a tab. This method is also called when the same tab button is tapped again, even if that tab is already selected.

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

A good place to implement this delegatewould probably be your AppDelegate. Or the object that logically "owns" the tab bar controller.

实现这一点的好地方delegate可能是您的AppDelegate. 或者逻辑上“拥有”标签栏控制器的对象。

I would declare and implement a method that can be called on your view controllers to scroll the UICollectionView.

我会声明并实现一个方法,该方法可以在您的视图控制器上调用以滚动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;
}

回答by jsanabria

SWIFT 3

斯威夫特 3

Here goes..

开始..

First implement the UITabBarControllerDelegatein the class and make sure the delegate is set in viewDidLoad

首先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
    }
}

Next, put this delegate function somewhere in your class.

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

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

    let tabBarIndex = tabBarController.selectedIndex

    print(tabBarIndex)

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

Make sure to select the correct index in the "if" statement. I included the print function so you can double check.

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

回答by Alessandro Martin

 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

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

回答by Michael

I was using this View hierarchy.

我正在使用这个视图层次结构。

UITabBarController > UINavigationController > UIViewController

UITabBarController > UINavigationController > UIViewController



I got a reference to the UITabBarControllerin the UIViewController

我有一个参考UITabBarControllerUIViewController

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

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

然后我创建了一个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)
}


Then in my UITabBarControllerCustom Class I called this

然后在我的UITabBarController自定义类中我称之为

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

结果与 Instagram 和 Twitter 的提要相同 :)

回答by James Kuang

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

您可以使用shouldSelect而不是didSelect,这样就不需要外部变量来跟踪前一个视图控制器。

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

回答by Jimi

Swift 3 approach::

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
}

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.

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

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 thenwe 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.

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

回答by Alexander

In this implementation you no need static variableand previous view controller state

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

If your UITableViewController in UINavigationController you can implement protocol and function:

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

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

Then in your UITableViewController:

然后在你的 UITableViewController 中:

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

Then in UITabBarControllerDelegate:

然后在 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
    }
}

回答by Chris Chute

Swift 5:no need for stored properties in the UITabBarController.

Swift 5:不需要在UITabBarController.

In MyTabBarController.swift, implement tabBarController(_:shouldSelect)to detect when the user re-selects the tab bar item:

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

In MyTableViewController.swift, handle the re-selection by scrolling the table view to the top:

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

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

Now you can easily extend this to other tabs by just implementing TabBarReselectHandling.

现在,您只需实现TabBarReselectHandling.

回答by D6mi

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.

我已经实现了一个即插即用的 UITabBarController,你可以在你的项目中自由地重用它。要启用滚动到顶部功能,您应该只需要使用子类,别无其他。

Should work out of the box with Storyboards also.

也应该与 Storyboards 一起开箱即用。

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

编辑 (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.

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

回答by Niklas

I have a collection view embedded in a navigation controller, in Swift this works.

我有一个嵌入在导航控制器中的集合视图,在 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;
}