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)
我正在尝试循环遍历 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)