Cara Membuat UITabBar yang Dapat Digesek Dari Bawah ke Atas
Diterbitkan: 2022-03-11Seperti yang Anda ketahui, SDK iOS Apple berisi banyak sekali komponen UI bawaan. Tombol, wadah, navigasi, tata letak tab, apa saja—hampir semua yang Anda perlukan ada di sana. Atau itu?
Semua komponen dasar ini memungkinkan kita untuk membuat UI terstruktur dasar, tetapi apa yang terjadi jika ada kebutuhan untuk keluar dari kotak; ketika pengembang iOS perlu membangun semacam perilaku yang tidak didukung di SDK secara default?
Salah satu kasus ini adalah UITabBar , di mana Anda tidak memiliki kemampuan untuk menggeser antar tab, dan Anda juga tidak memiliki animasi untuk beralih antar tab.
Mencari Perbaikan UITabBar yang Mudah
Setelah cukup lama mencari, saya hanya berhasil menemukan satu perpustakaan yang berguna di Github. Sayangnya, perpustakaan menciptakan banyak masalah saat menjalankan aplikasi, meskipun sekilas tampak sebagai solusi yang elegan.
Dengan kata lain, saya menemukan perpustakaan sangat mudah digunakan, tetapi bermasalah, yang jelas melebihi kemudahan penggunaannya dan cenderung menimbulkan masalah. Jika Anda masih tertarik, lib dapat ditemukan di bawah tautan ini.
Jadi, setelah beberapa pemikiran dan banyak pencarian, saya mulai menerapkan solusi saya sendiri dan saya berkata pada diri sendiri: “Hei, bagaimana jika kita menggunakan pengontrol tampilan halaman untuk menggesek, dan UITabBar asli. Bagaimana jika kita mengelompokkan dua hal ini bersama-sama, menangani indeks halaman sambil menggesek atau mengetuk tabbar?
Pada akhirnya, saya menemukan solusi, meskipun terbukti agak rumit, seperti yang akan saya jelaskan nanti.
Solusi UITabBar yang Rumit
Bayangkan Anda memiliki tiga item tabbar yang akan dibuat, yang secara otomatis berarti Anda memiliki tiga halaman/pengontrol untuk ditampilkan per setiap item tab.
Dalam hal ini, Anda perlu membuat instance ketiga pengontrol tampilan tersebut dan Anda juga akan memerlukan dua placeholder/pengontrol tampilan kosong untuk tabbar, untuk membuat item bilah tab, mengubah statusnya saat tab ditekan, atau saat pengguna ingin mengubah indeks tab secara terprogram.
Untuk ini, mari gali Xcode dan tulis beberapa kelas, hanya untuk melihat cara kerjanya.
Contoh Menggesek Antar Tab
Dalam tangkapan layar ini, Anda dapat melihat item bilah tab pertama berwarna biru, lalu pengguna menggesek ke tab kanan, yang berwarna kuning, dan layar terakhir menunjukkan item ketiga dipilih, sehingga seluruh halaman ditampilkan sebagai kuning.
Penggunaan Terprogram dari Bilah Tab yang Dapat Digesek
Jadi, mari selami fitur ini dan tulis contoh mudah tabbar yang dapat digesek untuk iOS. Pertama-tama, kita perlu membuat proyek baru.
Prasyarat yang diperlukan untuk proyek kami cukup mendasar: Xcode dan Xcode build tools terinstal di Mac Anda.
Untuk membuat proyek baru, buka aplikasi Xcode di Mac Anda dan pilih "Buat proyek Xcode baru," lalu beri nama proyek Anda, dan terakhir pilih jenis aplikasi yang akan dibuat. Cukup pilih "Aplikasi Tampilan Tunggal" dan tekan Berikutnya.
Seperti yang Anda lihat, layar berikutnya akan meminta Anda untuk memberikan beberapa informasi dasar:
- Nama Produk: Saya menamakannya SwipeableTabbar.
- Tim: Jika Anda ingin menjalankan aplikasi ini di perangkat nyata, Anda harus memiliki akun pengembang. Dalam kasus saya, saya akan menggunakan akun saya sendiri untuk ini.
Catatan: Jika Anda tidak memiliki akun pengembang, Anda juga dapat menjalankannya di Simulator.
- Nama Organisasi: Saya menamakannya Toptal .
- Pengidentifikasi Organisasi: Saya menamakannya com.toptal .
- Bahasa: Pilih Swift.
- Hapus centang: "Gunakan Data Inti," "Sertakan Tes Unit," dan "Sertakan Tes UI."
Tekan tombol Berikutnya, dan Anda siap untuk mulai membuat bilah tab yang dapat digesek.
Arsitektur Sederhana
Seperti yang sudah Anda ketahui sekarang, saat Anda membuat aplikasi baru, Anda sudah memiliki kelas Main ViewController
dan Main.Storyboard
.
Sebelum kita mulai mendesain, pertama-tama mari kita buat semua kelas dan file yang diperlukan untuk memastikan bahwa kita telah menyiapkan dan menjalankan semuanya sebelum kita melanjutkan ke bagian UI pekerjaan.
Di suatu tempat di dalam proyek Anda, cukup buat beberapa file baru -> TabbarController.swift , NavigationController.swift , PageViewController.swift .
Dalam kasus saya, sepertinya ini.
Di file AppDelegate , biarkan hanya didFinishLaunchingWithOptions
, karena Anda dapat menghapus semua metode lainnya.
Di dalam didFinishLaunchingWithOptions
, cukup salin dan tempel baris di bawah ini:
window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = NavigationController(rootViewController: TabbarController()) window?.makeKeyAndVisible() return true
Hapus semuanya dari file bernama ViewController.swift . Kami akan kembali ke file ini nanti.
Pertama, mari kita menulis kode untuk NavigationController.swift .
import Foundation import UIKit class NavigationController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() navigationBar.isTranslucent = true navigationBar.tintColor = .gray } }
Dengan ini, kami baru saja membuat UINavigationController
sederhana, di mana kami memiliki bilah tembus pandang dengan TintColor abu-abu. Itu semua di sini.
Sekarang, kita dapat melanjutkan untuk mengambil PageViewController
.
Di dalamnya, kita perlu membuat kode sedikit lebih banyak daripada di file sebelumnya yang kita bahas.
File ini berisi satu kelas, satu protokol, beberapa sumber data UIPageViewController
, dan metode delegasi.
File yang dihasilkan harus terlihat seperti ini:
Seperti yang Anda lihat, kami telah mendeklarasikan protokol kami sendiri yang disebut PageViewControllerDelegate
, yang akan memberi tahu pengontrol bilah tab bahwa indeks halaman diubah setelah gesekan ditangani.
import Foundation import UIKit protocol PageViewControllerDelegate: class { func pageDidSwipe(to index: Int) }
Kemudian kita perlu membuat kelas baru, yang disebut PageViewController
, yang akan menampung pengontrol tampilan kita, memilih halaman pada indeks tertentu, dan juga menangani gesekan.
Mari kita bayangkan pengontrol pertama yang dipilih pada proses pertama kita harus menjadi pengontrol tampilan tengah. Dalam hal ini, kami menetapkan nilai indeks default kami, sama dengan 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 } }
Seperti yang Anda lihat di sini, kami memiliki pages
variabel , yang akan berisi referensi dari semua pengontrol tampilan kami.
Variabel prevIndex
digunakan untuk menyimpan indeks yang terakhir dipilih.
Anda cukup memanggil metode selectPage
untuk mengatur indeks yang dipilih.
Jika Anda ingin mendengarkan perubahan indeks halaman, Anda harus berlangganan swipeDelegate
, dan pada setiap gesekan halaman, Anda akan diberi tahu bahwa indeks halaman berubah, ditambah Anda juga akan menerima indeks saat ini.
Arah metode akan mengembalikan arah gesek UIPageViewController
. Bagian terakhir dari teka-teki di kelas ini adalah implementasi delegasi/sumber data.
Untungnya, implementasi ini sangat sederhana.
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) } } }
Seperti yang Anda lihat di atas, ada tiga metode dalam permainan:

- Yang pertama menemukan indeks dan mengembalikan pengontrol tampilan sebelumnya.
- Yang kedua menemukan indeks dan mengembalikan pengontrol tampilan berikutnya.
- Yang terakhir memeriksa apakah gesekan telah berakhir, menyetel indeks saat ini ke properti lokal
prevIndex
dan kemudian memanggil metode delegasi untuk memberi tahu pengontrol tampilan induk bahwa gesekan telah berhasil diakhiri.
Sekarang kita akhirnya bisa menulis implementasi UITabBarController
kita:
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) } }
Seperti yang Anda lihat, kami membuat TabbarController
, dengan properti dan gaya default. Kita perlu mendefinisikan dua warna, untuk item bar yang dipilih dan yang tidak dipilih. Juga, saya telah memperkenalkan tiga gambar untuk item tabbar.
Di viewDidLoad
, saya hanya mengatur konfigurasi default tabbar kami dan memilih halaman #1. Artinya, halaman awal akan menjadi halaman nomor satu.
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) }
Di dalam metode setUp, Anda lihat, bahwa kami membuat dua pengontrol tampilan placeholder. Pengontrol placeholder ini diperlukan untuk UITabBar
karena jumlah item bilah tab harus sama dengan jumlah pengontrol tampilan yang Anda miliki.
Jika Anda ingat, kami menggunakan UIPageViewController
untuk menampilkan pengontrol, tetapi untuk UITabBar
, jika kami ingin membuatnya sepenuhnya dapat diterapkan, kami harus membuat semua pengontrol tampilan dipakai, sehingga item bilah akan berfungsi saat Anda mengetuknya. Jadi, dalam contoh ini, placeholderviewcontroller #0 dan #2 adalah pengontrol tampilan kosong.
Sebagai pengontrol tampilan terpusat, kami membuat PageViewController
dengan tiga pengontrol tampilan.
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) }
Metode pertama dan kedua yang digambarkan di atas adalah metode init dari pengontrol tampilan halaman kami.
Item tabbar
metode hanya mengembalikan item tabbar
di index.
Seperti yang Anda lihat, di dalam createCenterPageViewController()
saya menggunakan tag untuk setiap pengontrol tampilan. Ini membantu saya memahami pengontrol mana yang muncul di layar.
Selanjutnya, kita sampai pada metode yang mungkin paling penting, 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) } }
Dalam metode ini, saya menggunakan pengontrol tampilan sebagai parameter. Dari pengontrol tampilan ini, saya mendapatkan tag sebagai indeks yang dipilih. Untuk bilah tab, kita perlu mengatur warna yang dipilih dan tidak dipilih.
Sekarang kita perlu mengulang semua pengontrol kita dan memeriksa apakah i == selectedIndex
Kemudian kita perlu merender gambar sebagai mode rendering asli , jika tidak, kita perlu merender gambar sebagai mode template .
Saat Anda merender gambar menggunakan mode template , itu akan mewarisi warna dari warna rona item.
Kami hampir selesai. Kita hanya perlu memperkenalkan dua metode penting dari UITabBarControllerDelegate
dan 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) }
Yang pertama dipanggil saat Anda menekan item tab apa pun, sedangkan yang kedua dipanggil saat Anda menggesek antar tab.
Membungkus
Ketika Anda menggabungkan semua kode, Anda akan melihat bahwa Anda tidak perlu menulis implementasi Anda sendiri dari penangan gerakan dan Anda tidak perlu menulis banyak kode untuk memberikan pengguliran/gesekan yang mulus di antara item bilah tab.
Implementasi yang dibahas di sini bukanlah sesuatu yang ideal untuk semua skenario, tetapi merupakan solusi yang unik, cepat, dan relatif mudah yang memungkinkan Anda membuat fitur ini dengan sedikit kode.
Terakhir, jika Anda ingin mencoba pendekatan saya, Anda dapat menggunakan repo GitHub saya. Selamat mengkode!