Нажмите панель вкладок, чтобы перейти к верхней части UITableViewController

Прикосновение к значку панели вкладок для текущего контроллера навигации уже возвращает пользователя в корневой режим, но если они прокручиваются вниз, если они снова коснутся его, я хочу, чтобы он прокручивался вверх (тот же эффект, что и при нажатии на строку состояния). Как мне это сделать?

Хорошим примером является подача Instagram, прокрутка вниз, а затем нажмите значок дома на панели вкладок, чтобы прокрутить назад.

Прокрутка назад к вершине проста, но подключение ее к контроллеру панели вкладок - вот то, что я застрял.

Ответы

Ответ 1

Реализация UITabBarControllerDelegate метода tabBarController: didSelectViewController: получать уведомления, когда пользователь выбирает вкладку. Этот метод также вызывается, когда одна и та же кнопка табуляции снова используется, даже если эта вкладка уже выбрана.

Хорошим местом для реализации этого делегата, вероятно, будет ваш AppDelegate. Или объект, который логически "владеет" контроллером панели вкладок.

Я бы объявил и реализую метод, который можно вызвать на контроллерах вашего вида, чтобы прокрутить представление коллекции.

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

Ответ 2

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". Я включил функцию печати, чтобы вы могли дважды проверить.

Ответ 3

Я использовал эту иерархию View.

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

Затем в моем пользовательском классе 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()
        }
    }
}

Результат идентичен Feed Instagram и Twitter :)

Ответ 4

Вы можете использовать shouldSelect а не didSelect, что бы опустить необходимость в том, чтобы внешняя переменная отслеживала предыдущий контроллер представления.

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

Ответ 5

 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. Он использует вспомогательную функцию для рекурсивных вызовов, и она автоматически прокручивается вверх по вызову. Протестировано на UICollectionViewController, встроенном в UINavigationController, встроенном в UITabBarController

Ответ 6

Подход 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 уже изменена. 2. Нам нужно обработать событие перед сменой контроллера, чтобы иметь информацию о диспетчерах навигации, касающуюся действия прокрутки (или нет).

Объяснение :: Мы хотим прокрутить вверх, если текущее представление на самом деле является контроллером представления List/Table. Если продвижение навигации продвинулось, и мы коснемся той же панели вкладок, желаемое действие будет состоять только в том, чтобы просто нажать один шаг (функция по умолчанию), а не прокручивать вверх. Если навигация не имеет никакого значения, мы все еще находимся в контроллере таблицы/списка, и только тогда мы хотим прокручивать вверх, когда снова нажимаем. (То же самое происходит с Facebook, когда вы нажимаете "Feed" из пользовательского профиля. Он возвращается только к фиду без прокрутки вверх.

Ответ 7

У меня есть представление коллекции, встроенное в контроллер навигации, в 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;
}

Ответ 8

Я реализовал подключаемый модуль UITabBarController, который вы можете свободно использовать в своих проектах. Чтобы включить функцию прокрутки вверх, вам просто нужно использовать подкласс, и ничего больше.

Должен также работать из коробки с раскадровки.

Код:

/// 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 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, без использования закрытий.

Ответ 9

Я обнаружил, что метод scrollRectToVisible работает лучше, чем setContentOffset.

Swift:

После того как вы поймаете щелчок на панели вкладок от делегата, что-то вроде ниже:

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