Présentation des wrappers de propriété dans SwiftUI

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.

État


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

Source: https://habr.com/ru/post/fr475078/


All Articles