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