web-dev-qa-db-fra.com

Appuyez sur la barre d'onglets pour faire défiler UITableViewController

Si vous appuyez sur l'icône de la barre d'onglets du contrôleur de navigation actuel, l'utilisateur revient déjà à la vue racine. Toutefois, s'il défile de nouveau, s'il le fait de nouveau, je souhaite le faire défiler jusqu'en haut (même effet que d'appuyer sur la barre d'état). Comment je ferais ça? 

Un bon exemple est le flux Instagram, faites défiler l'écran, puis appuyez sur l'icône d'accueil dans la barre d'onglets pour revenir au début.

Le défilement vers le haut est facile, mais je suis coincé en le connectant au contrôleur de la barre d'onglets.

24

Implémentez la méthode UITabBarControllerDelegatetabBarController:didSelectViewController: pour être averti lorsque l'utilisateur sélectionne un onglet. Cette méthode est également appelée lorsque le même bouton d'onglet est à nouveau activé, même si cet onglet est déjà sélectionné.

Un bon endroit pour implémenter cette delegate serait probablement votre AppDelegate. Ou l'objet qui "possède" logiquement le contrôleur de barre d'onglets.

Je déclarerais et implémenterais une méthode qui peut être appelée sur vos contrôleurs de vue pour faire défiler la 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;
}
32
DrummerB

Swift 3

Voici..

Commencez par implémenter UITabBarControllerDelegate dans la classe et assurez-vous que le délégué est défini dans 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
    }
}

Ensuite, placez cette fonction de délégué quelque part dans votre classe. 

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

    let tabBarIndex = tabBarController.selectedIndex

    print(tabBarIndex)

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

Assurez-vous de sélectionner l'index correct dans l'instruction "if". J'ai inclus la fonction d'impression afin que vous puissiez vérifier.

18
jsanabria

J'utilisais cette hiérarchie de vues.

UITabBarController > UINavigationController > UIViewController


J'ai reçu une référence à la UITabBarController dans la UIViewController

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

Ensuite, j'ai créé une Bool qui a été appelée aux moments appropriés, ainsi qu'une méthode permettant de faire défiler en douceur

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

Ensuite, dans ma UITabBarController Custom Class, j'ai appelé cela

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

Le résultat est identique aux flux Instagram et Twitter :)

5
Michael

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

Quelques notes ici :: "ShouldSelect" au lieu de "didSelect" car cette dernière a lieu après la transition, ce qui signifie que viewController var var a déjà changé . 2. Nous devons gérer l'événement avant de changer de contrôleur, afin de disposer des informations des contrôleurs de vue de la navigation concernant l'action de défilement (ou non).

Explication: Nous voulons faire défiler vers le haut, si la vue actuelle est en fait un contrôleur de vue Liste/Table. Si la navigation est avancée et que nous appuyons sur la même barre d’onglet, l’action souhaitée serait de n’écraser qu’une étape (fonctionnalité par défaut) et non de faire défiler vers le haut. Si la navigation n'a pas avancé, cela signifie que nous sommes toujours dans le tableau/contrôleur de liste alors et seulement alors nous voulons faire défiler vers le haut lorsque vous appuyez à nouveau. (Même chose que fait Facebook lorsque vous appuyez sur «Flux» à partir du profil d’un utilisateur. Il ne fait que revenir au flux sans défiler vers le haut.

4
Jimi

Vous pouvez utiliser shouldSelect au lieu de didSelect, ce qui éviterait le recours à une variable externe pour assurer le suivi du contrôleur de vue précédent.

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
    if ([viewController isEqual:self] && [tabBarController.selectedViewController isEqual:viewController]) {
        // Do custom stuff here
    }
    return YES;
}
3
James Kuang
 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)
    }

}

C’est ma réponse dans Swift 3. Il utilise une fonction d’aide pour les appels récursifs et fait défiler automatiquement l’appel au début. Testé sur un UICollectionViewController intégré à un UINavigationController intégré à un UITabBarController 

3
Alessandro Martin

J'ai une vue de collection intégrée dans un contrôleur de navigation, cela fonctionne dans 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;
}
1
Niklas

J'ai mis en place un UITabBarController plug & play que vous pouvez librement réutiliser dans vos projets. Pour activer la fonctionnalité de défilement vers le haut, vous devez simplement utiliser la sous-classe, rien d’autre. 

Devrait fonctionner aussi avec 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): 

Après avoir tenté de compiler avec la configuration par défaut de Release (archivage), le compilateur ne permet pas de créer un grand nombre de fermetures capturées dans une fonction récursive. Par conséquent, il ne compile pas. Le code a été modifié pour renvoyer le premier UIScrollView trouvé avec "scrollsToTop" défini sur true sans utiliser les fermetures.

1
D6mi

Dans cette implémentation, vous pas besoin de variable statique et l'état précédent du contrôleur

Si votre UITableViewController dans UINavigationController vous pouvez implémenter un protocole et une fonction:

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

Puis dans votre UITableViewController:

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

Puis dans 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
    }
}
0
Alexander

J'ai trouvé que la méthode scrollRectToVisible fonctionnait mieux que la setContentOffset

Rapide:

Une fois que vous avez cliqué sur la barre d’onglet du délégué, procédez comme suit:

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

Passons maintenant à la fonction scrollToTop dans le contrôleur:

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