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!