Mengadaptasi solusi bisnis Anda yang sudah ada untuk SwiftUI. Bagian 4. Navigasi dan konfigurasi

Hari baik untuk semua! Dengan Anda, saya, Anna Zharkova, pengembang seluler terkemuka Usetech,
sekarang mari kita bicara tentang hal menarik lain dalam SwiftUI, navigasi.

Jika Anda melewatkan artikel sebelumnya dalam seri, Anda dapat membacanya di sini:

bagian 1
bagian 2
bagian 3

Dengan perubahan dalam deskripsi bagian visual dan transisi ke sintaksis deklaratif, kontrol navigasi dalam aplikasi SwiftUI juga telah berubah. Penggunaan UIViewContoller langsung ditolak, UINavigationController tidak langsung digunakan. Itu digantikan oleh NavigationView.

@available(iOS 13.0, OSX 10.15, tvOS 13.0, *)
@available(watchOS, unavailable)
public struct NavigationView<Content> : View where Content : View {

    public init(@ViewBuilder content: () -> Content)

    //....
}

Pada dasarnya pembungkus atas UINavigationController dan fungsinya.


Mekanisme transisi utama adalah NavigationLink (analog segue), yang diatur langsung dalam kode tampilan tubuh.

public struct NavigationLink<Label, Destination> : View where Label : View, 
                                                                                           Destination : View {
.
    public init(destination: Destination, @ViewBuilder label: () -> Label)

    public init(destination: Destination, isActive: Binding<Bool>, 
                   @ViewBuilder label: () -> Label)

    public init<V>(destination: Destination, tag: V, selection: Binding<V?>,
                          @ViewBuilder label: () -> Label) where V : Hashable
//....
}

Saat membuat NavigationLink, ini menunjukkan Tampilan tempat transisi dibuat, serta Tampilan yang dibungkus NavigationLink, yaitu ketika berinteraksi dengannya, NavigationLink diaktifkan. Informasi lebih lanjut tentang cara-cara yang mungkin untuk menginisialisasi NavigationLink dalam dokumentasi Apple.

Namun, harus diingat bahwa tidak ada akses langsung ke tumpukan Lihat karena enkapsulasi, navigasi diprogram hanya maju, kembali hanya mungkin 1 tingkat ke belakang dan kemudian melalui kode enkapsulasi untuk tombol "Kembali"



Juga di SwiftUI tidak ada navigasi perangkat lunak dinamis. Jika transisi tidak terkait dengan peristiwa pemicu, misalnya, menekan tombol, tetapi mengikuti sebagai akibat dari beberapa jenis logika, maka itu tidak dapat dilakukan. Transisi ke Tampilan berikutnya harus terikat pada mekanisme NavigationLink, yang segera diatur secara deklaratif saat mendeskripsikan Tampilan yang memuatnya. Semua.

Jika layar kita harus berisi transisi ke banyak layar yang berbeda, maka kodenya menjadi rumit:

  NavigationView{
            NavigationLink(destination: ProfileView(), isActive: self.$isProfile) {
                Text("Profile")
            }
            NavigationLink(destination: Settings(), isActive: self.$isSettings) {
                           Text("Settings")
                       }
            NavigationLink(destination: Favorite(), isActive: self.$isFavorite) {
                Text("Favorite")
            }
            NavigationLink(destination: Login(), isActive: self.$isLogin) {
                Text("Login")
            }
            NavigationLink(destination: Search(), isActive: self.$isSearch) {
                Text("Search")
            }
}

Kami dapat mengelola tautan dengan beberapa cara:
- Kontrol aktivitas NavigationLink melalui properti @Binding

 NavigationLink(destination: ProfileView(), isActive: self.$isProfile) {
                Text("Profile")
            }

- Kontrol atas pembuatan tautan melalui suatu kondisi (variabel keadaan)

       if self.isProfile {
            NavigationLink(destination: ProfileView()) {
                Text("Profile")
            }
}

Metode pertama menambah pekerjaan pemantauan status variabel kontrol.
Jika kami berencana untuk menavigasi lebih dari 1 tingkat ke depan, maka ini adalah tugas yang sangat sulit.

Dalam kasus daftar layar daftar barang serupa, semuanya terlihat kompak:

 NavigationView{
        List(model.data) { item in
            NavigationLink(destination: NewsItemView(item:item)) {
            NewsItemRow(data: item)
            }
        }

Masalah yang paling serius dari NavigationLink, menurut saya, adalah bahwa semua tautan yang ditentukan tidak malas. Mereka diciptakan bukan pada saat tautan dipicu, tetapi pada saat pembuatan. Jika kami memiliki daftar banyak elemen atau transisi ke banyak konten berat Lihat berbeda, ini tidak akan mempengaruhi kinerja aplikasi kami dengan cara terbaik. Jika kita masih memiliki ViewModel yang dilampirkan ke View ini dengan logika, di mana implementasi siklus hidup View tidak diperhitungkan atau tidak diperhitungkan dengan benar, maka situasinya menjadi sangat sulit.

Misalnya, kami memiliki daftar berita dengan elemen-elemen dengan tipe yang sama. Kami belum pernah pergi ke satu layar pun dari satu item berita, dan model-modelnya sudah melekat dalam ingatan kami:



Apa yang dapat kami lakukan dalam kasus ini untuk membuat hidup kami lebih mudah?

Pertama, ingat bahwa View tidak ada dalam ruang hampa, tetapi ditampilkan di UIHostingController.

open class UIHostingController<Content> : UIViewController where Content : View {

    public init(rootView: Content)

    public var rootView: Content
//...
}

Dan ini adalah UIViewController. Jadi kita bisa melakukan hal berikut. Kami akan mentransfer semua tanggung jawab untuk transisi ke Tampilan berikutnya di dalam UIHostingController baru ke pengontrol Tampilan saat ini. Mari kita buat modul navigasi dan konfigurasi yang akan kita panggil dari View kita.

Navigator yang bekerja dengan UIViewController akan terlihat seperti ini:

class Navigator {
    private init(){}
    
    static let shared = Navigator()
    
    private weak var view: UIViewController?
    
    internal weak var nc: UINavigationController?
        
   func setup(view: UIViewController) {
        self.view = view
    }
     
  internal func open<Content:View>(screen: Content.Type, _ data: Any? = nil) {
     if let vc = ModuleConfig.shared.config(screen: screen)?
        .createScreen(data) {
        self.nc?.pushViewController(vc, animated: true)
        }
   }

Dengan prinsip yang sama, kami akan membuat pabrik konfigurator, yang akan memberi kami implementasi konfigurator dari modul tertentu:

protocol IConfugator: class {
    func createScreen(_ data: Any?)->UIViewController
}

class ModuleConfig{
    private init(){}
    static let shared = ModuleConfig()
    
    func config<Content:View>(screen: Content.Type)->IConfugator? {
        if screen == NewsListView.self {
            return NewsListConfigurator.shared
        }
      // -
        return nil
    }
}

Navigator, berdasarkan jenis layar, meminta konfigurator modul tertentu, mentransfer kepadanya semua informasi yang diperlukan.

class NewsListConfigurator: IConfugator {
    static let shared = NewsListConfigurator()
    
    func createScreen(_ data: Any?) -> UIViewController {
         var view = NewsListView()
        let presenter = NewsListPresenter()
         let interactor = NewsListInteractor()
        
        interactor.output = presenter
        presenter.output = view
        view.output = interactor
        
        let vc = UIHostingController<ContainerView<NewsListView>>
                    (rootView: ContainerView(content: view))
        return vc
    }
}

Configurator memberikan UIViewController, yang merupakan Navigator dan mendorong UINavigationController ke tumpukan bersama.



Ganti NavigationLink dalam kode dengan panggilan ke Navigator. Sebagai pemicu, kami akan memiliki acara mengklik pada item daftar:

  List(model.data) { item in
            NewsItemRow(data: item)
                .onTapGesture {
                Navigator.shared.open(screen: NewsItemView.self, item)
            }
        }

Tidak ada yang mencegah kami dari memanggil Navigator dalam metode Tampilan apa pun dengan cara yang sama. Bukan hanya di dalam tubuh.

Selain fakta bahwa kodenya menjadi lebih bersih, kami juga menurunkan memori. Bagaimanapun, dengan pendekatan ini, View hanya akan dibuat ketika dipanggil.



Sekarang aplikasi SwiftUI kami lebih mudah untuk diperluas dan dimodifikasi. Kode itu bersih dan indah.
Anda dapat menemukan kode contoh di sini .

Lain kali kita akan berbicara tentang implementasi yang lebih dalam dari Combine.

All Articles