Anwendungsentwicklung auf SwiftUI. Teil 1: Datenfluss und Redux



Nachdem ich an der Sitzung zur Lage der Union auf der WWDC 2019 teilgenommen hatte, beschloss ich, SwiftUI im Detail zu studieren. Ich habe viel Zeit mit ihm verbracht und jetzt angefangen, eine echte Anwendung zu entwickeln, die für eine Vielzahl von Benutzern nützlich sein kann.

Ich habe es MovieSwiftUI genannt - dies ist eine App zum Auffinden neuer und alter Filme sowie zum Sammeln dieser Filme in einer Sammlung mithilfe der TMDB-API . Ich habe Filme immer geliebt und sogar eine Firma gegründet, die in diesem Bereich arbeitet, wenn auch lange Zeit. Es war schwer, die Firma cool zu nennen, aber die Anwendung - ja!

Wir erinnern Sie daran: Für alle Leser von „Habr“ - ein Rabatt von 10.000 Rubel bei der Anmeldung für einen Skillbox-Kurs mit dem Aktionscode „Habr“.

Skillbox empfiehlt: Der Online-Schulungskurs "Profession Java-Entwickler" .

Was macht MovieSwiftUI?

  • Interagiert mit der API - das macht fast jede moderne Anwendung.
  • Lädt asynchrone Anforderungsdaten und analysiert JSON im Swift-Modell mithilfe von Codable .
  • Zeigt Bilder an, die bei Bedarf heruntergeladen wurden, und speichert sie zwischen.
  • Diese App für iOS, iPadOS und macOS bietet die beste Benutzeroberfläche für Benutzer dieser Betriebssysteme.
  • Der Benutzer kann Daten generieren und eigene Filmlisten erstellen. Die Anwendung speichert Benutzerdaten und stellt sie wieder her.
  • Ansichten, Komponenten und Modelle werden mithilfe des Redux-Musters klar voneinander getrennt. Der Datenstrom ist hier unidirektional. Es kann vollständig zwischengespeichert, wiederhergestellt und überschrieben werden.
  • Die Anwendung verwendet die Grundkomponenten SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal usw. Es bietet auch benutzerdefinierte Ansichten, Gesten, UI / UX.


Tatsächlich ist die Animation flüssig, das GIF erwies sich als etwas nervös

Die Arbeit an der Anwendung hat mir viel Erfahrung gebracht, und insgesamt ist es eine positive Erfahrung. Ich konnte eine voll funktionsfähige Anwendung schreiben, im September werde ich sie verbessern und gleichzeitig mit der Veröffentlichung von iOS 13 in den AppStore stellen.

Redux, BindableObject und EnvironmentObject




Ich arbeite jetzt seit ungefähr zwei Jahren mit Redux, daher weiß ich das relativ gut. Insbesondere verwende ich es im Frontend für die React- Website sowie für die Entwicklung nativer iOS- (Swift) und Android- (Kotlin) Anwendungen.

Ich habe es nie bereut, Redux als Datenflussarchitektur für die Erstellung einer Anwendung auf SwiftUI ausgewählt zu haben. Die schwierigsten Momente bei der Verwendung von Redux in der UIKit-Anwendung sind die Arbeit mit dem Store sowie das Abrufen und Abrufen von Daten und der Vergleich mit Ihren Ansichten / Komponenten. Dazu musste ich eine Art Connector-Bibliothek erstellen (auf ReSwift und ReKotlin). Funktioniert gut, aber ziemlich viel Code. Leider ist es (noch nicht) Open Source.

Gute Neuigkeiten! Die einzigen Dinge, über die Sie sich bei SwiftUI Sorgen machen müssen - wenn Sie Redux verwenden möchten - sind Speicher, Status und Reduzierungen. Die SwiftUI übernimmt dank @EnvironmentObject die Interaktion mit dem Store. Der Speicher beginnt also mit BindableObject.

Ich habe ein einfaches Swift-Paket erstellt, SwiftUIFlux , das die grundlegende Verwendung von Redux bietet. In meinem Fall ist dies Teil von MovieSwiftUI. Ich habe auch eine Schritt-für-Schritt-Anleitung geschrieben , die Ihnen bei der Verwendung dieser Komponente hilft.

Wie funktioniert es

final public class Store<State: FluxState>: BindableObject { public let willChange = PassthroughSubject<Void, Never>() private(set) public var state: State private func _dispatch(action: Action) { willChange.send() state = reducer(state, action) } } 

Jedes Mal, wenn Sie eine Aktion starten, aktivieren Sie das Getriebe. Er bewertet die Aktionen gemäß dem aktuellen Status der Anwendung. Dann wird ein neuer geänderter Zustand entsprechend der Art der Aktion und der Daten zurückgegeben.

Da store ein BindableObject ist, benachrichtigt es SwiftUI über eine Änderung seines Werts mithilfe der von PassthroughSubject bereitgestellten willChange-Eigenschaft. Dies liegt daran, dass BindableObject PublisherType bereitstellen muss, die Protokollimplementierung jedoch für die Verwaltung verantwortlich ist. Alles in allem ist dies ein sehr leistungsfähiges Tool von Apple. Dementsprechend hilft SwiftUI im nächsten Renderzyklus dabei, den Darstellungskörper gemäß der Statusänderung anzuzeigen.

Eigentlich ist das alles - das Herz und die Magie von SwiftUI. In jeder Ansicht, die einen Status abonniert hat, wird die Ansicht entsprechend den Daten angezeigt, die vom Status empfangen wurden und was sich geändert hat.

 class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) let controller = UIHostingController(rootView: HomeView().environmentObject(store)) window.rootViewController = controller self.window = window window.makeKeyAndVisible() } } } struct CustomListCoverRow : View { @EnvironmentObject var store: Store<AppState> let movieId: Int var movie: Movie! { return store.state.moviesState.movies[movieId] } var body: some View { HStack(alignment: .center, spacing: 0) { Image(movie.poster) }.listRowInsets(EdgeInsets()) } } 

Der Speicher wird beim Start der Anwendung als EnvironmentObject implementiert und ist dann in jeder Ansicht mit @EnvironmentObject verfügbar. Die Leistung wird nicht verringert, da abgeleitete Eigenschaften schnell aus dem Anwendungsstatus abgerufen oder berechnet werden.

Der obige Code ändert das Bild, wenn sich das Poster für den Film ändert.

Und dies geschieht tatsächlich in nur einer Zeile, mit deren Hilfe die Ansichten mit dem Staat verbunden werden. Wenn Sie mit ReSwift unter iOS gearbeitet oder sich sogar mit React verbunden haben, werden Sie verstehen, worum es bei SwiftUI Magic geht.

Und jetzt können Sie versuchen, die Aktion zu aktivieren und einen neuen Status zu veröffentlichen. Hier ist ein komplexeres Beispiel.

 struct CustomListDetail : View { @EnvironmentObject var store: Store<AppState> let listId: Int var list: CustomList { store.state.moviesState.customLists[listId]! } var movies: [Int] { list.movies.sortedMoviesIds(by: .byReleaseDate, state: store.state) } var body: some View { List { ForEach(movies) { movie in NavigationLink(destination: MovieDetail(movieId: movie).environmentObject(self.store)) { MovieRow(movieId: movie, displayListImage: false) } }.onDelete { (index) in self.store.dispatch(action: MoviesActions.RemoveMovieFromCustomList(list: self.listId, movie: self.movies[index.first!])) } } } } 

Im obigen Code verwende ich für jede IP die Aktion .onDelete von SwiftUI. Dadurch kann in der Zeile in der Liste der übliche iOS-Wisch zum Löschen angezeigt werden. Wenn der Benutzer die Schaltfläche Löschen berührt, startet er die entsprechende Aktion und entfernt den Film aus der Liste.

Da die List-Eigenschaft vom Status von BindableObject abgeleitet und als EnvironmentObject implementiert ist, aktualisiert SwiftUI die Liste, da ForEach der berechneten Filmeigenschaft zugeordnet ist.

Hier ist ein Teil des MoviesState-Reduzierers:

 func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState { var state = state switch action { // other actions. case let action as MoviesActions.AddMovieToCustomList: state.customLists[action.list]?.movies.append(action.movie) case let action as MoviesActions.RemoveMovieFromCustomList: state.customLists[action.list]?.movies.removeAll{ $0 == action.movie } default: break } return state } 

Der Reduzierer wird ausgeführt, wenn Sie die Aktion senden und einen neuen Status zurückgeben, wie oben erwähnt.

Ich werde vorerst nicht auf Details eingehen - woher weiß SwiftUI wirklich, was angezeigt werden soll. Um dies besser zu verstehen, sollten Sie sich die WWDC-Sitzung zum Datenfluss in SwiftUI ansehen. Außerdem wird ausführlich erläutert, warum und wann State , @Binding, ObjectBinding und EnvironmentObject verwendet werden sollen.

Skillbox empfiehlt:

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


All Articles