
Sebagian besar aplikasi seluler berisi lebih dari selusin layar, transisi kompleks, serta bagian-bagian aplikasi, dipisahkan oleh makna dan tujuan. Oleh karena itu, ada kebutuhan untuk mengatur struktur navigasi yang benar untuk aplikasi, yang akan fleksibel, nyaman, dapat diperluas, menyediakan akses yang nyaman ke berbagai bagian aplikasi, dan juga akan berhati-hati tentang sumber daya sistem.
Pada artikel ini, kami akan merancang navigasi dalam aplikasi sedemikian rupa untuk menghindari kesalahan paling umum yang menyebabkan kebocoran memori, merusak arsitektur dan menghancurkan struktur navigasi.
Sebagian besar aplikasi memiliki setidaknya dua bagian: otentikasi (pra-masuk) dan bagian pribadi (pasca-masuk). Beberapa aplikasi mungkin memiliki struktur yang lebih kompleks, banyak profil dengan login tunggal, transisi kondisional setelah memulai aplikasi (tautan dalam), dll.
Dalam praktiknya, dua pendekatan terutama digunakan untuk menavigasi aplikasi:
- Satu tumpukan navigasi untuk pengontrol presentasi (sekarang) dan pengontrol navigasi (push), tanpa kemampuan untuk kembali. Pendekatan ini menyebabkan semua ViewControllers sebelumnya tetap berada dalam memori.
- Menggunakan toggle window.rootViewController. Dengan pendekatan ini, semua ViewControllers sebelumnya hancur dalam memori, tetapi ini tidak terlihat yang terbaik dari sudut pandang UI. Juga, itu tidak memungkinkan Anda untuk bergerak maju dan mundur jika perlu.
Sekarang mari kita lihat bagaimana Anda dapat membuat struktur yang mudah dipelihara yang memungkinkan Anda untuk beralih secara mulus antara berbagai bagian aplikasi, tanpa kode spageti dan navigasi yang mudah.
Mari kita bayangkan bahwa kita sedang menulis aplikasi yang terdiri dari:
- Layar utama (Layar splash ): ini adalah layar pertama yang Anda lihat, segera setelah aplikasi dimulai, Anda dapat menambahkan, misalnya, animasi atau membuat permintaan API primer apa pun.
- Bagian otentikasi : login, registrasi, setel ulang kata sandi, konfirmasi email, dll. Sesi kerja pengguna biasanya disimpan, jadi tidak perlu memasukkan login setiap kali aplikasi dimulai.
- Bagian utama : logika bisnis dari aplikasi utama
Semua bagian aplikasi ini terisolasi satu sama lain dan masing-masing ada di tumpukan navigasinya sendiri. Karena itu, kita mungkin memerlukan transisi berikut:
- Layar splash -> Layar otentikasi , jika sesi saat ini dari pengguna aktif tidak ada.
- Layar splash -> Layar utama , jika pengguna telah masuk ke aplikasi lebih awal dan ada sesi aktif.
- Layar utama -> layar Otentikasi , jika pengguna keluar
Pengaturan dasarKetika aplikasi dimulai, kita perlu menginisialisasi
RootViewController , yang akan dimuat terlebih dahulu. Ini dapat dilakukan dengan kode dan melalui Interface Builder. Buat proyek baru di xCode dan semua ini sudah dilakukan secara default:
main.storyboard sudah terikat ke
window.rootViewController .
Tetapi untuk fokus pada topik utama artikel, kami tidak akan menggunakan storyboard dalam proyek kami. Oleh karena itu, hapus
main.storyboard , dan kosongkan bidang "Antarmuka Utama" di bawah Target -> Umum -> Info penerapan:

Sekarang mari kita ubah metode
didFinishLaunchingWithOptions di
AppDelegate sehingga terlihat seperti ini:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = RootViewController() window?.makeKeyAndVisible() return true }
Sekarang aplikasi pertama akan meluncurkan
RootViewController . Ganti nama basis
ViewController ke
RootViewController :
class RootViewController: UIViewController { }
Ini akan menjadi pengontrol utama yang bertanggung jawab untuk semua transisi di antara berbagai bagian aplikasi. Oleh karena itu, kita akan memerlukan tautan ke sana setiap kali kita ingin melakukan transisi. Untuk melakukan ini, tambahkan ekstensi ke
AppDelegate :
extension AppDelegate { static var shared: AppDelegate { return UIApplication.shared.delegate as! AppDelegate } var rootViewController: RootViewController { return window!.rootViewController as! RootViewController } }
Pengambilan paksa opsi dalam kasus ini dibenarkan, karena RootViewController tidak berubah, dan jika ini terjadi secara tidak sengaja, maka aplikasi crash adalah situasi normal.
Jadi, sekarang kita memiliki tautan ke
RootViewController dari mana saja di aplikasi:
let rootViewController = AppDelegate.shared.rootViewController
Sekarang mari kita buat beberapa kontroler lagi yang kita butuhkan:
SplashViewController, LoginViewController, dan
MainViewController .
Splash Screen adalah layar pertama yang akan dilihat pengguna setelah memulai aplikasi. Pada saat ini, semua permintaan API yang diperlukan biasanya dibuat, aktivitas sesi pengguna diperiksa, dll. Untuk menampilkan tindakan latar belakang yang sedang berlangsung, gunakan
UIActivityIndicatorView :
class SplashViewController: UIViewController { private let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge) override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.white view.addSubview(activityIndicator) activityIndicator.frame = view.bounds activityIndicator.backgroundColor = UIColor(white: 0, alpha: 0.4) makeServiceCall() } private func makeServiceCall() { } }
Untuk mensimulasikan permintaan API, tambahkan metode
DispatchQueue.main.asyncAfter dengan penundaan selama 3 detik:
private func makeServiceCall() { activityIndicator.startAnimating() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(3)) { self.activityIndicator.stopAnimating() } }
Kami percaya bahwa sesi pengguna juga diatur dalam permintaan ini. Dalam aplikasi kami, kami menggunakan
UserDefaults untuk ini:
private func makeServiceCall() { activityIndicator.startAnimating() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(3)) { self.activityIndicator.stopAnimating() if UserDefaults.standard.bool(forKey: “LOGGED_IN”) {
Anda pasti tidak akan menggunakan UserDefaults untuk menyimpan status sesi pengguna dalam versi rilis program. Kami menggunakan pengaturan lokal dalam proyek kami untuk menyederhanakan pemahaman dan tidak melampaui topik utama artikel.Buat
LoginViewController . Ini akan digunakan untuk mengautentikasi pengguna jika sesi pengguna saat ini tidak aktif. Anda dapat menambahkan UI khusus Anda ke controller, tetapi saya akan menambahkan di sini hanya judul layar dan tombol login di Bilah Navigasi.
class LoginViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.white title = "Login Screen" let loginButton = UIBarButtonItem(title: "Log In", style: .plain, target: self, action: #selector(login)) navigationItem.setLeftBarButton(loginButton, animated: true) } @objc private func login() {
Dan akhirnya, buat pengontrol utama aplikasi
MainViewController :
class MainViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.lightGray // to visually distinguish the protected part title = “Main Screen” let logoutButton = UIBarButtonItem(title: “Log Out”, style: .plain, target: self, action: #selector(logout)) navigationItem.setLeftBarButton(logoutButton, animated: true) } @objc private func logout() { // clear the user session (example only, not for the production) UserDefaults.standard.set(false, forKey: “LOGGED_IN”) // navigate to the Main Screen } }
Navigasi rootSekarang kembali ke
RootViewController .
Seperti yang kami katakan sebelumnya,
RootViewController adalah satu-satunya objek yang bertanggung jawab untuk transisi antara tumpukan pengontrol independen yang berbeda. Untuk mengetahui keadaan aplikasi saat ini, kami akan membuat variabel untuk menyimpan
ViewController saat ini:
class RootViewController: UIViewController { private var current: UIViewController }
Tambahkan initializer kelas dan buat
ViewController pertama yang ingin kita muat saat aplikasi dimulai. Dalam kasus kami, ini akan menjadi
SplashViewController :
class RootViewController: UIViewController { private var current: UIViewController init() { self.current = SplashViewController() super.init(nibName: nil, bundle: nil) } }
Di
viewDidLoad, tambahkan
viewController saat ini ke
RootViewController :
class RootViewController: UIViewController { ... override func viewDidLoad() { super.viewDidLoad() addChildViewController(current)
Setelah kami menambahkan
childViewController (1), kami menyesuaikan ukurannya dengan mengatur
current.view.frame ke
view.bounds (2).
Jika kita melewati garis ini,
viewController akan tetap ditempatkan dengan benar dalam banyak kasus, tetapi masalah dapat terjadi jika ukuran
bingkai berubah.
Tambahkan subview baru (3) dan panggil metode didMove (toParentViewController :). Ini akan menyelesaikan operasi add controller. Segera setelah
RootViewController melakukan boot , segera setelah itu
SplashViewController ditampilkan.
Sekarang Anda dapat menambahkan beberapa metode untuk navigasi dalam aplikasi. Kami akan menampilkan
LoginViewController tanpa animasi apa pun,
MainViewController akan menggunakan animasi dengan peredupan halus, dan transisi layar ketika memutuskan
koneksi pengguna akan memiliki efek slide.
class RootViewController: UIViewController { ... func showLoginScreen() { let new = UINavigationController(rootViewController: LoginViewController()) // 1 addChildViewController(new) // 2 new.view.frame = view.bounds // 3 view.addSubview(new.view) // 4 new.didMove(toParentViewController: self) // 5 current.willMove(toParentViewController: nil) // 6 current.view.removeFromSuperview()] // 7 current.removeFromParentViewController() // 8 current = new // 9 }
Buat
LoginViewController (1), tambahkan sebagai pengontrol anak (2), atur bingkai (3). Tambahkan tampilan
LoginController sebagai subview (4) dan panggil metode didMove (5). Selanjutnya, siapkan pengontrol saat ini untuk dihapus menggunakan metode willMove (6). Akhirnya, hapus tampilan saat ini dari superview (7), dan hapus controller saat ini dari
RootViewController (8). Ingatlah untuk memperbarui nilai controller saat ini (9).
Sekarang mari kita buat metode
switchToMainScreen :
func switchToMainScreen() { let mainViewController = MainViewController() let mainScreen = UINavigationController(rootViewController: mainViewController) ... }
Animasi transisi memerlukan metode yang berbeda:
private func animateFadeTransition(to new: UIViewController, completion: (() -> Void)? = nil) { current.willMove(toParentViewController: nil) addChildViewController(new) transition(from: current, to: new, duration: 0.3, options: [.transitionCrossDissolve, .curveEaseOut], animations: { }) { completed in self.current.removeFromParentViewController() new.didMove(toParentViewController: self) self.current = new completion?()
Metode ini sangat mirip dengan
showLoginScreen , tetapi semua langkah terakhir dilakukan setelah animasi selesai. Untuk memberi tahu metode panggilan dari akhir transisi, di bagian paling akhir kita sebut closure (1).
Sekarang versi final dari metode
switchToMainScreen akan terlihat seperti ini:
func switchToMainScreen() { let mainViewController = MainViewController() let mainScreen = UINavigationController(rootViewController: mainViewController) animateFadeTransition(to: mainScreen) }
Dan akhirnya, mari kita buat metode terakhir yang akan bertanggung jawab untuk transisi dari
MainViewController ke
LoginViewController :
func switchToLogout() { let loginViewController = LoginViewController() let logoutScreen = UINavigationController(rootViewController: loginViewController) animateDismissTransition(to: logoutScreen) }
Metode
AnimateDismissTransition menyediakan animasi slide:
private func animateDismissTransition(to new: UIViewController, completion: (() -> Void)? = nil) { new.view.frame = CGRect(x: -view.bounds.width, y: 0, width: view.bounds.width, height: view.bounds.height) current.willMove(toParentViewController: nil) addChildViewController(new) transition(from: current, to: new, duration: 0.3, options: [], animations: { new.view.frame = self.view.bounds }) { completed in self.current.removeFromParentViewController() new.didMove(toParentViewController: self) self.current = new completion?() } }
Ini hanya dua contoh animasi, menggunakan pendekatan yang sama Anda dapat membuat animasi kompleks yang Anda butuhkan
Untuk menyelesaikan konfigurasi, tambahkan pemanggilan metode dengan animasi dari
SplashViewController, LoginViewController, dan
MainViewController :
class SplashViewController: UIViewController { ... private func makeServiceCall() { if UserDefaults.standard.bool(forKey: “LOGGED_IN”) { // navigate to protected page AppDelegate.shared.rootViewController.switchToMainScreen() } else { // navigate to login screen AppDelegate.shared.rootViewController.switchToLogout() } } } class LoginViewController: UIViewController { ... @objc private func login() { ... AppDelegate.shared.rootViewController.switchToMainScreen() } } class MainViewController: UIViewController { ... @objc private func logout() { ... AppDelegate.shared.rootViewController.switchToLogout() } }
Kompilasi, jalankan aplikasi dan periksa operasinya dengan dua cara:
- ketika pengguna sudah memiliki sesi aktif saat ini (masuk)
- ketika tidak ada sesi aktif dan otentikasi diperlukan
Dalam kedua kasus, Anda akan melihat transisi ke layar yang diinginkan, segera setelah memuat
SplashScreen .

Hasilnya, kami menciptakan model uji kecil aplikasi, dengan navigasi melalui modul utamanya. Jika Anda perlu memperluas kemampuan aplikasi, menambahkan modul tambahan dan transisi di antara mereka, Anda selalu dapat dengan cepat dan mudah memperluas dan skala sistem navigasi ini.