[譯]理解 SwiftUI 裏的屬性裝飾器@State, @Binding, @ObservedObject, @EnvironmentObject

原文地址:mecid.github.io/2019/06/12/…git

@State

經過使用 @State 修飾器咱們能夠關聯出 View 的狀態. SwiftUI 將會把使用過 @State 修飾器的屬性存儲到一個特殊的內存區域,而且這個區域和 View struct 是隔離的. 當 @State 裝飾過的屬性發生了變化,SwiftUI 會根據新的屬性值從新建立視圖github

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)
                }
            }
        }
    }
}
複製代碼

這個例子裏咱們建立了一個列表,點擊按鈕 showFavorited 會發生值的取反操做,而後 SwiftUI 會經過最新的值更新值swift

譯者:這個 demo 在最新的 xcode 11 beta 6 中已經沒法運行起來了,由於 Button 組件的語法已經修改了xcode

@Binding

有時候咱們會把一個視圖的屬性傳至子節點中,可是又不能直接的傳遞給子節點,由於在 Swift 中值的傳遞形式是值類型傳遞方式,也就是傳遞給子節點的是一個拷貝過的值。可是經過 @Binding 修飾器修飾後,屬性變成了一個引用類型,傳遞變成了引用傳遞,這樣父子視圖的狀態就能關聯起來了。session

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 裏的狀態值了,而且值發生了修改 SwiftUI 會更新 ProductsView 和 FilterView 視圖app

譯者:在 FilterView 視圖裏,Toggle 組件的建立也使用 showFavorited 這種格式,由於 Toggle 組件會修改傳入的值,若是是一個純讀的組件好比 Text 就不須要 使用showFavorited, 直接 Text(showFavorited) 使用就行了ide

@ObservedObject

@ObservedObject 的用處和 @State 很是類似,從名字看來它是來修飾一個對象的,這個對象能夠給多個獨立的 View 使用。若是你用 @ObservedObject 來修飾一個對象,那麼那個對象必需要實現 ObservableObject 協議,而後用 @Published 修飾對象裏屬性,表示這個屬性是須要被 SwiftUI 監聽的ui

final class PodcastPlayer: ObservableObject {
    @Published private(set) var isPlaying: Bool = false

    func play() {
        isPlaying = true
    }

    func pause() {
        isPlaying = false
    }
}
複製代碼

咱們定義了一個 PodcastPlayer 類,這個類能夠給不一樣的 View 使用,SwiftUI 會追蹤使用 View 裏通過 @ObservableObject 修飾過的對象裏進過 @Published 修飾的屬性變換,一旦發生了變換,SwiftUI 會更新相關聯的 UIspa

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 中已經沒法運行起來了,由於 Button 組件的語法已經修改了code

@EnvironmentObject

從名字上能夠看出,這個修飾器是針對全局環境的。經過它,咱們能夠避免在初始 View 時建立 ObservableObject, 而是從環境中獲取 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

繼續上面一段的說明,咱們的確開一個從 Environment 拿到用戶自定義的 object,可是 SwiftUI 自己就有不少系統級別的設定,咱們開一個經過 @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 修飾的屬性,咱們開一個監聽系統級別信息的變換,這個例子裏一旦 Calendar, Locale, ColorScheme 發生了變換,咱們定義的 CalendarView 就會刷新

謝謝

相關文章
相關標籤/搜索