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.
@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!