Dalam artikel sebelumnya , saya berbicara tentang pendekatan yang kami gunakan untuk menulis dan menavigasi antara pengontrol tampilan di beberapa aplikasi yang saya kerjakan, yang, sebagai hasilnya, menghasilkan perpustakaan RouteComposer terpisah. Saya menerima sejumlah besar umpan balik yang menyenangkan pada artikel sebelumnya dan beberapa saran praktis, yang mendorong saya untuk menulis satu lagi yang akan menjelaskan sedikit lebih banyak cara mengkonfigurasi perpustakaan. Di bawah potongan, saya akan mencoba membuat beberapa konfigurasi yang paling umum.

Bagaimana router mem-parsing konfigurasi
Untuk memulai, pertimbangkan bagaimana router mem-parsing konfigurasi yang Anda tulis. Ambil contoh dari artikel sebelumnya:
let productScreen = StepAssembly(finder: ClassFinder(options: [.current, .visible]), factory: ProductViewControllerFactory()) .using(UINavigationController.pushToNavigation()) .from(SingleContainerStep(finder: NilFinder(), factory: NavigationControllerFactory())) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble()
Router akan melalui rantai langkah-langkah mulai dari yang pertama, hingga salah satu langkah (menggunakan Finder
disediakan) "melaporkan" bahwa UIViewController
diinginkan sudah ada pada stack. (Misalnya, GeneralStep.current()
dijamin akan hadir dalam tumpukan pengontrol) .Kemudian router akan mulai bergerak kembali di sepanjang rantai langkah-langkah menciptakan UIViewController
diperlukan menggunakan UIViewController
yang disediakan dan mengintegrasikannya menggunakan Action
ditentukan s. Berkat mengetik pemeriksaan bahkan pada tahap kompilasi, paling sering, Anda tidak akan dapat menggunakan UITabBarController.addTab
tidak sesuai dengan Fabric
disediakan (yaitu, Anda tidak akan dapat menggunakan UITabBarController.addTab
di pengontrol UITabBarController.addTab
dibuat oleh NavigationControllerFactory
).
Jika Anda membayangkan konfigurasi yang dijelaskan di atas, maka jika Anda hanya memiliki ProductViewController
tertentu di layar, langkah-langkah berikut akan dilakukan:
ClassFinder
tidak akan menemukan ProductViewController
dan router akan melanjutkanNilFinder
tidak akan pernah menemukan apa pun dan router akan melanjutkanGeneralStep.current
akan selalu mengembalikan UIViewController
paling atas pada stack.- Mulai
UIViewController
ditemukan, router akan kembali - Membangun
UINavigationController
menggunakan `NavigationControllerFactory - Akan menunjukkannya secara digital menggunakan
GeneralAction.presentModally
ProductViewController
ProductViewController ProductViewControllerFactory
- Mengintegrasikan
ProductViewController
dibuat ke dalam UINavigationController
sebelumnya menggunakan UINavigationController.pushToNavigation
- Selesaikan navigasi
NB: Harus dipahami bahwa pada kenyataannya tidak mungkin untuk menunjukkan UINavigationController
modal tanpa beberapa UIViewController
di dalamnya. Oleh karena itu, langkah 5-8 akan dilakukan oleh router dalam urutan yang sedikit berbeda. Tetapi Anda tidak harus memikirkannya. Konfigurasi dijelaskan secara berurutan.
Praktik yang baik saat menulis konfigurasi adalah mengasumsikan bahwa pengguna dapat ditemukan di mana saja di aplikasi Anda saat ini, dan, tiba-tiba, menerima pesan push dengan permintaan untuk membuka layar yang Anda gambarkan, dan mencoba menjawab pertanyaan - "Bagaimana seharusnya aplikasi berperilaku? ? "," Bagaimana Finder
berperilaku dalam konfigurasi yang saya jelaskan? ". Jika semua pertanyaan ini diperhitungkan, Anda mendapatkan konfigurasi yang dijamin untuk menunjukkan kepada pengguna layar yang diinginkan di mana pun dia berada. Dan ini adalah persyaratan utama untuk aplikasi modern dari tim yang terlibat dalam pemasaran dan menarik pengguna (menarik) .
StackIteratingFinder
dan opsinya:
Anda dapat menerapkan konsep Finder
dengan cara apa pun yang menurut Anda paling dapat diterima. Namun, cara termudah adalah beralih melalui grafik pengontrol tampilan di layar. Untuk menyederhanakan tujuan ini, perpustakaan menyediakan StackIteratingFinder
dan berbagai implementasi yang akan mengambil tugas ini. Anda hanya perlu menjawab pertanyaan - apakah ini UIViewController
yang Anda harapkan.
Untuk memengaruhi perilaku StackIteratingFinder
dan memberitahukannya di bagian grafik mana (saya ingin pengontrolnya mencari, Anda bisa menentukan kombinasi SearchOptions
saat membuatnya. Dan mereka harus tinggal lebih detail:
current
: Pengontrol tampilan teratas pada tumpukan. (Yang merupakan rootViewController
UIWindow
atau yang ditampilkan secara moderat di atas)visible
: Jika UIViewController
adalah sebuah wadah, lihat ah yang terlihat di UIViewController
(Sebagai contoh: UINavigationController
selalu memiliki satu UIViewController
terlihat, UISplitController
mungkin memiliki satu atau dua tergantung pada bagaimana disajikan.)contained
: Dalam hal UIViewController
adalah sebuah wadah, cari di semua UIViewController
bersarang (Misalnya: Periksa semua pengontrol tampilan UINavigationController
termasuk yang terlihat)presenting
: Cari juga di semua ah UIViewController
bawah yang UIViewController
(jika ada tentu saja)presented
: Cari UIViewController
untuk yang disediakan (untuk StackIteratingFinder
opsi ini tidak masuk akal, karena selalu dimulai dari atas)
Gambar berikut dapat membuat penjelasan di atas lebih jelas:

Saya akan merekomendasikan membiasakan diri dengan konsep wadah di artikel sebelumnya.
Contoh Jika Anda ingin Finder
Anda mencari AccountViewController
di seluruh tumpukan, tetapi hanya di antara UIViewController
terlihat, maka ini harus ditulis seperti ini:
ClassFinder<AccountViewController, Any?>(options: [.current, .visible, .presenting])
NB Jika karena alasan tertentu pengaturan yang disediakan akan sedikit - Anda selalu dapat dengan mudah menulis implementasi Finder
a. Salah satu contoh akan ada di artikel ini.
Mari kita beralih pada contoh.
Contoh konfigurasi dengan penjelasan
Saya punya UIViewController
tertentu, yang merupakan rootViewController
UIWindow
, dan saya ingin HomeViewController
dengan HomeViewController
tertentu di akhir navigasi:
let screen = StepAssembly( finder: ClassFinder<HomeViewController, Any?>(), factory: XibFactory()) .using(GeneralAction.replaceRoot()) .from(GeneralStep.root()) .assemble()
XibFactory
memuat HomeViewController
dari file xib HomeViewController.xib
Jangan lupa bahwa jika Anda menggunakan implementasi abstrak dari Finder
dan Factory
dalam kombinasi, Anda harus menentukan tipe UIViewController
dan konteks untuk setidaknya satu entitas - ClassFinder<HomeViewController, Any?>
Apa yang terjadi jika, dalam contoh di atas, saya mengganti GeneralStep.root
dengan GeneralStep.current
?
Konfigurasi akan berfungsi hingga dipanggil saat ada modal UIViewController
di layar. Dalam hal ini, GeneralAction.replaceRoot
tidak akan dapat mengganti pengendali root, karena ada pengendali modal di atasnya, dan router akan melaporkan kesalahan. Jika Anda tetap ingin konfigurasi ini bekerja, maka Anda perlu menjelaskan kepada router bahwa Anda ingin GeneralAction.replaceRoot
diterapkan secara khusus ke root UIViewController
. Kemudian router akan menghapus semua UIViewController
diwakili secara UIViewController
dan konfigurasi akan bekerja dalam situasi apa pun.
Saya ingin menunjukkan beberapa AccountViewController
, jika masih ditampilkan dengan baik, di dalam UINavigationController
dan yang saat ini ada di layar di suatu tempat (bahkan jika UINavigationController
berada di bawah modal UIViewController
):
let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(SingleStep(ClassFinder<UINavigationController, Any?>(), NilFactory())) .from(GeneralStep.current()) .assemble()
Apa yang dimaksud dengan NilFactory
dalam konfigurasi ini? Dengan ini, Anda memberi tahu router bahwa jika ia tidak dapat menemukan UINavigationController
di layar, Anda tidak ingin dia membuatnya dan hanya tidak melakukan apa pun dalam kasus ini. Ngomong-ngomong, karena ini adalah NilFactory
, Anda tidak bisa menggunakan Action
setelahnya.
Saya ingin menunjukkan beberapa AccountViewController
, jika belum ditampilkan, di dalam UINavigationController
apa pun dan yang saat ini berada di suatu tempat di layar, dan jika tidak berubah menjadi seperti itu, buat dan tunjukkan secara modular:
let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.PushToNavigation()) .from(SwitchAssembly<UINavigationController, Any?>() .addCase(expecting: ClassFinder<UINavigationController, Any?>(options: .visible))
Saya ingin menunjukkan UITabBarController
dengan UITabBarController
berisi HomeViewController
dan AccountViewController
menggantinya dengan root saat ini:
let tabScreen = SingleContainerStep( finder: ClassFinder(), factory: CompleteFactoryAssembly(factory: TabBarControllerFactory()) .with(XibFactory<HomeViewController, Any?>(), using: UITabBarController.addTab()) .with(XibFactory<AccountViewController, Any?>(), using: UITabBarController.addTab()) .assemble()) .using(GeneralAction.replaceRoot()) .from(GeneralStep.root()) .assemble()
Dapatkah saya menggunakan UIViewControllerTransitioningDelegate
khusus dengan tindakan GeneralAction.presentModally
:
let transitionController = CustomViewControllerTransitioningDelegate()
Saya ingin pergi ke AccountViewController
, di mana pun pengguna berada, di tab lain atau bahkan di beberapa jenis modal modal:
let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: NilFactory()) .from(tabScreen) .assemble()
Mengapa kita menggunakan NilFactory
? Kami tidak perlu membangun AccountViewController
jika tidak ditemukan. Ini akan dibangun di konfigurasi tabScreen
. Lihat dia di atas.
Saya ingin menunjukkan secara modern ForgotPasswordViewController
, tetapi, tentu saja, setelah LoginViewController
di dalam UINavigationController
:
let loginScreen = StepAssembly( finder: ClassFinder<LoginViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(NavigationControllerStep()) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() let forgotPasswordScreen = StepAssembly( finder: ClassFinder<ForgotPasswordViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(loginScreen.expectingContainer()) .assemble()
Anda dapat menggunakan konfigurasi dalam contoh untuk navigasi di ForgotPasswordViewController
dan LoginViewController
Mengapa expectingContainer
dalam contoh di atas?
Karena tindakan pushToNavigation
memerlukan keberadaan UINavigationController
dan dalam konfigurasi setelahnya, metode expectingContainer
memungkinkan kita untuk menghindari kesalahan kompilasi dengan memastikan bahwa kita berhati-hati ketika router mencapai loginScreen
dalam loginScreen
, UINavigationController
akan ada di sana.
Apa yang terjadi jika dalam konfigurasi di atas saya mengganti GeneralStep.current
dengan GeneralStep.root
?
Ini akan berfungsi, tetapi karena Anda memberi tahu router bahwa Anda ingin mulai membangun rantai dari root UIViewController
, jika ada modal UIViewController
dibuka di atasnya, router akan menyembunyikannya sebelum Anda mulai membangun rantai.
Aplikasi saya memiliki UITabBarController
berisi HomeViewController
dan BagViewController
sebagai tab. Saya ingin pengguna dapat beralih di antara mereka menggunakan ikon pada tab seperti biasa. Tetapi jika saya memanggil konfigurasi secara terprogram (misalnya, pengguna mengklik "Pergi ke Tas" di dalam HomeViewController
), aplikasi tidak boleh beralih tab, tetapi tunjukkan BagViewController
.
Ada 3 cara untuk mencapai ini dalam konfigurasi:
- Setel
StackIteratingFinder
untuk mencari hanya yang terlihat menggunakan [.current, .visible] - Gunakan
NilFinder
yang berarti bahwa router tidak akan pernah menemukan BagViewController di BagViewController
dan akan selalu membuatnya. Namun, pendekatan ini memiliki efek samping - jika, misalnya, pengguna yang sudah ada di BagViewController
disajikan secara BagViewController
, dan, misalnya, mengklik tautan universal yang harus ditunjukkan BagViewController
kepadanya, maka router tidak akan menemukannya dan akan membuat contoh lain dan menunjukkannya di atasnya secara modal. Ini mungkin bukan yang Anda inginkan. - Ubah sedikit
ClassFinder
sehingga hanya menemukan BagViewController
ditampilkan secara modern dan mengabaikan sisanya, dan sudah menggunakannya dalam konfigurasi.
struct ModalBagFinder: StackIteratingFinder { func isTarget(_ viewController: BagViewController, with context: Any?) -> Bool { return viewController.presentingViewController != nil } } let screen = StepAssembly( finder: ModalBagFinder(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(NavigationControllerStep()) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble()
Alih-alih sebuah kesimpulan
Saya harap metode konfigurasi router menjadi lebih jelas. Seperti yang saya katakan, kami menggunakan pendekatan ini dalam 3 aplikasi dan belum mengalami situasi di mana itu tidak cukup fleksibel. Perpustakaan, serta implementasi router yang disediakan untuknya, tidak menggunakan trik objektif dengan runtime dan sepenuhnya mengikuti semua konsep Cocoa Touch, hanya membantu memecah proses komposisi menjadi langkah-langkah dan mengeksekusi mereka dalam urutan yang diberikan dan diuji dengan iOS versi 9 sampai 12. Selain itu , pendekatan ini cocok dengan semua pola arsitektur yang melibatkan bekerja dengan tumpukan UIViewController
(MVC, MVVM, VIP, RIB, VIPER, dll.)
Saya akan senang atas komentar dan saran Anda. Terutama jika Anda berpikir bahwa beberapa aspek layak untuk ditelusuri lebih detail. Mungkin konsep konteks perlu klarifikasi.