在 returns 某些视图 (SwiftUI) 的闭包内声明一个临时变量或常量

Declare a temporary variable or constant inside a closure that returns some View (SwiftUI)

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

Unable to infer complex closure return type; add explicit type to disambiguate

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:) 方法的调用,构建就会成功。显然,上面的例子是我对该问题的简化再现。在我的实际应用程序中,我试图存储该方法的 return 值,因为在为单个项目创建视图时它被多次使用,并且该操作需要在我的模型中进行可能昂贵的评估。多次调用该方法很浪费。

几个注意事项:

如何编写此代码,以便我不必多次调用可能昂贵的方法?有人可以解释 language/syntax 级别发生了什么导致错误吗?

不幸的是,这对 Swift 来说太复杂了,但有几个解决方案:

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

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

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

  1. 使用Group:
var body: some View {
        MyListView(items: cards) { card in
            Group {
                let label = label(for: card)
                Text(label)
            }
        }
    }

  1. 将 func 与 @ViewBuilder 标签一起使用

@ViewBuilder func cardView(card: Card) -> some View {
        let label = label(for: card)
        Text(label)
    }
    
    var body: some View {
        MyListView(items: cards, content: cardView)
    }
    

此外,您可以简化第二个示例而不使用 ViewBuilder,因为您可以手动说出您将 return 文本,例如:

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

这是由于 Swift 编译器仅尝试推断闭包的 return 类型(如果它是单个表达式)的限制。由结果生成器处理的闭包,例如 @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。如果没有他们的意见和有用的答案,我永远不会偶然发现这个发现。