So erstellen Sie eine Swipe-UITabBar von Grund auf
Veröffentlicht: 2022-03-11Wie Sie wissen, enthält das iOS-SDK von Apple unzählige integrierte UI-Komponenten. Schaltflächen, Container, Navigationen, Layouts mit Registerkarten, Sie nennen es – fast alles, was Sie jemals brauchen werden, ist da. Oder ist es?
All diese grundlegenden Komponenten ermöglichen es uns, grundlegende strukturierte Benutzeroberflächen zu erstellen, aber was passiert, wenn es notwendig ist, über den Tellerrand hinauszugehen? wenn ein iOS-Entwickler ein Verhalten entwickeln muss, das standardmäßig nicht im SDK unterstützt wird?
Einer dieser Fälle ist UITabBar , wo Sie nicht die Möglichkeit haben, zwischen Registerkarten zu wischen, und Sie auch keine Animationen zum Wechseln zwischen den Registerkarten haben.
Suche nach einer einfachen UITabBar-Korrektur
Nach einiger Suche habe ich es geschafft, nur eine nützliche Bibliothek auf Github zu finden. Leider hat die Bibliothek beim Ausführen der Anwendung viele Probleme verursacht, obwohl sie auf den ersten Blick als elegante Lösung erschien.
Mit anderen Worten, ich fand die Bibliothek sehr einfach zu bedienen, aber fehlerhaft, was offensichtlich die Benutzerfreundlichkeit überwog und dazu neigte, Probleme zu verursachen. Falls Sie dennoch interessiert sind, finden Sie die Bibliothek unter diesem Link.
Also begann ich nach einigem Nachdenken und viel Suchen mit der Implementierung meiner eigenen Lösung und sagte mir: „Hey, was wäre, wenn wir den Seitenaufruf-Controller für Wischen und die native UITabBar verwenden würden. Was wäre, wenn wir diese beiden Dinge zusammen gruppieren und den Seitenindex handhaben, während wir auf die Tableiste wischen oder tippen?“
Letztendlich fand ich eine Lösung, die sich jedoch als etwas schwierig herausstellte, wie ich später erklären werde.
Eine aufwändige UITabBar-Lösung
Stellen Sie sich vor, Sie müssten drei Registerkartenelemente erstellen, was automatisch bedeutet, dass Sie drei Seiten/Controller pro Registerkartenelement anzeigen müssen.
In diesem Fall müssen Sie diese drei Ansichts-Controller instanziieren und Sie benötigen außerdem zwei Platzhalter/leere Ansichts-Controller für die Registerkartenleiste, um Elemente der Registerkartenleiste zu erstellen, ihren Zustand zu ändern, wenn die Registerkarte gedrückt wird oder wenn der Benutzer dies ändern möchte den Registerkartenindex programmgesteuert.
Lassen Sie uns dafür in Xcode eintauchen und ein paar Klassen schreiben, nur um zu sehen, wie diese Dinge funktionieren.
Ein Beispiel für das Wischen zwischen Tabs
In diesen Screenshots können Sie sehen, dass das erste Element der Registerkartenleiste blau ist, der Benutzer dann zur rechten Registerkarte wischt, die gelb ist, und der letzte Bildschirm zeigt, dass das dritte Element ausgewählt ist, sodass die gesamte Seite gelb angezeigt wird.
Programmatische Verwendung der Swipe-Tab-Leiste
Lassen Sie uns also in diese Funktion eintauchen und ein einfaches Beispiel für eine swipeable Tabbar für iOS schreiben. Zunächst müssen wir ein neues Projekt erstellen.
Die Voraussetzungen für unser Projekt sind recht einfach: Xcode und Xcode-Build-Tools, die auf Ihrem Mac installiert sind.
Um ein neues Projekt zu erstellen, öffnen Sie die Xcode-Anwendung auf Ihrem Mac und wählen Sie „Neues Xcode-Projekt erstellen“, benennen Sie Ihr Projekt und wählen Sie schließlich den Typ der zu erstellenden Anwendung aus. Wählen Sie einfach „Single View App“ und drücken Sie auf „Weiter“.
Wie Sie sehen können, müssen Sie auf dem nächsten Bildschirm einige grundlegende Informationen angeben:
- Produktname: Ich habe es SwipeableTabbar genannt.
- Team: Wenn Sie diese Anwendung auf einem echten Gerät ausführen möchten, müssen Sie über ein Entwicklerkonto verfügen. In meinem Fall werde ich dafür mein eigenes Konto verwenden.
Hinweis: Wenn Sie kein Entwicklerkonto haben, können Sie dieses auch im Simulator ausführen.
- Name der Organisation: Ich habe sie Toptal genannt.
- Organisationskennung: Ich habe sie com.toptal genannt.
- Sprache: Wählen Sie Swift aus.
- Deaktivieren Sie: „Kerndaten verwenden“, „Einheitentests einbeziehen“ und „UI-Tests einbeziehen“.
Klicken Sie auf die Schaltfläche „Weiter“, und Sie können mit dem Erstellen Ihrer wischbaren Registerkartenleiste beginnen.
Einfache Architektur
Wie Sie bereits wissen, haben Sie beim Erstellen einer neuen App bereits die Klasse Main ViewController
und Main.Storyboard
.
Bevor wir mit dem Entwerfen beginnen, erstellen wir zunächst alle erforderlichen Klassen und Dateien, um sicherzustellen, dass alles eingerichtet und ausgeführt wird, bevor wir mit dem UI-Teil des Jobs fortfahren.
Erstellen Sie einfach irgendwo in Ihrem Projekt ein paar neue Dateien -> TabbarController.swift , NavigationController.swift , PageViewController.swift .
In meinem Fall sieht es so aus.
Belassen Sie in der AppDelegate -Datei nur didFinishLaunchingWithOptions
, da Sie alle anderen Methoden entfernen können.
Kopieren Sie in didFinishLaunchingWithOptions
einfach die folgenden Zeilen und fügen Sie sie ein:
window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = NavigationController(rootViewController: TabbarController()) window?.makeKeyAndVisible() return true
Entfernen Sie alles aus der Datei namens ViewController.swift . Wir werden später auf diese Datei zurückkommen.
Lassen Sie uns zunächst Code für NavigationController.swift schreiben.
import Foundation import UIKit class NavigationController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() navigationBar.isTranslucent = true navigationBar.tintColor = .gray } }
Damit haben wir gerade einen einfachen UINavigationController
, in dem wir einen durchscheinenden Balken mit einer grauen TintColor haben. Das ist alles hier.
Jetzt können wir damit fortfahren, den PageViewController
zu übernehmen.
Darin müssen wir etwas mehr codieren als in den vorherigen Dateien, die wir besprochen haben.
Diese Datei enthält eine Klasse, ein Protokoll, einige UIPageViewController
-Datenquellen und Delegate-Methoden.
Die resultierende Datei muss wie folgt aussehen:
Wie Sie sehen können, haben wir unser eigenes Protokoll namens PageViewControllerDelegate
, das dem Tableisten-Controller mitteilen sollte, dass der Seitenindex geändert wurde, nachdem das Wischen ausgeführt wurde.
import Foundation import UIKit protocol PageViewControllerDelegate: class { func pageDidSwipe(to index: Int) }
Dann müssen wir eine neue Klasse namens PageViewController
, die unsere View-Controller enthält, Seiten an einem bestimmten Index auswählt und auch Swipes verarbeitet.
Stellen wir uns vor, der erste ausgewählte Controller bei unserem ersten Durchlauf sollte der Center View-Controller sein. In diesem Fall weisen wir unseren Standardindexwert gleich 1 zu.
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 } }
Wie Sie hier sehen können, haben wir pages
, die Verweise auf alle unsere View-Controller enthalten.
Die Variable prevIndex
wird verwendet, um den zuletzt ausgewählten Index zu speichern.
Sie können einfach die Methode selectPage
, um den ausgewählten Index zu setzen.
Wenn Sie auf Seitenindexänderungen lauschen möchten, müssen Sie swipeDelegate
abonnieren, und bei jedem Seitenwischen werden Sie benachrichtigt, dass sich der Seitenindex geändert hat, und Sie erhalten auch den aktuellen Index.
Die Methode direction gibt die Wischrichtung von UIPageViewController
. Das letzte Puzzleteil in dieser Klasse sind Delegat-/Datenquellenimplementierungen.

Glücklicherweise sind diese Implementierungen sehr einfach.
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) } } }
Wie Sie oben sehen können, gibt es drei Methoden im Spiel:
- Der erste findet den Index und gibt den vorherigen View-Controller zurück.
- Der zweite findet den Index und gibt den nächsten View-Controller zurück.
- Der letzte überprüft, ob das Wischen beendet wurde, setzt den aktuellen Index auf die lokale Eigenschaft
prevIndex
und ruft dann die Delegate-Methode auf, um den übergeordneten Ansichtscontroller zu benachrichtigen, dass das Wischen erfolgreich beendet wurde.
Jetzt können wir endlich unsere UITabBarController
Implementierung schreiben:
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) } }
Wie Sie sehen können, erstellen wir den TabbarController
mit Standardeigenschaften und -stil. Wir müssen zwei Farben für ausgewählte und nicht ausgewählte Balkenelemente definieren. Außerdem habe ich drei Bilder für Tabbar-Elemente eingeführt.
In viewDidLoad
ich lediglich die Standardkonfiguration unserer Tabbar ein und wähle Seite #1 aus. Das bedeutet, dass die Startseite Seite Nummer eins ist.
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) }
Innerhalb der setUp-Methode sehen Sie, dass wir zwei Platzhalter-View-Controller erstellt haben. Diese Platzhalter-Controller werden für UITabBar
benötigt, da die Anzahl der Registerkartenleistenelemente gleich der Anzahl der View-Controller sein muss, die Sie haben.
Wenn Sie sich erinnern können, verwenden wir UIPageViewController
, um Controller anzuzeigen, aber für UITabBar
müssen wir, wenn wir es vollständig funktionsfähig machen wollen, alle View-Controller instanziiert haben, damit Balkenelemente funktionieren, wenn Sie darauf tippen. In diesem Beispiel sind placeholderviewcontroller #0 und #2 also leere View-Controller.
Als zentrierter View-Controller erstellen wir einen PageViewController
mit drei View-Controllern.
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) }
Die erste und zweite oben dargestellte Methode sind Init-Methoden unseres Pageview- Controllers.
Die Methode tabbar
item gibt nur das tabbar
Element am Index zurück.
Wie Sie sehen können, verwende ich in createCenterPageViewController()
Tags für jeden View-Controller. Dies hilft mir zu verstehen, welcher Controller auf dem Bildschirm erschienen ist.
Als nächstes kommen wir zu unserer möglicherweise wichtigsten Methode, 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) } }
In dieser Methode verwende ich den View-Controller als Parameter. Von diesem View-Controller erhalte ich ein Tag als ausgewählten Index. Für die Registerkartenleiste müssen wir ausgewählte und nicht ausgewählte Farben festlegen.
Jetzt müssen wir alle unsere Controller durchlaufen und prüfen, ob i == selectedIndex
Dann müssen wir das Bild im Original-Rendering-Modus rendern , andernfalls müssen wir das Bild im Vorlagenmodus rendern.
Wenn Sie ein Bild im Vorlagenmodus rendern, erbt es die Farbe von der Tönungsfarbe des Elements.
Wir sind fast fertig. Wir müssen nur zwei wichtige Methoden von UITabBarControllerDelegate
und 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) }
Der erste wird aufgerufen, wenn Sie auf ein beliebiges Registerkartenelement drücken, während der zweite aufgerufen wird, wenn Sie zwischen den Registerkarten wischen.
Einpacken
Wenn Sie den gesamten Code zusammenstellen, werden Sie feststellen, dass Sie keine eigene Implementierung von Gestenhandlern schreiben müssen und dass Sie nicht viel Code schreiben müssen, um ein reibungsloses Scrollen/Swipen zwischen Registerkartenleistenelementen zu ermöglichen.
Die hier besprochene Implementierung ist nicht für alle Szenarien ideal, aber es ist eine skurrile, schnelle und relativ einfache Lösung, mit der Sie diese Features mit ein wenig Code erstellen können.
Wenn Sie meinen Ansatz ausprobieren möchten, können Sie schließlich mein GitHub-Repo verwenden. Viel Spaß beim Codieren!