Comprender los envoltorios de propiedades en SwiftUI

La traducción del artículo fue preparada específicamente para estudiantes del curso "Desarrollador iOS. Curso avanzado v 2.0 ".




La semana pasada comenzamos una nueva serie de publicaciones sobre el marco SwiftUI. Hoy quiero continuar con este tema hablando sobre Property Wrappers en SwiftUI. SwiftUI nos proporciona envoltorios para @State , @Binding , @ObservedObject , @EnvironmentObject y @Environment . Entonces, tratemos de entender la diferencia entre ellos y cuándo, por qué y cuál debemos usar.

Envoltorios de propiedad


Los envoltorios de propiedades (en lo sucesivo denominados "envoltorios de propiedades") se describen en SE-0258 . La idea principal es ajustar las propiedades con la lógica, que se puede extraer en una estructura separada para su reutilización en la base del código.

Estado


@State es un contenedor que podemos usar para indicar el estado de una View . SwiftUI lo almacenará en una memoria interna especial fuera de la estructura de View . Solo una View vinculada puede acceder a ella. A @State cambia el valor de la propiedad @State , SwiftUI reconstruye la View para tener en cuenta los cambios de estado. Aquí hay un ejemplo 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) } } } } } 

En el ejemplo anterior, tenemos una pantalla simple con un botón y una lista de productos. Tan pronto como hacemos clic en el botón, cambia el valor de la propiedad del estado y SwiftUI reconstruye la View .

@Binding


@Binding proporciona acceso de referencia para el tipo de valor. A veces necesitamos hacer que el estado de nuestra View accesible para sus hijos. Pero no podemos simplemente tomar y pasar este valor, porque es un tipo de valor, y Swift pasará una copia de este valor. Aquí es donde la envoltura de la propiedad @Binding viene al rescate.

 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) } } } } } 

Usamos @Binding para marcar la propiedad showFavorited dentro de FilterView . También usamos el carácter especial $ para pasar el enlace de anclaje, porque sin $ Swift pasará una copia del valor en lugar de pasar el enlace de anclaje en sí. FilterView puede leer y escribir el valor de la propiedad showFavorited en ProductsView , pero no puede realizar un seguimiento de los cambios mediante este enlace. Tan pronto como FilterView cambie el valor de la propiedad showFavorited, SwiftUI vuelve a crear ProductsView y FilterView como elementos secundarios.

@ObservedObject


@ObservedObject funciona de manera similar a @State , pero la principal diferencia es que podemos dividirlo entre varias View independientes, que pueden suscribirse y observar los cambios de este objeto, y tan pronto como aparecen los cambios, SwiftUI reconstruye todas las vistas asociadas con este objeto . Veamos un ejemplo.

 import Combine final class PodcastPlayer: ObservableObject { @Published private(set) var isPlaying: Bool = false func play() { isPlaying = true } func pause() { isPlaying = false } } 

Aquí tenemos la clase PodcastPlayer , que las pantallas de nuestra aplicación comparten entre sí. Cada pantalla debe mostrar un botón de pausa flotante cuando la aplicación está reproduciendo un episodio de podcast. SwiftUI rastrea los cambios en un ObservableObject utilizando el contenedor @Published , y tan pronto como la propiedad marcada como @Published cambios, SwiftUI reconstruye todas las SwiftUI asociadas con este PodcastPlayer . Aquí usamos el contenedor @ObservedObject para vincular nuestro EpisodesView a la clase 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


En lugar de pasar un ObservableObject través del método init de nuestra View , podemos incorporarlo implícitamente en el Environment nuestra jerarquía de View . Al hacer esto, hacemos posible que todas las vistas secundarias del Environment actual accedan a este 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) } } } } 

Como puede ver, debemos pasar el PodcastPlayer través del modificador PodcastPlayer de nuestra View . Al hacer esto, podemos acceder fácilmente al PodcastPlayer definiéndolo usando el contenedor @EnvironmentObject . @EnvironmentObject utiliza la función de búsqueda dinámica de miembros para encontrar una instancia de la clase PodcastPlayer en Environment , por lo que no necesita pasarla por el método de inicio EpisodesView . El entorno es la forma correcta de inyectar dependencias en SwiftUI .

@Environment


Como dijimos en el capítulo anterior, podemos transferir objetos personalizados a la jerarquía de View del Environment dentro de SwiftUI . Pero SwiftUI ya tiene un Environment lleno de configuraciones de todo el sistema. Podemos acceder fácilmente a ellos utilizando el contenedor @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) } } 

Al marcar nuestras propiedades con el contenedor @Environment , obtenemos acceso y nos suscribimos a los cambios en la configuración de todo el sistema. Tan pronto como cambian los sistemas Locale , Calendar o ColorScheme , SwiftUI recrea nuestro CalendarView .

Conclusión


Hoy hablamos sobre los Envoltorios de propiedades proporcionados por SwiftUI . @State , @Binding , @EnvironmentObject y @ObservedObject juegan un papel muy importante en el desarrollo de SwiftUI . Gracias por su atencion!

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


All Articles