在返回一些View(SwiftUI)的闭包中声明一个临时变量或常量



我正在构建一个基于SwiftUI的视图,我想在返回some View的闭包中存储一个临时值(这样它就可以多次使用(。编译器给了我以下错误:

无法推断复杂闭包返回类型;添加显式类型以消除的歧义

struct GameView: View {
@State private var cards = [
Card(value: 100),
Card(value: 20),
Card(value: 80),
]

var body: some View {
MyListView(items: cards) { card in // Error is on this line, at the starting curly brace
let label = label(for: card)
return Text(label)
}
}

func label(for card: Card) -> String {
return "Card with value (card.value)"
}
}
struct MyListView<Item: Identifiable, ItemView: View>: View {
let items: [Item]
let content: (Item) -> ItemView

var body: some View {
List {
ForEach(items) { item in
content(item)
}
}
}
}
struct Card: Identifiable {
let value: Int
let id = UUID()
}

如果我内联对label(for:)方法的调用,则构建成功。显然,上面的例子是我对这个问题的简化复制。在我的实际应用程序中,我试图存储该方法的返回值,因为在为单个项目创建视图时,它会被多次使用,并且该操作需要在我的模型中进行潜在的昂贵评估。多次调用该方法是浪费。

注意事项:

  • 传递给MyListView的这个content闭包是而不是@ViewBuilder,但即使是,我也认为使用let应该没问题
  • 当闭包包含单个表达式时,我不依赖于隐式return——我添加了自己的显式return
  • 我是否使用声明的变量并不重要。只要出现该语句,编译器就会感到不安

我如何写这篇文章,这样我就不必多次调用这个潜在的昂贵方法了?有人能解释在语言/语法层面上发生了什么导致错误吗?

不幸的是,Swift无法理解这一点,但有几种解决方案:

首先,您可以手动声明它是什么功能:

MyListView(items: cards) { (card: Card) -> Text in 
let label = label(for: card)
return Text(label)
}

或者,您需要使用@ViewBuilder的强大功能才能使其正常工作。因此,我有两个同等质量的工作建议

  1. 使用Group
var body: some View {
MyListView(items: cards) { card in
Group {
let label = label(for: card)
Text(label)
}
}
}
  1. 使用带有@ViewBuilder标记的func

@ViewBuilder func cardView(card: Card) -> some View {
let label = label(for: card)
Text(label)
}

var body: some View {
MyListView(items: cards, content: cardView)
}

此外,您可以简化第二个示例,而不使用ViewBuilder,因为您可以手动说您将返回Text,例如:

func cardView(card: Card) -> Text {
let label = label(for: card)
return Text(label)
}

这是由于Swift编译器仅在闭包是单个表达式时尝试推断其返回类型的限制。由结果生成器(如@ViewBuilder(处理的闭包不受此限制重要的是,这个限制也不会影响函数(只有闭包(

我能够通过将闭包移动到结构内部的方法来完成这项工作。注意:这与@cluelessCoder的第二个解决方案相同,只是排除了@ViewBuilder属性。

struct GameView: View {
@State private var cards = [
Card(value: 100),
Card(value: 20),
Card(value: 80),
]

var body: some View {
MyListView(items: cards, content: cardView)
}

func cardView(for card: Card) -> some View {
let label = label(for: card) // only called once, and can be reused.
return Text(label)
}

func label(for card: Card) -> String {
return "Card with value (card.value)"
}
}

感谢@cluelessCoder。如果没有他们的投入和有用的答案,我绝不会偶然发现这一发现。

最新更新