SwiftUI的环境变量详解

3,714 阅读3分钟

SwiftUI中传递环境变量时会发现两个相似的方法:environment<V>(_ keyPath:, _ value:)environmentObject<T>(_ object:),这篇文章我们来看一下这两个方法的不同之处和分别的适用场景。

到公众号【iOS开发栈】学习更多SwiftUI、iOS开发相关内容。

首先要明确的一点是,这两个方法都是用来设置环境变量的,而环境变量会影响到被设置变量的视图以及它的所有子视图,并且这个传递过程不需要明确指出。

也就是说,假设有三个视图,view1、view2、view3,其中view2和view3是view1的子视图,要想在view2和view3中使用view1设置的环境变量的话,只需要直接在view中使用@EnvironmentObject或者@Environment而不需要在创建view2和view3的时候显式传参。

@EnvironmentObject

下面以@EnvironmentObject举例说明上面的这段话:

// 公众号——iOS开发栈
class OfficalAccount: ObservableObject {
    @Published var amount = 3000 // 订阅人数
}

struct DetailView: View {
    @EnvironmentObject var account: OfficalAccount

    var body: some View {
        VStack {
            Button("Increase Score in detailView") {
                account.amount += 1
            }

            Text("account: \(account.amount)")
        }
    }
}

struct DetailView2: View {
    @EnvironmentObject var account: OfficalAccount

    var body: some View {
        Text("Score: \(account.amount)")
    }
}

struct ContentView: View {
    @StateObject var account = OfficalAccount()

    var body: some View {
        VStack {
            Button("Increase Score") {
                account.amount += 1
            }
            
            DetailView()
            DetailView2()
        }
        .environmentObject(account)
    }
}

上面的例子是以我的公众号(”iOS开发栈“)的订阅人数做说明,可以看出ContentView创建DetailViewDetailView2时并没有直接传递变量accout,但是结果是这两个子视图中的数量显示会随着点击按钮而增加。

对上面使用environmentObject进行环境变量传递有几点需要注意:

  • 使用@Environment@StateObject@ObservedObject包装的类必须遵守ObservableObject协议,并且只能是类不能是结构体,否则会报错 {% label danger@Non-class type 'OfficalAccount' cannot conform to class protocol 'ObservableObject' %}
  • @Published也只能在class中使用,如果放在了struct中,会报错{% label danger@'wrappedValue' is unavailable: @Published is only available on properties of classes %}
  • 使用@EnvironmentObject包装的变量是不需要初始化的,编译器会从环境变量中获取这个对象的值
  • 在子视图中可以修改了环境变量的值,并且这个改变会在当前的视图层级中传递

@Environment

而对于@Environment来说,它主要是用来处理一些当前的系统设置的,比如说语言、时区、黑暗模式、CoreData的Context等。

在使用过程中一个很大的不同是,@Environment(_ keyPath:)需要指定一个类型为KeyPath的参数,而这个参数大多数情况下我们都是使用的EnvironmentValues中已经定义好的,比如managedObjectContext/locale等。

下面以使用CoreData为例说明使用方法:

// App.swift
...
let persistenceController = PersistenceController.shared
...
var body: some Scene {
    WindowGroup {
        ContentView()
            .environment(\.managedObjectContext, persistenceController.container.viewContext)
    }
}
...

// ContentView.swift
...
@Environment(\.managedObjectContext) private var viewContext
...
let newItem = Item(context: viewContext)
newItem.platform = 1
...

在这个例子中,我们在App.swift中创建了persistenceController并通过调用.environment把值传递给了ContentViewContentView中使用@Environment(\.managedObjectContext) private var viewContext获取到了这个环境变量。

除了需要一个KeyPath和上面提到的主要用来处理系统自带的环境变量外,@Environment@EnvironmentObject在用法上相差不大。

到公众号【iOS开发栈】学习更多SwiftUI、iOS开发相关内容。

总结

通过灵活使用环境变量,可以让开发过程更加简单,页面之间可以写更少的值传递(正向、反相)相关代码。通过本文对EnvironmentEnvironmentObject的对比,我们更深入的了解了SwiftUI中环境变量的一些使用细节。