Array.forEach 产生错误 "Cannot convert value of type '()' to closure result type '_'"

Array.forEach creates error "Cannot convert value of type '()' to closure result type '_'"

我正在尝试循环遍历 SwuftUI 中的数组以在不同位置呈现多个文本字符串。手动遍历数组是可行的,但使用 forEach 循环会产生错误。 在下面的代码示例中,我注释掉了手动方法(有效)。 这种方法在本教程中适用于绘制线条 (https://developer.apple.com/tutorials/swiftui/drawing-paths-and-shapes)

(作为奖励问题:有没有办法通过这种方法获得各个位置的 index/key,而无需将该键添加到每一行的位置数组中?)

我尝试了各种方法,例如 ForEach,在其中添加了 identified(),并为闭包添加了各种类型定义,但我最终还是产生了其他错误

import SwiftUI

var positions: [CGPoint] = [
    CGPoint(x: 100, y: 100),
    CGPoint(x: 100, y: 200),
    CGPoint(x: 100, y: 300),
]

struct ContentView : View {
    var body: some View {
        ZStack {
            positions.forEach { (position) in
                Text("Hello World")
                    .position(position)
            }
/* The above approach produces error, this commented version works
           Text("Hello World")
                .position(positions[0])
            Text("Hello World")
                .position(positions[1])
            Text("Hello World")
                .position(positions[2]) */
        }
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

并非所有内容在视图主体中都有效。如果你想执行一个 for-each 循环,你需要使用特殊视图 ForEach:

https://developer.apple.com/documentation/swiftui/foreach

视图需要可识别数组,可识别数组要求元素符合Hashable。您的示例需要像这样重写:

import SwiftUI

extension CGPoint: Hashable {
    public func hash(into hasher: inout Hasher) {
        hasher.combine(x)
        hasher.combine(y)
    }
}
var positions: [CGPoint] = [
    CGPoint(x: 100, y: 100),
    CGPoint(x: 100, y: 200),
    CGPoint(x: 100, y: 300),
]

struct ContentView : View {
    var body: some View {
        ZStack {
            ForEach(positions.identified(by: \.self)) { position in
                Text("Hello World")
                    .position(position)
            }
        }
    }
}

或者,如果您的数组无法识别,您可以逃避它:

var positions: [CGPoint] = [
    CGPoint(x: 100, y: 100),
    CGPoint(x: 100, y: 200),
    CGPoint(x: 100, y: 300),
]

struct ContentView : View {
    var body: some View {
        ZStack {
            ForEach(0..<positions.count) { i in
                Text("Hello World")
                    .position(positions[i])
            }
        }
    }
}

在您指向的 Apple 教程中,他们在 Path 闭包中使用了“.forEach”,这是一个 "normal" 闭包。 SwiftUI,使用了一个名为 "function builders" 的新 swift 特性。 ZStack 之后的 { brackets } 可能看起来像一个普通的闭包,但它不是!

参见示例。 https://www.swiftbysundell.com/posts/the-swift-51-features-that-power-swiftuis-api 了解更多关于函数生成器的信息。

本质上,"function builder"(更具体地说,在本例中为 ViewBuilder;阅读更多内容:https://developer.apple.com/documentation/swiftui/viewbuilder)获取 [=50= 中所有语句的数组],或者更确切地说,他们的价值观。在 ZStack 中,这些值应该符合 View 协议。

当你运行someArray.forEach {...}时,它会return什么都没有,void,也就是()。但是 ViewBuilder 需要符合 View 协议的东西!换句话说:

Cannot convert value of type '()' to closure result type '_'

当然不能!那么,我们如何做 loop/forEach 我们想要的 return?

同样,查看 SwiftUI 文档,在 "View Layout and Presentation" -> "Lists and Scroll Views" 下,我们得到:ForEach,这允许我们以声明方式描述迭代,而不是强制循环遍历位置:https://developer.apple.com/documentation/swiftui/foreach

当视图的状态发生变化时,SwiftUI 重新生成描述视图的结构,将其与旧结构进行比较,然后只对实际的结构进行必要的修补 UI,以节省性能并允许更精美的动画等。为了能够做到这一点,它需要能够识别例如中的每个项目。 a ForEach(例如,将新点的插入与现有点的更改区分开来)。因此,我们不能直接将 CGPoint 数组传递给 ForEach(至少不能不向 CGPoint 添加扩展,使它们符合 Identifiable 协议)。我们可以制作一个包装器结构:

import SwiftUI

var positions: [CGPoint] = [
    CGPoint(x: 100, y: 100),
    CGPoint(x: 100, y: 200),
    CGPoint(x: 100, y: 300),
]

struct Note: Identifiable {
    let id: Int
    let position: CGPoint
    let text: String
}

var notes = positions.enumerate().map { (index, position) in
    // using initial index as id during setup
    Note(index, position, "Point \(index + 1) at position \(position)")
}

struct ContentView: View {
    var body: some View {
        ZStack {
            ForEach(notes) { note in
                Text(note.text)
                    .position(note.position)
            }
        }
    }
}

然后我们可以将此功能添加到 tap-and-drag 注释中。当点击一个音符时,我们可能希望将它移动到 ZStack 的顶部。如果音符上正在播放任何动画(例如,在拖动过程中改变其位置),它通常会停止(因为整个 note-view 将被替换),但因为音符结构现在是 Identifiable, SwiftUI 会理解它只是被移动了,并在不干扰任何动画的情况下进行更改。

有关更深入的教程,请参阅 https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-views-in-a-loop-using-foreach or https://medium.com/@martinlasek/swiftui-dynamic-list-identifiable-73c56215f9ff:)

注意:代码尚未经过测试(gah beta Xcode)