La traduction de l'article a été préparée spécialement pour les étudiants du cours «Développeur iOS. Cours avancé v 2.0. ”
La semaine dernière, nous avons commencé une nouvelle série de
publications sur le cadre SwiftUI. Aujourd'hui, je veux poursuivre ce sujet en parlant des
Property Wrappers dans SwiftUI. SwiftUI nous fournit des wrappers pour
@State
,
@Binding
,
@ObservedObject
,
@EnvironmentObject
et
@Environment
. Essayons donc de comprendre la différence entre eux et quand, pourquoi et lequel utiliser.
Enveloppeurs de propriété
Les wrappers de propriété (ci
- après
dénommés «wrappers de propriété») sont décrits dans
SE-0258 . L'idée principale est d'envelopper les propriétés avec la logique, qui peut être extraite dans une structure distincte pour être réutilisée dans la base de code.
@State
est un wrapper que nous pouvons utiliser pour indiquer l'état d'une
View
. SwiftUI le stockera dans une mémoire interne spéciale en dehors de la structure
View
. Seule une
View
liée peut y accéder. Lorsque la valeur de la propriété
@State
change, SwiftUI reconstruit la
View
pour tenir compte des changements d'état. Voici un exemple simple.
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) } } } } }
Dans l'exemple ci-dessus, nous avons un écran simple avec un bouton et une liste de produits. Dès que nous cliquons sur le bouton, il change la valeur de la propriété d'état et SwiftUI reconstruit la
View
.
@Binding
@Binding
fournit un accès de référence pour le type de valeur. Parfois, nous devons rendre l'état de notre
View
accessible à ses enfants. Mais nous ne pouvons pas simplement prendre et transmettre cette valeur, car il s'agit d'un type de valeur, et Swift transmettra une copie de cette valeur. C'est là que l'habillage de la propriété
@Binding
vient à la rescousse.
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) } } } } }
Nous utilisons
@Binding
pour marquer la propriété
showFavorited
à l'intérieur de
FilterView
. Nous utilisons également le caractère spécial
$
pour passer le lien d'ancrage, car sans
$
Swift, il transmettra une copie de la valeur au lieu de passer le lien d'ancrage lui-même.
FilterView
peut lire et écrire la valeur de la propriété
showFavorited
dans un
ProductsView
, mais ne peut pas suivre les modifications à l'aide de cette liaison. Dès que
FilterView
modifie la valeur de la propriété showFavorited, SwiftUI recrée
ProductsView
et
FilterView
tant qu'enfant.
@ObservedObject
@ObservedObject
fonctionne de manière similaire à
@State
, mais la principale différence est que nous pouvons le diviser entre plusieurs
View
indépendantes, qui peuvent s'abonner et regarder les modifications de cet objet, et dès que les modifications apparaissent,
SwiftUI
reconstruit toutes les vues associées à cet objet . Regardons un exemple.
import Combine final class PodcastPlayer: ObservableObject { @Published private(set) var isPlaying: Bool = false func play() { isPlaying = true } func pause() { isPlaying = false } }
Nous avons ici la classe
PodcastPlayer
, que les écrans de notre application partagent entre eux. Chaque écran doit afficher un bouton de pause flottant lorsque l'application lit un épisode de podcast.
SwiftUI
suit les modifications apportées à un
ObservableObject
à l'aide de l'encapsuleur
@Published
, et dès que la propriété marquée comme
@Published
change,
SwiftUI
reconstruit toutes les
SwiftUI
associées à ce
PodcastPlayer
. Ici, nous utilisons le wrapper
@ObservedObject
pour lier notre
EpisodesView
à la classe
PodcastPlayer
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
Au lieu de passer un
ObservableObject
via la méthode init de notre
View
, nous pouvons l'impliquer implicitement dans l'
Environment
notre hiérarchie de
View
. Ce faisant, nous permettons à toutes les vues enfant de l'
Environment
actuel d'accéder à cet
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) } } } }
Comme vous pouvez le voir, nous devons passer le
PodcastPlayer
via le modificateur
environmentObject
de notre
View
. Ce faisant, nous pouvons facilement accéder au
PodcastPlayer
en le définissant à l'aide du wrapper
@EnvironmentObject
.
@EnvironmentObject
utilise la fonction de recherche dynamique de membres pour trouver une instance de la classe
PodcastPlayer
dans
Environment
, vous n'avez donc pas besoin de la passer par la méthode init
EpisodesView
. L'environnement est le bon moyen d'
injecter des dépendances dans
SwiftUI .
@Environnement
Comme nous l'avons dit dans le chapitre précédent, nous pouvons transférer des objets personnalisés vers la hiérarchie de la
View
Environment
dans
SwiftUI . Mais
SwiftUI a déjà un
Environment
rempli de paramètres à l'échelle du système. Nous pouvons facilement y accéder en utilisant le wrapper
@Environment
.
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) } }
En marquant nos propriétés avec l'encapsuleur
@Environment
, nous avons accès et souscrivons aux modifications apportées aux paramètres à l'échelle du système. Dès que les systèmes
Locale ,
Calendar ou
ColorScheme changent,
SwiftUI recrée notre
CalendarView
.
Conclusion
Aujourd'hui, nous avons parlé des Property Wrappers fournis par
SwiftUI .
@State
,
@Binding
,
@EnvironmentObject
et
@ObservedObject
jouent un rôle énorme dans le développement de
SwiftUI . Merci de votre attention!