【问题标题】:Array.forEach creates error "Cannot convert value of type '()' to closure result type '_'"Array.forEach 创建错误“无法将 '()' 类型的值转换为闭包结果类型 '_'”
【发布时间】:2019-11-16 20:33:31
【问题描述】:

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

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

我尝试了各种方法,例如 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

【问题讨论】:

    标签: foreach swiftui


    【解决方案1】:

    在您指出的 Apple 教程中,他们在 Path 闭包中使用了“.forEach”,这是一个“正常”闭包。 SwiftUI,使用了一个新的 swift 特性,叫做“函数构建器”。 ZStack 之后的{ brackets } 可能看起来像一个通常的闭包,但事实并非如此!

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

    本质上,“函数构建器”(更具体地说,ViewBuilder,在这种情况下;阅读更多:https://developer.apple.com/documentation/swiftui/viewbuilder)获取“闭包”中所有语句的数组,或者更确切地说,它们的值。在 ZStack 中,这些值应该符合View 协议。

    当你运行someArray.forEach {...}时,它不会返回任何东西,void,也称为()。但是 ViewBuilder 期望符合View 协议的东西!换句话说:

    无法将类型“()”的值转换为闭包结果类型“_”

    当然不能!那么,我们如何做一个循环/forEach 来返回我们想要的呢?

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

    当视图的状态发生变化时,SwiftUI 会重新生成描述视图的结构,将其与旧结构进行比较,然后只对实际 UI 进行必要的补丁,以节省性能并允许更精美的动画等。为此,它需要能够识别例如中的每个项目。 a ForEach(例如,将插入新点与仅更改现有点区分开来)。因此,我们不能只将 CGPoints 数组直接传递给 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)
                }
            }
        }
    }
    

    然后我们可以添加点击和拖动笔记的功能。当点击一个笔记时,我们可能想要将它移动到 ZStack 的顶部。如果在音符上播放任何动画(例如,在拖动过程中改变其位置),它通常会停止(因为整个音符视图将被替换),但由于音符结构​​现在是 Identifiable,SwiftUI 会理解它只是被移动了,并且在不干扰任何动画的情况下进行更改。

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

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

    【讨论】:

    • 感谢您非常清晰易懂的回答!请注意,我尝试为 .forEach 循环返回的内容添加一些定义,但这些定义会导致更令人困惑的错误......现在我明白了为什么,我应该改用 ForEach 。再次感谢!
    【解决方案2】:

    并非所有内容在视图主体中都是有效的。如果你想执行一个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 的教程 (developer.apple.com/tutorials/swiftui/drawing-paths-and-shapes) 中有效的原因是循环在路径内? (你必须向下滚动到第 4 步才能看到 .forEach 的使用)
    • 是的,你是对的。在布局视图时,有很多限制。它不是真正的 swift。
    • 谢谢!我自己也对其进行了测试,这很有效。必须学习更多基础知识...code struct ContentView : View { var body: some View { Path { path in path.move(to: position[2]) position.forEach { (position) in path.addLine(to : 位置) } } .fill(Color.black) } }code
    • 我添加了另一种方法,它不需要 Hashable 或 Identifiable 一致性。
    • 还要注意你的数组并没有被声明为可绑定的,如果以后发生变化,SwiftUI 无从得知,你的视图也不会更新。如果您希望您的视图随着数组中的更改而更新,您需要创建一个 BindableObject、一个 State 或一个 EnvironmentObject。 WWDC2019 session 226 - Data Flow Through SwiftUI 中详细解释了所有内容。强烈推荐观看。
    猜你喜欢
    • 2021-03-08
    • 1970-01-01
    • 1970-01-01
    • 2017-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多