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!