是否可以提取依赖于视图之外的SwiftUI环境的逻辑?
例如,考虑一个场景,其中有一个Theme
结构,它根据环境中的属性计算颜色,类似于下面的示例代码。
我想做的是提取出计算颜色的逻辑,这样它就可以在多个地方使用。理想情况下,我希望在Theme
结构中使用@Environment
,这样我只需要在一个地方检索它的值-另一种选择是,我从主题计算的调用站点的环境中检索并将值注入。这种选择很好,但我希望避免在所有地方检索环境值。
/// A structure to encapsulate common logic, but the logic depends on values in the environment.
struct Theme {
@Environment(.isEnabled) var isEnabled: Bool
var color: Color {
isEnabled ? .blue : .gray
}
}
/// One of many views that require the logic above
struct MyView: View {
let theme: Theme
var body: some View {
theme.color
}
}
/// A little app to simulate the environment values changing
struct MyApp: App {
@State var disabled: Bool
var body: some Scene {
WindowGroup {
VStack {
Toggle("Disabled", isOn: $disabled)
MyView(theme: Theme())
.disabled(disabled)
}
}
}
}
上面的示例代码不起作用,即如果您在应用程序中切换开关,视图的颜色不会改变。这个示例代码只是用来展示我理想情况下希望它如何工作,特别是因为它不需要我在整个MyView
和类似的视图中丢弃@Environment
,只为了检索值并将其传递到共享函数中。
我认为可能导致问题的一件事是,Theme
是在环境变化的范围之外创建的,但如果我在MyView
内部构造Theme
,行为不会改变。
我在这里的困惑表明我在理解SwiftUI环境时遗漏了一些基本的东西。我很想了解为什么示例代码不起作用。如果Theme
是一个View
,其body
中有color
逻辑,那么它将进行更新,那么为什么它不在当前设置中引起更新呢?
方法应该不同,在视图中查看零件,在模型中建模零件,"分离和规则";
struct Theme { // << view layer independent !!
func color(for enabled: Bool) -> Color { // << dependency injection !!
enabled ? .blue : .gray
}
}
struct MyView: View {
@Environment(.isEnabled) var isEnabled: Bool
let theme: Theme
var body: some View {
theme.color(for: isEnabled) // << here !!
}
}
我觉得你在混合一些东西,所以让我告诉你我该如何构建它。
首先,我不认为一个主题应该有状态,它应该是一个颜色库。
struct Theme {
var enabledColor: Color = .blue
var disabledColor: Color = .gray
}
您的View
是您应该拥有州的地方
struct MyView: View {
@Environment(.isEnabled) var isEnabled: Bool
var body: some View {
// ??
}
}
我建议您创建自己的EnvironmentKey
,并将您的主题注入到环境中:
struct ThemeKey: EnvironmentKey {
static let defaultValue: Theme = .init()
}
extension EnvironmentValues {
var theme: Theme {
get { self[ThemeKey.self] }
set { self[ThemeKey.self] = newValue }
}
}
现在您的View
可以使用环境来阅读主题。我喜欢保持我的观点轻逻辑,但其中的演示逻辑对我来说是有意义的
struct MyView: View {
@Environment(.theme) var theme
@Environment(.isEnabled) var isEnabled: Bool
var body: some View {
isEnabled ? theme.enabledColor : theme.disabledColor
}
}
你需要在应用程序的某个时刻注入你的主题,你应该将其添加到视图层次结构的顶部附近,以确保所有视图都可以访问它。
struct MyApp: App {
@State var disabled: Bool
@State var theme: Theme = .init()
var body: some Scene {
WindowGroup {
VStack {
Toggle("Disabled", isOn: $disabled)
MyView()
.disabled(disabled)
}
.environment(.theme, theme)
}
}
}
我写了这篇关于使用环境为视图层次结构提供值的文章,您可能会发现它很有用。