属性包装器@Environment、@EnvironmentObject、@ObservedObject和@State、@Binding

3周前 40次点击 来自 SwiftUI

原文链接:
Understanding Property Wrappers in SwiftUI

@State、@Binding、@EnvironmentObject 和 @ObjectBinding 在 SwiftUI 开发中必不可少,如果你能不使用属性包装器而开发出一个完整的App,请收下我的膝盖,当我刚开始学习SwiftUI的时候还不是很清楚该在什么场景下使用不同的属性包装器,阅读完上面的文章后脑子就清晰了。以下是一点小小的总结而已,建议阅读原文:

1.@Environment

@Environment 属性包装器可以访问到SwiftUI提供的系统级全局环境变量,例如在App的开发中,如果你使用的是原生控件,那么控件已经完全适配好了深色模式,你无需再做其他工作;但是总有些情况下,例如使用到了UIKit,那么如何根据系统的设置适配深色模式呢?

这里就需要引入一个环境变量 @Environment(.colorScheme)

//colorScheme 的值有两个分别是.dark和.light
@Environment(\.colorScheme) var colorScheme

再如做多语种适配,访问当前语言环境

@Environment(\.locale) var locale: Locale

2.@EnvironmentObject

@EnvironmentObject 是SwiftUI提供的在App级用户自定义全局变量,一般做法是在App启动之前就注入视图树的环境:

struct RootObject: ObservableObject{
    @Published var noMeaningCount: Int = 0
}

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)

        window.rootViewController = UIHostingController(
            rootView: ContentView().environmentObject(RootObject())
        )
        self.window = window
        window.makeKeyAndVisible()
    }
}

struct ContentView: View {
    //通过声明一个 @EnvironmentObject 属性包装器来直接访问 RootObject,无需再次初始化,@EnvironmentObject 使用动态成员查找环境中的 RootObject 类实例,在 SwiftUI 中,通过环境实现了依赖注入,😁Amazing~~~
    @EnvironmentObject var ro: RootObject

    var body: some View {
        Text("\(ro.noMeaningCount)")
    }
}

3.@ObservedObject

@ObservedObject 你可以理解为,再一次将它的使用范围从App级别缩小为多个特定视图。例如:

import Combine

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

    func play() {
        isPlaying = true
    }

    func pause() {
        isPlaying = false
    }
}

PodcastPlayer在我们的应用程序的多个屏幕之间共享。每个屏幕都必须显示浮动暂停按钮的情况下,当应用程序播放播客节目。在 @Published 属性包装器的帮助下,SwiftUI会跟踪 ObservableObject 上的变化,一旦一个属性被标记为 @Published , SwiftUI就会重新构建绑定到PodcastPlayer对象的所有视图。这里我们使用 @ObservedObject 属性包装器来绑定我们的EpisodesView到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)
            }
        }
    }
}

4.@State和@Binding

@State@Binding 放在一起讲,按照套路,它们的适用范围进一步缩小了。它们的适用范围为单个视图和它的子视图。

@State是一个属性包装器,可以用来描述视图的状态。SwiftUI会将它存储在视图结构之外的特殊内存中。只有相关的视图可以访问它。一旦**@State** 属性的值发生变化,SwiftUI就会重新构建视图以尊重状态的变化。

@Binding为值类型提供了类似访问的引用。有时,我们需要让视图的子视图可以访问视图的状态。但我们不能简单地传递那个值因为它是一个值类型Swift会传递那个值的副本。在这里我们可以使用 @Binding 属性包装器。

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

struct FilterView: View {
    @Binding var showFavorited: Bool

    var body: some View {
        Toggle(isOn: $showFavorited) {
            Text("Change filter")
        }
    }
}

我们使用 @Binding 来标记FilterView中的 showFavorited 属性。我们使用了 $ 来传递绑定引用。FilterView可以读写ProductsView的showFavorited属性的值。一旦FilterView改变showFavorited属性的值,SwiftUI将重新创建ProductsView和FilterView作为它的子视图。

注意:
@State 可以在视图中单独使用,但是 @Binding 一定需要一个 @State 与它配对使用;那么,在app开发中就需要注意一点,在一个视图有很多个子视图的情况下使用同一个属性包装器 @State - @Binding 可能导致开发变得复杂,这种情况下,我习惯将这个属性包装进 @EnvironmentObject ,简单粗暴,一了百了。

Card image cap
开发者雷

尘世间一个小小的开发者

技术文档 >> 系列应用 >>
热推应用

EntryS

学习Swift的入门教程
标签