我正在尝试访问我的自定义环境值,而不在
@Environment
中声明 View
属性。那么为什么我需要它?
让我们看一下例子。我有我的自定义环境值:
enum Theme {
case black
case white
func color() -> Color {
// ... Get color functional
}
}
struct ThemeKey: EnvironmentKey {
static let defaultValue: Theme = .black
}
extension EnvironmentValues {
var theme: Theme {
get { self[ThemeKey.self] }
set { self[ThemeKey.self] = newValue }
}
}
要使用它,我必须在我的
@Environment
中写入 View
属性,如下所示:
struct AppView: View {
var body: some View {
ContentView()
.environment(\.theme, .white)
}
}
}
struct ContentView: View {
@Environment (\.theme) private var theme
var body: some View {
Rectangle()
.fill(theme.color())
}
}
那么如果我想在我的所有视图中访问
theme
环境值怎么办?例如,我可以编写 View
的扩展,它会像这样得到 theme
:
extension View {
var theme: Theme {
return Environment(\.theme).wrappedValue
}
}
struct ContentView: View {
var body: some View {
Rectangle()
.fill(theme.color())
}
}
但它不起作用,Xcode 显示错误
Accessing Environment<Theme>'s value outside of being installed on a View. This will always read the default value and will not update.
所以我的问题是我是否必须在视图中添加
@Environment
属性才能访问 theme
值,或者我可以通过其他方式来实现,例如不声明 @Environment
属性?
您遇到的错误消息源于对 SwiftUI 环境系统设计工作方式的根本误解。
@Environment
属性包装器专门设计用于将环境值注入到 SwiftUI 视图中。此机制可确保您的视图在环境值发生变化时自动更新,遵循 SwiftUI 的响应式设计原则。尝试在不使用 @Environment
(或其他适当的机制)的情况下直接访问环境值会绕过此系统,从而导致您所观察到的问题。
当您尝试在
View
上的扩展中创建计算属性以直接访问环境值时,您将绕过环境的预期使用模式。 SwiftUI 的环境不仅仅是一个可以随时访问值的简单字典;它还包括一个简单的字典。它是一个托管系统,可沿视图层次结构传播值,确保有效处理更新。
但是,如果您的目标是减少样板代码并使主题值在视图中轻松访问,而无需每次都显式声明
@Environment
属性,那么您有几种选择:
您可以创建一个小助手
View
,将环境值注入到其子视图中。这种方法允许您抽象出环境访问:
struct WithTheme<Content: View>: View {
@Environment(\.theme) var theme
let content: (Theme) -> Content
init(@ViewBuilder content: @escaping (Theme) -> Content) {
self.content = content
}
var body: some View {
content(theme)
}
}
// Usage
struct ContentView: View {
var body: some View {
WithTheme { theme in
Rectangle()
.fill(theme.color())
}
}
}
另一种方法是创建一个自定义视图修饰符,将主题注入视图中。这更符合SwiftUI的设计理念,可以让你灵活地使用环境值:
struct ThemeModifier: ViewModifier {
@Environment(\.theme) var theme
func body(content: Content) -> some View {
content
.fill(theme.color()) // Assuming the content can be filled with a color
}
}
extension View {
func withTheme() -> some View {
modifier(ThemeModifier())
}
}
// Usage
struct ContentView: View {
var body: some View {
Rectangle()
.withTheme()
}
}
在这两种方法中,您都通过利用
@Environment
属性包装器来访问环境值来遵循 SwiftUI 的预期设计。这可确保您的视图保持响应状态,并在环境值发生变化时自动更新。
SwiftUI 不支持直接访问这些模式之外的环境,并且正如您所发现的,这会导致错误或意外行为。关键是在框架的指导方针内工作以实现所需的功能。