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!