原文地址:https://mecid.github.io/2019/06/12/understanding-property-wrappers-in-swiftui/
@States
通過使用 @State 修飾器我們可以關(guān)聯(lián)出 View 的狀態(tài). SwiftUI 將會把使用過 @State 修飾器的屬性存儲到一個特殊的內(nèi)存區(qū)域捉片,并且這個區(qū)域和 View struct 是隔離的. 當(dāng) @State 裝飾過的屬性發(fā)生了變化甘萧,SwiftUI 會根據(jù)新的屬性值重新創(chuàng)建視圖
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)
}
}
}
}
}
這個例子里我們創(chuàng)建了一個列表,點擊按鈕 showFavorited 會發(fā)生值的取反操作糠爬,然后 SwiftUI 會通過最新的值更新值
譯者:這個 demo 在最新的 xcode 11 beta 6
中已經(jīng)無法運行起來了界弧,因為 Button 組件的語法已經(jīng)修改了
@Binding
有時候我們會把一個視圖的屬性傳至子節(jié)點中气嫁,但是又不能直接的傳遞給子節(jié)點料睛,因為在 Swift 中值的傳遞形式是值類型
傳遞方式府蔗,也就是傳遞給子節(jié)點的是一個拷貝過的值灾票。但是通過 @Binding 修飾器修飾后峡谊,屬性變成了一個引用類型
,傳遞變成了引用傳遞刊苍,這樣父子視圖的狀態(tài)就能關(guān)聯(lián)起來了既们。
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)
}
}
}
}
}
我們在 FilterView 視圖里用 @Binding 修飾 showFavorited 屬性, 在傳遞屬性是使用 $ 來傳遞 showFavorited 屬性的引用,這樣 FilterView 視圖就能讀寫父視圖 ProductsView 里的狀態(tài)值了正什,并且值發(fā)生了修改 SwiftUI 會更新 ProductsView 和 FilterView 視圖
譯者:在 FilterView 視圖里啥纸,Toggle 組件的創(chuàng)建也使用 showFavorited斯棒, 直接 Text(showFavorited) 使用就好了
@ObservedObject
@ObservedObject 的用處和 @State 非常相似,從名字看來它是來修飾一個對象的莹妒,這個對象可以給多個獨立的 View 使用名船。如果你用 @ObservedObject 來修飾一個對象,那么那個對象必須要實現(xiàn) ObservableObject 協(xié)議旨怠,然后用 @Published 修飾對象里屬性渠驼,表示這個屬性是需要被 SwiftUI 監(jiān)聽的
final class PodcastPlayer: ObservableObject {
@Published private(set) var isPlaying: Bool = false
func play() {
isPlaying = true
}
func pause() {
isPlaying = false
}
}
我們定義了一個 PodcastPlayer 類,這個類可以給不同的 View 使用鉴腻,SwiftUI 會追蹤使用 View 里經(jīng)過 @ObservableObject 修飾過的對象里進過 @Published 修飾的屬性變換迷扇,一旦發(fā)生了變換,SwiftUI 會更新相關(guān)聯(lián)的 UI
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)
}
}
}
}
譯者:這個 demo 在最新的 xcode 11 beta 6
中已經(jīng)無法運行起來了爽哎,因為 Button 組件的語法已經(jīng)修改了
@EnvironmentObject
從名字上可以看出蜓席,這個修飾器是針對全局環(huán)境的。通過它课锌,我們可以避免在初始 View 時創(chuàng)建 ObservableObject, 而是從環(huán)境中獲取 ObservableObject
SceneDelegate.swift 文件
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()
}
}
EpisodesView.swift 文件
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)
}
}
}
}
可以看出我們獲取 PodcastPlayer 這個 ObservableObject 是通過 @EnvironmentObject 修飾器厨内,但是在入口需要傳入 .environmentObject(player)
。@EnvironmentObject 的工作方式是在 Environment 查找 PodcastPlayer 實例渺贤。
@Environment
繼續(xù)上面一段的說明雏胃,我們的確開一個從 Environment 拿到用戶自定義的 object,但是 SwiftUI 本身就有很多系統(tǒng)級別的設(shè)定志鞍,我們開一個通過 @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 修飾的屬性瞭亮,我們開一個監(jiān)聽系統(tǒng)級別信息的變換,這個例子里一旦 Calendar, Locale, ColorScheme 發(fā)生了變換固棚,我們定義的 CalendarView 就會刷新
謝謝