Comment créer une UITabBar Swipeable à partir de zéro

Publié: 2022-03-11

Comme vous le savez, le SDK iOS d'Apple contient une myriade de composants d'interface utilisateur intégrés. Boutons, conteneurs, navigations, mises en page à onglets, etc. - presque tout ce dont vous aurez besoin est là. Ou est-ce?

Tous ces composants de base nous permettent de créer des interfaces utilisateur structurées de base, mais que se passe-t-il s'il est nécessaire de sortir des sentiers battus ? lorsqu'un développeur iOS a besoin de créer une sorte de comportement qui n'est pas pris en charge par défaut dans le SDK ?

L'un de ces cas est UITabBar , où vous n'avez pas la possibilité de balayer entre les onglets, et vous n'avez pas non plus d'animations pour basculer entre les onglets.

Recherche d'un correctif UITabBar facile

Après de nombreuses recherches, j'ai réussi à trouver une seule bibliothèque utile sur Github. Malheureusement, la bibliothèque a créé de nombreux problèmes lors de l'exécution de l'application, même si elle est apparue comme une solution élégante à première vue.

En d'autres termes, j'ai trouvé la bibliothèque très facile à utiliser, mais boguée, ce qui l'emportait évidemment sur sa facilité d'utilisation et avait tendance à poser des problèmes. Si vous êtes toujours intéressé, la bibliothèque se trouve sous ce lien.

Alors, après quelques réflexions et beaucoup de recherches, j'ai commencé à implémenter ma propre solution et je me suis dit : « Hé, et si on utilisait le contrôleur de vue de page pour le swipe, et l'UITabBar natif. Et si nous regroupions ces deux éléments, gérons l'index de la page tout en glissant ou en tapant sur la barre d'onglets ? »

En fin de compte, j'ai trouvé une solution, même si cela s'est avéré un peu délicat, comme je l'expliquerai plus tard.

Une solution UITabBar élaborée

Imaginez que vous ayez trois éléments de barre d'onglets à construire, ce qui signifie automatiquement que vous avez trois pages/contrôleurs à afficher pour chaque élément d'onglet.

Dans ce cas, vous devrez instancier ces trois contrôleurs de vue et vous aurez également besoin de deux espaces réservés/contrôleurs de vue vides pour la barre d'onglets, pour créer des éléments de barre d'onglets, changer leur état lorsque l'onglet est pressé ou lorsque l'utilisateur veut changer l'index de tabulation par programme.

Pour cela, creusons dans Xcode et écrivons quelques classes, juste pour voir comment ces choses fonctionnent.

Un exemple de balayage entre les onglets

Exemple d'onglets glissables dans iOS

Dans ces captures d'écran, vous pouvez voir que le premier élément de la barre d'onglets est bleu, puis l'utilisateur glisse vers l'onglet de droite, qui est jaune, et le dernier écran montre que le troisième élément est sélectionné, de sorte que toute la page s'affiche en jaune.

Utilisation programmatique de la barre d'onglets glissable

Alors, plongeons dans cette fonctionnalité et écrivons un exemple simple d'une barre d'onglets glissable pour iOS. Tout d'abord, nous devons créer un nouveau projet.

Les prérequis nécessaires à notre projet sont assez basiques : Xcode et les outils de build Xcode installés sur votre Mac.

Pour créer un nouveau projet, ouvrez l'application Xcode sur votre Mac et sélectionnez "Créer un nouveau projet Xcode", puis nommez votre projet, et enfin choisissez le type d'application à créer. Sélectionnez simplement "Application Single View" et appuyez sur Suivant.

Capture d'écran Xcode

Comme vous pouvez le voir, l'écran suivant vous demandera de fournir quelques informations de base :

  • Nom du produit : je l'ai nommé SwipeableTabbar.
  • Équipe : Si vous souhaitez exécuter cette application sur un appareil réel, vous devrez avoir un compte développeur. Dans mon cas, j'utiliserai mon propre compte pour cela.

Remarque : Si vous n'avez pas de compte développeur, vous pouvez également l'exécuter sur Simulator.

  • Nom de l'organisation : Je l'ai nommée Toptal .
  • Identifiant de l'organisation : je l'ai nommé com.toptal .
  • Langue : sélectionnez Swift.
  • Décochez : "Utiliser les données de base", "Inclure les tests unitaires" et "Inclure les tests d'interface utilisateur".

Appuyez sur le bouton Suivant et vous êtes prêt à commencer à créer votre barre d'onglets à faire glisser.

Architecture simplifiée

Comme vous le savez déjà, lorsque vous créez une nouvelle application, vous avez déjà la classe Main ViewController et Main.Storyboard .

Avant de commencer la conception, créons d'abord toutes les classes et tous les fichiers nécessaires pour nous assurer que tout est configuré et en cours d'exécution avant de passer à la partie interface utilisateur du travail.

Quelque part dans votre projet, créez simplement quelques nouveaux fichiers -> TabbarController.swift , NavigationController.swift , PageViewController.swift .

Dans mon cas, ça ressemble à ça.

Capture d'écran : Contrôleurs Xcode

Dans le fichier AppDelegate , ne laissez que didFinishLaunchingWithOptions , car vous pouvez supprimer toutes les autres méthodes.

Dans didFinishLaunchingWithOptions , copiez et collez simplement les lignes ci-dessous :

 window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = NavigationController(rootViewController: TabbarController()) window?.makeKeyAndVisible() return true

Supprimez tout du fichier appelé ViewController.swift . Nous reviendrons sur ce dossier ultérieurement.

Commençons par écrire le code pour NavigationController.swift .

 import Foundation import UIKit class NavigationController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() navigationBar.isTranslucent = true navigationBar.tintColor = .gray } }

Avec cela, nous venons de créer un simple UINavigationController , où nous avons une barre translucide avec un TintColor gris. C'est tout ici.

Maintenant, nous pouvons continuer à prendre le PageViewController .

Dans celui-ci, nous devons coder un peu plus que dans les fichiers précédents dont nous avons discuté.

Ce fichier contient une classe, un protocole, une source de données UIPageViewController et des méthodes déléguées.

Le fichier résultant doit ressembler à ceci :

Capture d'écran Xcode : Méthodes

Comme vous pouvez le voir, nous avons déclaré notre propre protocole appelé PageViewControllerDelegate , qui devrait indiquer au contrôleur de la barre d'onglets que l'index de la page a été modifié après la gestion du balayage.

 import Foundation import UIKit protocol PageViewControllerDelegate: class { func pageDidSwipe(to index: Int) }

Ensuite, nous devons créer une nouvelle classe, appelée PageViewController , qui contiendra nos contrôleurs de vue, sélectionnera des pages à un index spécifique et gérera également les balayages.

Imaginons que le premier contrôleur sélectionné lors de notre première exécution soit le contrôleur de la vue centrale. Dans ce cas, nous attribuons notre valeur d'index par défaut, égale à 1.

 class PageViewController: UIPageViewController { weak var swipeDelegate: PageViewControllerDelegate? var pages = [UIViewController]() var prevIndex: Int = 1 override func viewDidLoad() { super.viewDidLoad() self.dataSource = self self.delegate = self } func selectPage(at index: Int) { self.setViewControllers( [self.pages[index]], direction: self.direction(for: index), animated: true, completion: nil ) self.prevIndex = index } private func direction(for index: Int) -> UIPageViewController.NavigationDirection { return index > self.prevIndex ? .forward : .reverse } }

Comme vous pouvez le voir ici, nous avons des pages variables, qui contiendront les références de tous nos contrôleurs de vue.

La variable prevIndex est utilisée pour stocker le dernier index sélectionné.

Vous pouvez simplement appeler la méthode selectPage afin de définir l'index sélectionné.

Si vous souhaitez écouter les changements d'index de page, vous devez vous abonner à swipeDelegate , et à chaque balayage de page, vous serez averti que l'index de page a changé, et vous recevrez également l'index actuel.

La direction de la méthode renverra la direction de balayage de UIPageViewController . La dernière pièce du puzzle de cette classe sont les implémentations de délégué/source de données.

Heureusement, ces implémentations sont très simples.

 extension PageViewController: UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let viewControllerIndex = pages.firstIndex(of: viewController) else { return nil } let previousIndex = viewControllerIndex - 1 guard previousIndex >= 0 else { return nil } guard pages.count > previousIndex else { return nil } return pages[previousIndex] } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let viewControllerIndex = pages.firstIndex(of: viewController) else { return nil } let nextIndex = viewControllerIndex + 1 guard nextIndex < pages.count else { return nil } guard pages.count > nextIndex else { return nil } return pages[nextIndex] } } extension PageViewController: UIPageViewControllerDelegate { func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if completed { guard let currentPageIndex = self.viewControllers?.first?.view.tag else { return } self.prevIndex = currentPageIndex self.swipeDelegate?.pageDidSwipe(to: currentPageIndex) } } }

Comme vous pouvez le voir ci-dessus, il existe trois méthodes en jeu :

  • Le premier trouve l'index et renvoie le contrôleur de vue précédent.
  • Le second trouve l'index et renvoie le contrôleur de vue suivant.
  • Le dernier vérifie si le balayage est terminé, définit l'index actuel sur la propriété locale prevIndex , puis appelle la méthode déléguée pour informer le contrôleur de vue parent que le balayage s'est terminé avec succès.

Nous pouvons enfin écrire notre implémentation UITabBarController :

 import UIKit class TabbarController: UITabBarController { let selectedColor = UIColor.blue let deselectedColor = UIColor.gray let tabBarImages = [ UIImage(named: "ic_music")!, UIImage(named: "ic_play")!, UIImage(named: "ic_star")! ] override func viewDidLoad() { view.backgroundColor = .gray self.delegate = self tabBar.isTranslucent = true tabBar.tintColor = deselectedColor tabBar.unselectedItemTintColor = deselectedColor tabBar.barTintColor = UIColor.white.withAlphaComponent(0.92) tabBar.itemSpacing = 10.0 tabBar.itemWidth = 76.0 tabBar.itemPositioning = .centered setUp() self.selectPage(at: 1) } }

Comme vous pouvez le voir, nous créons le TabbarController , avec les propriétés et le style par défaut. Nous devons définir deux couleurs, pour les éléments de barre sélectionnés et désélectionnés. De plus, j'ai introduit trois images pour les éléments de la barre d'onglets.

Dans viewDidLoad , je configure simplement la configuration par défaut de notre barre d'onglets et sélectionne la page #1. Cela signifie que la page de démarrage sera la page numéro un.

 private func setUp() { guard let centerPageViewController = createCenterPageViewController() else { return } var controllers: [UIViewController] = [] controllers.append(createPlaceholderViewController(forIndex: 0)) controllers.append(centerPageViewController) controllers.append(createPlaceholderViewController(forIndex: 2)) setViewControllers(controllers, animated: false) selectedViewController = centerPageViewController } private func selectPage(at index: Int) { guard let viewController = self.viewControllers?[index] else { return } self.handleTabbarItemChange(viewController: viewController) guard let PageViewController = (self.viewControllers?[1] as? PageViewController) else { return } PageViewController.selectPage(at: index) }

Dans la méthode setUp, vous voyez que nous avons créé deux contrôleurs de vue d'espace réservé. Ces contrôleurs d'espace réservé sont nécessaires pour UITabBar car le nombre d' éléments de la barre d'onglets doit être égal au nombre de contrôleurs de vue dont vous disposez.

Si vous vous en souvenez, nous utilisons UIPageViewController pour afficher les contrôleurs, mais pour UITabBar , si nous voulons le rendre entièrement fonctionnel, nous devons avoir tous les contrôleurs de vue instanciés, afin que les éléments de la barre fonctionnent lorsque vous appuyez dessus. Ainsi, dans cet exemple, placeholderviewcontroller #0 et #2 sont des contrôleurs de vue vides.

En tant que contrôleur de vue centré, nous créons un PageViewController avec trois contrôleurs de vue.

 private func createPlaceholderViewController(forIndex index: Int) -> UIViewController { let emptyViewController = UIViewController() emptyViewController.tabBarItem = tabbarItem(at: index) emptyViewController.view.tag = index return emptyViewController } private func createCenterPageViewController() -> UIPageViewController? { let leftController = ViewController() let centerController = ViewController2() let rightController = ViewController3() leftController.view.tag = 0 centerController.view.tag = 1 rightController.view.tag = 2 leftController.view.backgroundColor = .red centerController.view.backgroundColor = .blue rightController.view.backgroundColor = .yellow let storyBoard = UIStoryboard.init(name: "Main", bundle: nil) guard let pageViewController = storyBoard.instantiateViewController(withIdentifier: "PageViewController") as? PageViewController else { return nil } pageViewController.pages = [leftController, centerController, rightController] pageViewController.tabBarItem = tabbarItem(at: 1) pageViewController.view.tag = 1 pageViewController.swipeDelegate = self return pageViewController } private func tabbarItem(at index: Int) -> UITabBarItem { return UITabBarItem(title: nil, image: self.tabBarImages[index], selectedImage: nil) }

La première et la deuxième méthode décrites ci-dessus sont des méthodes d'initialisation de notre contrôleur de pages vues .

L'élément de la barre d'onglets de la méthode tabbar simplement l'élément de la barre d' tabbar à l'index.

Comme vous pouvez le voir, dans createCenterPageViewController() , j'utilise des balises pour chaque contrôleur de vue. Cela m'aide à comprendre quelle manette est apparue à l'écran.

Ensuite, nous arrivons à ce qui est peut-être notre méthode la plus importante, handleTabbarItemChange .

 private func handleTabbarItemChange(viewController: UIViewController) { guard let viewControllers = self.viewControllers else { return } let selectedIndex = viewController.view.tag self.tabBar.tintColor = selectedColor self.tabBar.unselectedItemTintColor = selectedColor for i in 0..<viewControllers.count { let tabbarItem = viewControllers[i].tabBarItem let tabbarImage = self.tabBarImages[i] tabbarItem?.selectedImage = tabbarImage.withRenderingMode(.alwaysTemplate) tabbarItem?.image = tabbarImage.withRenderingMode( i == selectedIndex ? .alwaysOriginal : .alwaysTemplate ) } if selectedIndex == 1 { viewControllers[selectedIndex].tabBarItem.selectedImage = self.tabBarImages[1].withRenderingMode(.alwaysOriginal) } }

Dans cette méthode, j'utilise le contrôleur de vue comme paramètre. À partir de ce contrôleur de vue, je reçois une balise en tant qu'index sélectionné. Pour la barre d'onglets, nous devons définir les couleurs sélectionnées et non sélectionnées.

Maintenant, nous devons parcourir tous nos contrôleurs et vérifier si i == selectedIndex

Ensuite, nous devons restituer l'image en tant que mode de rendu d'origine , sinon, nous devons restituer l'image en mode modèle .

Lorsque vous rendez une image à l'aide du mode modèle , elle hérite de la couleur de la teinte de l'élément.

On a presque fini. Nous avons juste besoin d'introduire deux méthodes importantes de UITabBarControllerDelegate et PageViewControllerDelegate .

 func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { self.selectPage(at: viewController.view.tag) return false } func pageDidSwipe(to index: Int) { guard let viewController = self.viewControllers?[index] else { return } self.handleTabbarItemChange(viewController: viewController) }

Le premier est appelé lorsque vous appuyez sur n'importe quel élément de l'onglet, tandis que le second est appelé lorsque vous glissez entre les onglets.

Emballer

Lorsque vous assemblez tout le code, vous remarquerez que vous n'avez pas besoin d'écrire votre propre implémentation de gestionnaires de gestes et que vous n'avez pas besoin d'écrire beaucoup de code pour offrir un défilement/balayage fluide entre les éléments de la barre d'onglets.

L'implémentation décrite ici n'est pas idéale pour tous les scénarios, mais c'est une solution originale, rapide et relativement simple qui vous permet de créer ces fonctionnalités avec un peu de code.

Enfin, si vous voulez essayer mon approche, vous pouvez utiliser mon repo GitHub. Bon codage !