Swift UI ForEach 只能用于*常量*数据

Ama*_*nda 4 iphone ios swift swiftui

所以我在运行我设置的游戏应用程序时收到此错误:

\n

当出现更多相同卡牌时的游戏屏幕图像(游戏中的所有卡牌都应该是唯一的)

\n
ForEach<Range<Int>, Int, ModifiedContent<_ConditionalContent<_ConditionalContent<_ConditionalContent<_ConditionalContent\n<ZStack<TupleView<(_ShapeView<Squiggle, ForegroundStyle>, \n_StrokedShape<Squiggle>)>>, ZStack<TupleView<(ModifiedContent<_StrokedShape<StripedRect>, \n_ClipEffect<Squiggle>>, _StrokedShape<Squiggle>)>>>, _StrokedShape<Squiggle>>, \n_ConditionalContent<_ConditionalContent<ZStack<TupleView<(_ShapeView<Capsule, ForegroundStyle>, _StrokedShape<Capsule>)>>, \nZStack<TupleView<(ModifiedContent<_StrokedShape<StripedRect>, _ClipEffect<Capsule>>, \n_StrokedShape<Capsule>)>>>, _StrokedShape<Capsule>>>, _ConditionalContent<_ConditionalContent<ZStack<TupleView<(_ShapeView<Diamond, \nForegroundStyle>, _StrokedShape<Diamond>)>>, ZStack<TupleView<(ModifiedContent<_StrokedShape<StripedRect>, _ClipEffect<Diamond>>, \n_StrokedShape<Diamond>)>>>, _StrokedShape<Diamond>>>,\n _FrameLayout>> count (2) != its initial count (1). `ForEach(_:content:)`\n should only be used for *constant* data. Instead conform data to `Identifiable` \nor use `ForEach(_:id:content:)` and provide an explicit `id`!\n
Run Code Online (Sandbox Code Playgroud)\n

视图本身下方的卡片模型似乎没有改变。除了卡片上的形状数量之外,所有内容都保持不变。因此,仅当底层模型正确匹配时,显示错误的卡片才会匹配,并且不会像视图中显示的那样匹配。

\n

仅当我获得正确的设置并按“显示另外三张卡片”或当我显示的卡片多于可见卡片然后在屏幕上上下滚动时,才会出现该错误。

\n

应用程序不会因错误而停止,但每张卡上的 numberOfShapes 会动态变化(如上面的屏幕截图所示)。(当我滚动到屏幕顶部然后滚动到底部时,这一点最为清晰,并且某些卡片上的形状数量每次都会发生变化)

\n

所以我认为问题可能出在卡片视图中:

\n
import SwiftUI\n\nstruct CardView: View {\n    \n    var card: Model.Card\n    \n    var body: some View {\n        GeometryReader { geometry in\n            ZStack{\n                let shape = RoundedRectangle(cornerRadius: 10)\n                if !card.isMatched {\n                    shape.fill().foregroundColor(.white)\n                    shape.strokeBorder(lineWidth: 4).foregroundColor(card.isSelected ? .red : .blue)\n                }\n            \n                VStack {\n                    Spacer(minLength: 0)\n                    //TODO: Error here?\n                    ForEach(0..<card.numberOfShapes.rawValue) { index in\n                        cardShape().frame(height: geometry.size.height/6)\n                    }\n                    Spacer(minLength: 0)\n                }.padding()\n                .foregroundColor(setColor())\n                .aspectRatio(CGFloat(6.0/8.0), contentMode: .fit)\n                .frame(width: geometry.size.width, height: geometry.size.height)\n            }\n        }\n    }\n    \n    @ViewBuilder private func cardShape() -> some View {\n        switch card.shape {\n        case .squiglle:          shapeFill(shape: Squiggle())\n        case .roundedRectangle:  shapeFill(shape: Capsule())\n        case .diamond:           shapeFill(shape: Diamond())\n        }\n    }\n    \n    private func setColor() -> Color {\n        switch card.color {\n        case .pink: return Color.pink\n        case .orange: return Color.orange\n        case .green: return Color.green\n        }\n    }\n    \n    @ViewBuilder private func shapeFill<setShape>(shape: setShape) -> some View\n    //TODO: se l\xc3\xb8sning, brug rawvalues for cleanness\n    where setShape: Shape {\n        switch card.fill {\n        case .fill:       shape.fillAndBorder()\n        case .stripes:    shape.stripe()\n        case .none:       shape.stroke(lineWidth: 2)\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

卡片视图以灵活的网格布局显示:

\n
import SwiftUI\n\nstruct AspectVGrid<Item, ItemView>: View where ItemView: View, Item: Identifiable {\n    var items: [Item]\n    var aspectRatio: CGFloat\n    var content: (Item) -> ItemView\n    let maxColumnCount = 6\n    \n    init(items: [Item], aspectRatio: CGFloat, @ViewBuilder content: @escaping (Item) -> ItemView) {\n        self.items = items\n        self.aspectRatio = aspectRatio\n        self.content = content\n    }\n    \n    var body: some View {\n            GeometryReader { geometry in\n                VStack {\n                    let width: CGFloat = widthThatFits(itemCount: items.count, in: geometry.size, itemAspectRatio: aspectRatio)\n                    ScrollView{\n                    LazyVGrid(columns: [adaptiveGridItem(width: width)], spacing: 0) {\n                        ForEach(items) { item in\n                            content(item).aspectRatio(aspectRatio, contentMode: .fit)\n                    }\n                    Spacer(minLength: 0)\n                    }\n                }\n                }\n            }\n        }\n    \n    private func adaptiveGridItem(width: CGFloat) -> GridItem {\n        var gridItem = GridItem(.adaptive(minimum: width))\n        gridItem.spacing = 0\n        return gridItem\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

看法:

\n
import SwiftUI\n\nstruct SetGameView: View {\n    @StateObject var game: ViewModel\n    \n    var body: some View {\n        VStack{\n            HStack(alignment: .bottom){\n                Text("Set Game")\n                    .font(.largeTitle)\n            }\n            AspectVGrid(items: game.cards, aspectRatio: 2/3) { card in\n                    CardView(card: card)\n                        .padding(4)\n                        .onTapGesture {\n                            game.choose(card)\n        \n                }\n            }.foregroundColor(.blue)\n            .padding(.horizontal)\n            Button("show 3 more cards"){\n                game.showThreeMoreCardsFromDeck()\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

非常感谢任何解决此问题的帮助。我已经在这上面花了很多时间了。

\n

rob*_*off 13

你的问题在这里:

\n
\n
ForEach(0..<card.numberOfShapes.rawValue) { index in\n    cardShape().frame(height: geometry.size.height/6)\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

ForEach初始化程序的第一个参数是Range<Int>. 文档说(强调):

\n
\n

该实例仅读取所提供数据的初始值,并且不需要识别更新之间的视图。要在动态范围内按需计算视图,请使用ForEach/init(_:id:content:)

\n
\n

因为它只读取\xe2\x80\x9c提供的数据\xe2\x80\x9d的初始值,所以只有当 是Range<Int>常量时才可以安全使用。如果改变范围,效果是不可预测的。因此,从 Xcode 13 开始,如果您传递非常量范围,编译器会发出警告。

\n

现在,也许你的范围 ( 0..<card.numberOfShapes.rawValue) 确实是恒定的。但编译器不知道这一点。(根据您的错误,它不是一个恒定的范围。)

\n

您可以通过切换到带有参数的初始化器来避免警告id:

\n
ForEach(0..<card.numberOfShapes.rawValue, id: \\.self) { index in\n                                     // ^^^^^^^^^^^^^\n                                     //    add this\n    cardShape().frame(height: geometry.size.height/6)\n}\n
Run Code Online (Sandbox Code Playgroud)\n