Anpassung Ihrer vorhandenen Geschäftslösung an SwiftUI. Teil 4. Navigation und Konfiguration

Guten Tag allerseits! Mit Ihnen ich, Anna Zharkova, eine führende mobile Entwicklerin von Usetech.
Lassen Sie uns nun über einen weiteren interessanten Punkt in SwiftUI sprechen, die Navigation.

Wenn Sie die vorherigen Artikel in der Reihe verpasst haben, können Sie sie hier lesen:

Teil 1
Teil 2
Teil 3

Mit der Änderung der Beschreibung des visuellen Teils und dem Übergang zur deklarativen Syntax hat sich auch die Navigationssteuerung in der SwiftUI-Anwendung geändert. Die Verwendung des UIViewContoller wird direkt verweigert, der UINavigationController wird nicht direkt verwendet. Es wird durch NavigationView ersetzt.

@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)

    //....
}

Im Wesentlichen ein Wrapper über den UINavigationController und seine Funktionalität.


Der Hauptübergangsmechanismus ist NavigationLink (ähnlich wie segue), der sofort im Body View-Code festgelegt wird.

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
//....
}

Beim Erstellen eines Navigationslinks wird die Ansicht angezeigt, zu der der Übergang erfolgt, sowie die Ansicht, die der Navigationslink umschließt, d. H. Bei der Interaktion mit ihm wird der Navigationslink aktiviert. Weitere Informationen zu den möglichen Möglichkeiten zum Initialisieren von NavigationLink finden Sie in der Apple-Dokumentation.

Es ist jedoch zu beachten, dass aufgrund der Kapselung kein direkter Zugriff auf den View-Stapel besteht. Die Navigation wird nur vorwärts programmiert. Die Rückgabe ist nur 1 Ebene zurück und dann über den gekapselten Code für die Schaltfläche "Zurück" möglich



Auch in SwiftUI gibt es keine dynamische Software-Navigation. Wenn der Übergang nicht an ein Triggerereignis gebunden ist, z. B. durch Drücken einer Taste, sondern aufgrund einer Logik folgt, tun Sie dies einfach nicht. Der Übergang zur nächsten Ansicht ist notwendigerweise an den NavigationLink-Mechanismus gebunden, der bei der Beschreibung der sie enthaltenden Ansicht sofort deklarativ festgelegt wird. Alle.

Wenn unser Bildschirm einen Übergang zu vielen verschiedenen Bildschirmen enthalten soll, wird der Code umständlich:

  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")
            }
}

Wir können Links auf verschiedene Arten verwalten:
- NavigationLink-Aktivitätssteuerung über die Eigenschaft @Binding

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

- Kontrolle über die Linkerstellung durch eine Bedingung (Zustandsvariablen)

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

Die erste Methode erweitert uns um die Überwachung des Zustands von Steuervariablen.
Wenn wir vorhaben, mehr als eine Ebene voraus zu navigieren, ist dies eine sehr schwierige Aufgabe.

Auf dem Bildschirm mit der Liste ähnlicher Elemente sieht alles kompakt aus:

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

Das schwerwiegendste Problem von NavigationLink ist meiner Meinung nach, dass alle angegebenen View-Links nicht faul sind. Sie werden nicht zum Zeitpunkt der Auslösung der Verknüpfung erstellt, sondern zum Zeitpunkt der Erstellung. Wenn wir eine Liste mit vielen Elementen oder Übergängen zu vielen verschiedenen View-Inhalten haben, wirkt sich dies nicht optimal auf die Leistung unserer Anwendung aus. Wenn wir ViewModel immer noch mit Logik an diese Ansicht angehängt haben, bei deren Implementierung die Lebenszyklusansicht nicht oder nicht korrekt berücksichtigt wird, wird die Situation sehr schwierig.

Zum Beispiel haben wir eine Nachrichtenliste mit Elementen des gleichen Typs. Wir sind noch nie zu einem einzigen Bildschirm einer einzelnen Nachricht gegangen, und die Modelle hängen bereits in unserer Erinnerung:



Was können wir in diesem Fall tun, um unser Leben einfacher zu machen?

Denken Sie zunächst daran, dass View nicht in einem Vakuum vorhanden ist, sondern im UIHostingController gerendert wird.

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

    public init(rootView: Content)

    public var rootView: Content
//...
}

Und das ist der UIViewController. Wir können also Folgendes tun. Wir übertragen die gesamte Verantwortung für den Wechsel zur nächsten Ansicht im neuen UIHostingController auf den aktuellen View-Controller. Erstellen wir Navigations- und Konfigurationsmodule, die wir aus unserer Ansicht aufrufen.

Der mit dem UIViewController arbeitende Navigator sieht folgendermaßen aus:

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)
        }
   }

Nach dem gleichen Prinzip werden wir eine Fabrik von Konfiguratoren erstellen, die uns die Implementierung des Konfigurators eines bestimmten Moduls ermöglicht:

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
    }
}

Der Navigator fragt nach der Art des Bildschirms nach dem Konfigurator eines bestimmten Moduls und überträgt ihm alle erforderlichen Informationen.

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
    }
}

Der Konfigurator gibt den UIViewController, den Navigator, weiter und schiebt den UINavigationController auf den gemeinsam genutzten Stapel.



Ersetzen Sie NavigationLink im Code durch einen Aufruf von Navigator. Als Auslöser haben wir das Ereignis, auf ein Listenelement zu klicken:

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

Nichts hindert uns daran, Navigator in einer View-Methode auf dieselbe Weise aufzurufen. Nicht nur im Körper.

Neben der Tatsache, dass der Code spürbar sauberer geworden ist, haben wir auch den Speicher entladen. Schließlich wird bei diesem Ansatz die Ansicht nur beim Aufruf erstellt.



Jetzt ist unsere SwiftUI-Anwendung einfacher zu erweitern und zu ändern. Der Code ist sauber und schön.
Den Beispielcode finden Sie hier .

Nächstes Mal werden wir über die tiefere Implementierung von Combine sprechen.

All Articles