Contoh Konfigurasi UIViewControllers Menggunakan RouteComposer

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:


  1. ClassFinder tidak akan menemukan ProductViewController dan router akan melanjutkan
  2. NilFinder tidak akan pernah menemukan apa pun dan router akan melanjutkan
  3. GeneralStep.current akan selalu mengembalikan UIViewController paling atas pada stack.
  4. Mulai UIViewController ditemukan, router akan kembali
  5. Membangun UINavigationController menggunakan `NavigationControllerFactory
  6. Akan menunjukkannya secara digital menggunakan GeneralAction.presentModally
  7. ProductViewController ProductViewController ProductViewControllerFactory
  8. Mengintegrasikan ProductViewController dibuat ke dalam UINavigationController sebelumnya menggunakan UINavigationController.pushToNavigation
  9. 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)) //   -    .assemble(default: { //      return ChainAssembly() .from(SingleContainerStep(finder: NilFinder(), factory: NavigationControllerFactory())) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() }) ).assemble() 

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() //     .using(GeneralAction.PresentModally(transitioningDelegate: transitionController)) 

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:


  1. Setel StackIteratingFinder untuk mencari hanya yang terlihat menggunakan [.current, .visible]
  2. 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.
  3. 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.

Source: https://habr.com/ru/post/id428990/


All Articles