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!