在 @ViewBuilder 闭包中更改 @State

Posted

技术标签:

【中文标题】在 @ViewBuilder 闭包中更改 @State【英文标题】:Change @State in a @ViewBuilder closure 【发布时间】:2019-08-06 10:37:42 【问题描述】:

我试图了解如何在 @ViewBuilder 闭包中更改 @State 变量。下面只是一个简单的例子:

struct ContentView: View 
    @State var width = CGFloat(0)

    var body: some View  //Error 1
        GeometryReader  geometry in //Error 2
            self.width = geometry.size.width
            return Text("Hello world!")
        
    

我遇到了一些错误:

错误 1:

函数声明了一个不透明的返回类型,但没有返回语句 在它的主体中推断底层类型

但是return 是多余的,因为View 计算属性内只有一行代码。

错误 2:

无法将“GeometryReader<_>”类型的返回表达式转换为返回 输入“一些视图”

既然我明确写了return Text("..."),类型不应该清楚吗?

这里有什么问题?

【问题讨论】:

将您的GeometryReader 嵌入到VStack 中,同时删除您的return 语句。这将消除与View 相关的错误,但我不确定如何处理您将在self.width = geometry.size.width 中遇到的错误。我认为这是根本问题。 SwiftUI 中的编译器错误并不总是向您显示错误的根本原因。 我认为这里不需要 VStack。视图中只有一个元素,它是 GeometryReader。所以应该没问题。相反,return 语句是必要的,因为 Swift 无法在多行闭包中推断返回类型。 我知道,但是发生的情况是编译器给你一个与你的潜在问题无关的错误,所以通过使用 VStack,你只是在消除编译器错误的最直接原因为了找出根本问题是什么。在这种情况下,问题在于赋值语句——在 ViewBuilder 闭包中很可能不允许使用它。你甚至不能在 ViewBuilder 闭包中使用if let 我明白你的意思。我同意你的观点,编译器给我的错误可能与真正的问题无关。另一方面,对我来说,我无法在这些闭包中引用我的实例变量似乎很奇怪。或者至少我们应该能够找到一些关于此的文档。 【参考方案1】:

首先,您不能在 functionBuilder 中做出任意的 swift 语句。允许使用特定的语法。在 SwiftUI 的例子中,它是一个 ViewBuilder,它在底层组成了你的类型。在 SwiftUI 中进行连续语句时,实际上是在依赖编译器根据该 DSL 的规则在下面组合一个新类型。

第二,SwiftUI 是一个秘诀,它不是一个事件系统。您body 函数中更改状态变量,您设置当状态变量在外部发生变化时事物应如何反应。如果您希望另一个视图以某种方式对该宽度做出反应,您需要根据您想要的组件的宽度定义该内容。在不知道您的最终目标是什么的情况下,很难回答如何将内容相互关联。

编辑:

我被要求详细说明什么是允许的。每个functionBuilder 都有不同的允许语法,在functionBuilder 本身中定义。这对 Swift 5.1 中的函数构建器有一个很好的概述:https://www.swiftbysundell.com/posts/the-swift-51-features-that-power-swiftuis-api

至于 SwiftUI 专门寻找什么,它本质上是在寻找每个语句以返回一个 View 的实例。

// works fine!
VStack 
  Text("Does this")
  Text("Work?")


// doesn't work!
VStack 
  Text("Updating work status...")
  self.workStatus = .ok // this doesn't return an instance of `View`!


// roundabout, but ok...
VStack 
  Text("Being clever")
  gimmeView()


// fine to execute arbitrary code now, as long as we return a `View`
private func gimmeView() -> some View 
    self.workingStatus = .roundabout 
    return Text("Yes, but it does work.")

这就是你得到钝错误的原因:

无法将“GeometryReader<_>”类型的返回表达式转换为“某些视图”类型的返回

当你执行时,类型系统不能从View 和本质上的Void 中构造任何View

self.width = geometry.size.width

当您连续执行View 语句时,在下面,它仍然被转换为View 的新类型:

// the result of this is TupleView<Text, Text>
Text("left")
Text("right")

【讨论】:

感谢您的回答。您说“[...] 允许使用特定的语法。[...]”,这正是我正在寻找的。问题中的示例只是一个愚蠢的示例,但我真的很想了解我们可以在这些闭包中编码什么以及我们不能编码什么。答案甚至可能是“只不过是构建最终类型所需的相同类型的表达式列表”,这对我来说非常好。但我正在努力寻找这些理论(非常重要的)信息。如果您可以链接我一些文档/博客文章/GitHub 自述文件/任何内容,那就太好了。谢谢。 我编辑了我的答案并提供了更多指导。 谢谢。您的回答帮助我找到了另一个有用的链接 blog.vihan.org/swift-function-builders 和 Apple 的这个 GitHub 页面 github.com/apple/swift-evolution/blob/…

以上是关于在 @ViewBuilder 闭包中更改 @State的主要内容,如果未能解决你的问题,请参考以下文章

@ViewBuilder 在使用其他属性初始化时抛出错误

有啥方法可以在 SwiftUI 中使用 @ViewBuilder 创建/提取视图数组

在 SwiftUI 视图中展开可选

如何减少我的 SwiftUI ViewBuilder 布局?

SwiftUI 如果让内部视图

如何使 UIViewRepresentable @ViewBuilder 与动态内容一起使用?