Grundlegendes zu Eigenschaftenwrappern in SwiftUI

Die Übersetzung des Artikels wurde speziell für Studenten des Kurses „iOS Developer. Fortgeschrittenenkurs v 2.0. “




Letzte Woche haben wir eine neue Reihe von Posts zum SwiftUI-Framework gestartet. Heute möchte ich dieses Thema fortsetzen und über Property Wrapper in SwiftUI sprechen. SwiftUI stellt uns Wrapper für die @State , @Binding , @ObservedObject , @EnvironmentObject und @Environment . Versuchen wir also, den Unterschied zwischen ihnen zu verstehen und wann, warum und welche wir verwenden sollten.

Property Wrapper


Property Wrapper (im Folgenden als "Property Wrapper" bezeichnet) sind in SE-0258 beschrieben . Die Hauptidee besteht darin, Eigenschaften mit Logik zu umschließen, die zur Wiederverwendung in der Codebasis in eine separate Struktur extrahiert werden können.

Zustand


@State ist ein Wrapper, mit dem der Status einer View . SwiftUI speichert es in einem speziellen internen Speicher außerhalb der View Struktur. Nur eine verknüpfte View kann darauf zugreifen. Wenn sich der Wert der Eigenschaft @State ändert, erstellt SwiftUI die View , um Statusänderungen zu berücksichtigen. Hier ist ein einfaches Beispiel.

 struct ProductsView: View { let products: [Product] @State private var showFavorited: Bool = false var body: some View { List { Button( action: { self.showFavorited.toggle() }, label: { Text("Change filter") } ) ForEach(products) { product in if !self.showFavorited || product.isFavorited { Text(product.title) } } } } } 

Im obigen Beispiel haben wir einen einfachen Bildschirm mit einer Schaltfläche und einer Produktliste. Sobald wir auf die Schaltfläche klicken, ändert sich der Wert der Eigenschaft state, und SwiftUI erstellt die View .

@ Bindung


@Binding bietet Referenzzugriff für @Binding . Manchmal müssen wir den Zustand unserer View für seine Kinder zugänglich machen. Wir können diesen Wert jedoch nicht einfach annehmen und übergeben, da es sich um einen Werttyp handelt, und Swift übergibt eine Kopie dieses Werts. Hier @Binding der Wrapping der @Binding Eigenschaft.

 struct FilterView: View { @Binding var showFavorited: Bool var body: some View { Toggle(isOn: $showFavorited) { Text("Change filter") } } } struct ProductsView: View { let products: [Product] @State private var showFavorited: Bool = false var body: some View { List { FilterView(showFavorited: $showFavorited) ForEach(products) { product in if !self.showFavorited || product.isFavorited { Text(product.title) } } } } } 

Wir verwenden @Binding , um die showFavorited @Binding in der @Binding zu markieren. Wir verwenden auch das spezielle $ -Zeichen, um den Ankerlink zu übergeben, da es ohne $ Swift eine Kopie des Werts übergibt, anstatt den Ankerlink selbst zu übergeben. FilterView kann den Wert der Eigenschaft showFavorited in einer ProductsView lesen und schreiben, jedoch keine Änderungen mithilfe dieser Bindung verfolgen. Sobald die FilterView den Wert der Eigenschaft FilterView ändert, erstellt SwiftUI die ProductsView und die FilterView als showFavorited Element neu.

@ObservedObject


@ObservedObject funktioniert ähnlich wie @State , aber der Hauptunterschied besteht darin, dass wir es auf mehrere unabhängige View @State können, die Änderungen an diesem Objekt abonnieren und beobachten können. Sobald Änderungen auftreten, erstellt SwiftUI alle mit diesem Objekt SwiftUI Ansichten neu . Schauen wir uns ein Beispiel an.

 import Combine final class PodcastPlayer: ObservableObject { @Published private(set) var isPlaying: Bool = false func play() { isPlaying = true } func pause() { isPlaying = false } } 

Hier haben wir die PodcastPlayer Klasse, die die Bildschirme unserer Anwendung untereinander teilen. Auf jedem Bildschirm sollte eine schwebende Pause-Schaltfläche angezeigt werden, wenn die Anwendung eine Podcast-Episode abspielt. SwiftUI verfolgt Änderungen an einem ObservableObject mithilfe des @Published Wrappers. Sobald sich die als @Published markierte @Published ändert, erstellt SwiftUI alle mit diesem PodcastPlayer SwiftUI PodcastPlayer . Hier verwenden wir den @ObservedObject Wrapper, um unsere EpisodesView an die PodcastPlayer Klasse zu binden

 struct EpisodesView: View { @ObservedObject var player: PodcastPlayer let episodes: [Episode] var body: some View { List { Button( action: { if self.player.isPlaying { self.player.pause() } else { self.player.play() } }, label: { Text(player.isPlaying ? "Pause": "Play") } ) ForEach(episodes) { episode in Text(episode.title) } } } } 

@EnvironmentObject


Anstatt ein ObservableObject über die init-Methode unserer View , können wir es implizit in die Environment unserer View Hierarchie einbetten. Auf diese Weise ermöglichen wir allen untergeordneten Ansichten der aktuellen Environment den Zugriff auf dieses ObservableObject .

 class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let window = UIWindow(frame: UIScreen.main.bounds) let episodes = [ Episode(id: 1, title: "First episode"), Episode(id: 2, title: "Second episode") ] let player = PodcastPlayer() window.rootViewController = UIHostingController( rootView: EpisodesView(episodes: episodes) .environmentObject(player) ) self.window = window window.makeKeyAndVisible() } } struct EpisodesView: View { @EnvironmentObject var player: PodcastPlayer let episodes: [Episode] var body: some View { List { Button( action: { if self.player.isPlaying { self.player.pause() } else { self.player.play() } }, label: { Text(player.isPlaying ? "Pause": "Play") } ) ForEach(episodes) { episode in Text(episode.title) } } } } 

Wie Sie sehen, müssen wir den PodcastPlayer über den environmentObject Modifikator unserer View . Auf diese Weise können wir einfach auf den PodcastPlayer indem wir ihn mit dem @EnvironmentObject Wrapper definieren. @EnvironmentObject verwendet die dynamische @EnvironmentObject , um eine Instanz der PodcastPlayer Klasse in Environment , sodass Sie sie nicht über die Init-Methode von EpisodesView . Die Umgebung ist der richtige Weg, um Abhängigkeiten in SwiftUI einzufügen .

@Umwelt


Wie bereits im vorherigen Kapitel erwähnt, können wir benutzerdefinierte Objekte in die Environment View Hierarchie in SwiftUI übertragen . SwiftUI verfügt jedoch bereits über eine Environment mit systemweiten Einstellungen. Mit dem @Environment Wrapper können wir problemlos darauf zugreifen.

 struct CalendarView: View { @Environment(\.calendar) var calendar: Calendar @Environment(\.locale) var locale: Locale @Environment(\.colorScheme) var colorScheme: ColorScheme var body: some View { return Text(locale.identifier) } } 

Durch Markieren unserer Eigenschaften mit dem @Environment Wrapper erhalten wir Zugriff auf Änderungen an systemweiten Einstellungen und abonnieren diese. Sobald sich das Gebietsschema- , Kalender- oder ColorScheme- System ändert, erstellt SwiftUI unsere CalendarView .

Fazit


Heute haben wir über die Property Wrapper von SwiftUI gesprochen . @State , @Binding , @EnvironmentObject und @ObservedObject spielen eine wichtige Rolle bei der Entwicklung von SwiftUI . Vielen Dank für Ihre Aufmerksamkeit!

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


All Articles