Noções básicas sobre wrappers de propriedades no SwiftUI

A tradução do artigo foi preparada especificamente para os alunos do curso “iOS Developer. Curso Avançado v 2.0. ”




Na semana passada, começamos uma nova série de posts sobre o framework SwiftUI. Hoje, quero continuar esse tópico falando sobre os Wrappers de propriedades no SwiftUI. O SwiftUI fornece invólucros para @State , @Binding , @ObservedObject , @EnvironmentObject e @Environment . Então, vamos tentar entender a diferença entre eles e quando, por que e qual devemos usar.

Wrappers de propriedades


Invólucros de propriedades (a seguir denominados "invólucros de propriedades") são descritos em SE-0258 . A idéia principal é agrupar as propriedades com a lógica, que pode ser extraída em uma estrutura separada para reutilização na base de código.

Estado


@State é um invólucro que podemos usar para indicar o estado de uma View . O SwiftUI o armazenará em uma memória interna especial fora da estrutura do View . Somente uma View vinculada pode acessá-la. À medida que o valor da propriedade @State muda, o SwiftUI reconstrói a View para considerar as alterações de estado. Aqui está um exemplo simples.

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

No exemplo acima, temos uma tela simples com um botão e uma lista de produtos. Assim que clicamos no botão, ele altera o valor da propriedade state e o SwiftUI reconstrói a View .

@Binding


@Binding fornece acesso de referência para o tipo de valor. Às vezes, precisamos tornar o estado de nossa View acessível a seus filhos. Mas não podemos simplesmente pegar e passar esse valor, porque é um tipo de valor, e Swift passará uma cópia desse valor. É aqui que o empacotamento da propriedade @Binding vem para o resgate.

 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 a propriedade showFavorited dentro do FilterView . Também usamos o caractere $ especial para passar o link de âncora, porque sem $ Swift ele passará uma cópia do valor em vez de passar o próprio link de âncora. FilterView pode ler e gravar o valor da propriedade showFavorited em um ProductsView , mas não pode rastrear alterações usando essa ligação. Assim que o FilterView altera o valor da propriedade showFavorited, o SwiftUI recria o ProductsView e o FilterView como filho.

@ObservedObject


@ObservedObject funciona de maneira semelhante ao @State , mas a principal diferença é que podemos dividi-lo entre várias View independentes que podem se inscrever e observar as mudanças nesse objeto. Assim que as alterações aparecem, o SwiftUI reconstrói todas as views associadas a esse objeto. . Vejamos um exemplo.

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

Aqui temos a classe PodcastPlayer , que as telas de nosso aplicativo compartilham entre si. Cada tela deve exibir um botão de pausa flutuante quando o aplicativo estiver reproduzindo um episódio de podcast. SwiftUI rastreia alterações em um ObservableObject usando o wrapper @Published e, assim que a propriedade marcada como @Published muda, o SwiftUI reconstrói todas as SwiftUI associadas a este PodcastPlayer . Aqui usamos o wrapper @ObservedObject para vincular nossos EpisodesView à 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


Em vez de passar um ObservableObject pelo método init da nossa View , podemos implicitamente incorporá-lo no Environment hierarquia da View . Ao fazer isso, possibilitamos que todas as visualizações filho do Environment atual acessem esse 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 você pode ver, precisamos passar o PodcastPlayer pelo modificador environmentObject da nossa View . Ao fazer isso, podemos acessar facilmente o PodcastPlayer , definindo-o usando o wrapper @EnvironmentObject . @EnvironmentObject usa a função de pesquisa dinâmica de membros para encontrar uma instância da classe PodcastPlayer em Environment , para que você não precise passar pelo método init do EpisodesView . O ambiente é a maneira correta de injetar dependências no SwiftUI .

@Environment


Como dissemos no capítulo anterior, podemos transferir objetos personalizados para a hierarquia do Environment View dentro do SwiftUI . Mas o SwiftUI já possui um Environment preenchido com as configurações do sistema. Podemos acessá-los facilmente usando o 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) } } 

@Environment nossas propriedades com o wrapper @Environment , obtemos acesso e @Environment alterações nas configurações do sistema. Assim que os sistemas Locale , Calendar ou ColorScheme mudam, o SwiftUI recria nosso CalendarView .

Conclusão


Hoje falamos sobre os Wrappers de propriedades fornecidos pelo SwiftUI . @State , @Binding , @EnvironmentObject e @ObservedObject desempenham um papel enorme no desenvolvimento do SwiftUI . Obrigado pela atenção!

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


All Articles