[英]How to use @State and @Environment properties in a struct that can produce a view but isn't a view?
I'm trying to create a view style for customizing a custom SwiftUI view that I'm developing.我正在尝试创建一个视图样式来自定义我正在开发的自定义 SwiftUI 视图。 I declared a protocol with a makeBody
function, I'm storing the style as a @State
property so it can be updated, and I'm calling makeBody
from inside a view.我用makeBody
function 声明了一个协议,我将样式存储为@State
属性以便它可以更新,并且我从视图内部调用makeBody
。 This technique closely follows what SwiftUI already does but there's one thing that I can't figure out.该技术与SwiftUI 已经采用的技术非常相似,但有一件事我无法弄清楚。
There's one important behavior that the first-party view styles have that I can't replicate — that's using @State
, @Environment
, or any other SwiftUI property wrapper inside the custom view style.第一方视图 styles 有一个我无法复制的重要行为,即在自定义视图样式中使用@State
、 @Environment
或任何其他 SwiftUI 属性包装器。
For example, I can implement the following custom ButtonStyle
that just displays the value of the current color scheme as the button's content.例如,我可以实现以下自定义ButtonStyle
,它只显示当前配色方案的值作为按钮的内容。
struct MyButtonStyle: ButtonStyle {
@Environment(\.colorScheme) var colorScheme
func makeBody(configuration: Configuration) -> some View {
Text(String(describing: colorScheme))
}
}
struct MyView: View {
var body: some View {
Button("foo", action: {}).buttonStyle(MyButtonStyle())
}
}
When I preview MyView
with a .dark
preferred color scheme, the colorScheme
property of MyButtonStyle
is updated, the makeBody
function is called again, and the text reads “dark”.当我使用.dark
首选配色方案预览MyView
时,更新了MyButtonStyle
的colorScheme
属性,再次调用makeBody
function,文本显示为“dark”。 This is all expected.这都是意料之中的。
Now, let's take a look at the custom view style that I created.现在,让我们看一下我创建的自定义视图样式。
struct MyCustomStyle /* : CustomStyle */ {
@Environment(\.colorScheme) var colorScheme
@ViewBuilder func makeBody(configuration: CustomStyleConfiguration) -> some View {
Text(String(describing: colorScheme))
}
}
struct MyView: View {
@State var style = MyCustomStyle()
var body: some View {
style.makeBody(configuration: .init())
}
}
In the above scenario, when I preview MyView
with a .dark
preferred color scheme, the colorScheme
property is not updated, and the text reads “light” even though the label's foreground color has correctly picked up the change.在上述情况下,当我使用.dark
首选配色方案预览MyView
时, colorScheme
属性未更新,并且文本显示为“light”,即使标签的前景色已正确拾取更改。
How to make @State
and @Environment
properties work in the custom view style, just as they work in ButtonStyle
and in views, without introducing any boilerplate to the implementation of makeBody
function?如何使@State
和@Environment
属性在自定义视图样式中工作,就像它们在ButtonStyle
和视图中工作一样,而不向makeBody
function 的实现引入任何样板?
I'm certain that it's possible because all the first-party view styles already support this, without styles conforming to View
, and without them implementing the private _makeView
API that manually adds dependencies to SwiftUI's graph (at least according to the module interface of SwiftUI
).我确信这是可能的,因为所有第一方视图 styles 已经支持这个,没有 styles 符合View
,并且没有他们实现私有_makeView
API 手动添加依赖关系到 SwiftUI 的图形(至少根据模块接口SwiftUI
).
I tried initializing the view style inside a computable body
of a view that wraps MyView
.我尝试在包装MyView
的视图的可计算body
内初始化视图样式。 That doesn't work either.那也不管用。
struct ContentView: View {
var body: some View {
MyView(style: MyCustomStyle())
}
}
Wrapping the return value of the custom makeBody
function in a separate view, with colorScheme
property being part of that view, is a known workaround that results in correct behavior.将自定义makeBody
function 的返回值包装在单独的视图中, colorScheme
属性是该视图的一部分,这是一种已知的解决方法,可以产生正确的行为。 Answers suggesting this as a workaround will not be accepted because this question is all about avoiding that .建议将此作为解决方法的答案将不会被接受,因为这个问题都是为了避免这种情况。
Answers saying that it is impossible to use @Environment
or @State
outside of a type conforming to View
will not be accepted (because it is possible, see ButtonStyle
), unless they can prove that private API is what drives this.回答说不可能在符合View
的类型之外使用@Environment
或@State
将不会被接受(因为这是可能的,请参阅ButtonStyle
),除非他们可以证明private API 是驱动它的原因。
We don't have access to whatever the EnvironmentKey
is that corresponds with ColorScheme
, but its defaultValue
must be light
.我们无权访问与ColorScheme
对应的任何EnvironmentKey
,但它的defaultValue
必须是light
。 That's why you can ever get a value at all, outside of a view.这就是为什么您可以在视图之外获得任何值的原因。
Your type won't ever actually be supplied with EnvironmentValues
unless it's a View
, ViewModifier
, ButtonStyle
, …Apple doesn't actually offer an exhaustive list of protocols that are considered "part of the view hierarchy".你的类型实际上永远不会提供EnvironmentValues
除非它是View
, ViewModifier
, ButtonStyle
,......Apple实际上并没有提供被认为是“视图层次结构的一部分”的协议的详尽列表。
What you're trying to do, which is unfortunately currently impossible, is to implement your own Style
.不幸的是,您正在尝试做的是实现您自己的Style
,这目前是不可能的。 There is no protocol that all of the built-in Style
s use, requiring an implementation of没有所有内置Style
使用的协议,需要实现
makeBody(configuration: Configuration)
For example, the ButtonStyle
documentation tells us,例如, ButtonStyle
文档告诉我们,
The system calls this method for each Button instance in a view hierarchy where this style is the current button style.系统为视图层次结构中的每个 Button 实例调用此方法,其中此样式是当前按钮样式。
All of the following fit the same profile.以下所有内容都符合相同的配置文件。 You can implement one of these styles, but you can't make a type that conforms to some parent Style
protocol.您可以实现这些 styles 之一,但您不能创建符合某些父Style
协议的类型。
If you don't conform to one of the "view hierarchy" protocols, then relying not on the environment, but rather, dependency injection, is necessary.如果您不遵守其中一种“视图层次结构”协议,则不依赖于环境,而是依赖注入是必要的。 Eg例如
struct MyCustomStyle {
init(environment: EnvironmentValues) {
colorScheme = environment.colorScheme
}
private let colorScheme: ColorScheme
@ViewBuilder func makeBody() -> some View {
Text(String(describing: colorScheme))
}
}
struct ContentView: View {
@Environment(\.self) private var environment
var style: MyCustomStyle { .init(environment: environment) }
var body: some View {
style.makeBody()
}
}
You have to give up on what you actually want to do unless you go work on SwiftUI at Apple.你必须放弃你真正想做的事情,除非你 go 在 Apple 工作 SwiftUI。 However, you should evaluate if you can get away with a targeted functionality subset.但是,您应该评估是否可以摆脱目标功能子集。 Eg例如
struct MyCustomStyle: ViewModifier {
struct CustomStyleConfiguration { }
@Environment(\.colorScheme) var colorScheme
init(configuration: CustomStyleConfiguration = .init()) {
self.configuration = configuration
}
func body(content: Content) -> some View {
Text(String(describing: colorScheme))
}
private let configuration: CustomStyleConfiguration
}
struct MyView: View {
var body: some View {
EmptyView()
}
}
extension MyView {
func style(_ style: MyCustomStyle = .init()) -> some View {
modifier(style)
}
}
MyView().style()
Your question also mentions State
, but you didn't supply an example for it.您的问题还提到State
,但您没有提供示例。 You probably just need to conform to DynamicProperty
for whatever you're doing with State
.无论您使用State
DynamicProperty
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.