是否可以在使用环境的结构之外提取依赖于环境的逻辑



是否可以提取依赖于视图之外的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)
}
}
}

我写了这篇关于使用环境为视图层次结构提供值的文章,您可能会发现它很有用。

最新更新