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.
Implémentez la méthode UITabBarControllerDelegate
tabBarController: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;
}
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.
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 :)
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.
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;
}
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
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;
}
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.
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
}
}
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)
}