【问题标题】:SwiftUI: ScrollView not working correctly with ForEachSwiftUI:ScrollView 无法与 ForEach 一起正常工作
【发布时间】:2021-10-15 07:14:34
【问题描述】:

我正在制作一个纸牌游戏,我正在尝试将纸牌放入ScrollView。我通过遍历一组卡片并随后使用CardView 为每张卡片构建视图来做到这一点。

struct ContentView: View {
    @ObservedObject var viewModel = SetGameViewModel()
    
    var body: some View {
        VStack {
            ScrollView {
                LazyVGrid(columns: [GridItem(),GridItem(),GridItem(),GridItem()]) {
                    ForEach(viewModel.cards) { card in
                        CardView(card: card)
                            //Individual card gets built depending on its data.
                            .aspectRatio(2/3, contentMode: .fill)
                        }
                }
            }
            DealThreeCardsButton()
                .onTapGesture { viewModel.dealThreeCards() }
        }
    }
}

每张卡片都有特定数量的符号(以及其他几个特征,但现在不是问题。)它应该显示。 ->

struct CardView: View {
    let card: SetGame.Card
    private var color: Color {
        switch card.content.color {
        case .green:
            return Color(.green)
        case .purple:
            return Color(.purple)
        case .red:
            return Color(.red)
        }
    }
    
    
    var body: some View {
        if card.isFaceUp {
            ZStack{
                RoundedRectangle(cornerRadius: 20)
                    .foregroundColor(.clear)
                RoundedRectangle(cornerRadius: 20)
                    .stroke(lineWidth: 2.0)
                    .foregroundColor(/*@START_MENU_TOKEN@*/.blue/*@END_MENU_TOKEN@*/)
                VStack {
                    CardContentView(content: card.content)
                        //The contents of the cards are created
                        .foregroundColor(color)
                    Text("\(card.id), n: \(card.content.number)")
                }
            }
        } else {
            RoundedRectangle(cornerRadius: 20)
                .foregroundColor(.blue)
        }
    }
}


private struct CardContentView: View {
    let content: SetGame.CardContent
    var body: some View {
        ForEach(1..<content.number+1) { _ in
            //This ForEach should create the number of symbols held by the card struct
            //However this seems to not work.
            ZStack {
                switch content.symbol {
                case .oval:
                    DrawCircle(fillType: content.fillType.rawValue)
                case .diamond:
                    DrawDiamond(fillType: content.fillType.rawValue)
                case .squiggle:
                    DrawSquiggle(fillType: content.fillType.rawValue)
                }
            }
        }
    }
}

当我运行它时,视图会正确构建。该图显示了 id 和 n(卡片上应包含的符号数。)

Correctly built view

但是,当我向下滚动和向后滚动时,视图会错误地重建。符号的数量似乎是完全随机的。这仅在ScrollView 具有一定长度时才会发生,因此当卡片数量超过 30 张卡片时(取决于它们的大小)。卡片不会混淆,因为 id 保持不变并且 n 也没有变化.

Refreshed and incorrectly rebuilt View

我错过了什么吗?一定与ScrollViewForEach 交互的方式有关。 CardContentView 结构和其中的 ForEach 语句似乎有问题。我只是不知道是什么。

这些是我刷新后遇到的错误;

ForEach, DrawSquiggle>>> count (3) != 它的初始计数 (2)。 ForEach(_:content:) 应该只用于常量数据。而是将数据符合Identifiable 或使用ForEach(_:id:content:) 并提供明确的id

下面;整个Model 包含CardContentCard 结构以及创建卡片数组的init;

struct SetGame {
    private(set) var cards: Array<Card>
    private(set) var deck: Array<Card>
    
    init() {
        deck = []
        cards = []
        var id = 1
        var color = CardContent.Color.red
        var fillType = CardContent.FillType.hollow
        var symbol = CardContent.Symbol.oval
        while deck.count < 81 {
            if deck.count % 27 == 0 {
                switch color {
                case .red:
                    color = .green
                case .green:
                    color = .purple
                case .purple:
                    color = .red
                }
            }
            if deck.count % 9 == 0 {
                switch symbol {
                case .oval:
                    symbol = .diamond
                case .diamond:
                    symbol = .squiggle
                case .squiggle:
                    symbol = .oval
                }
            }
            if deck.count % 3 == 0 {
                switch fillType {
                case .hollow:
                    fillType = .shaded
                case .shaded:
                    fillType = .filled
                case .filled:
                    fillType = .hollow
                }
            }
            deck.append(Card(id: id, content: CardContent(number: 1, color: color, fillType: fillType, symbol: symbol)))
            deck.append(Card(id: id+1, content: CardContent(number: 2, color: color, fillType: fillType, symbol: symbol)))
            deck.append(Card(id: id+2, content: CardContent(number: 3, color: color, fillType: fillType, symbol: symbol)))
            id += 3
        }
        //deck.shuffle()
        while cards.count < 81 {
            //When cards.count > 28; the view starts bugging
            cards.append(deck.popLast()!)
        }
    }
    
    mutating func dealThreeCards() {
        for _ in 0...2 {
            if deck.isEmpty {
                break
            } else {
                cards.append(deck.popLast()!)
            }
        }
        print("I was called :)")
    }
    
    struct Card: Identifiable {
        var isFaceUp = true
        var isMatched = false
        let id: Int
        let content: CardContent
    }
    
    struct CardContent: Equatable {
        let number: Int
        let color: Color
        let fillType: FillType
        let symbol: Symbol
        enum Color { case red, green, purple }
        enum FillType: Double {
            case hollow = 0.0
            case shaded = 0.2
            case filled = 1.0
        }
        enum Symbol { case oval, diamond, squiggle }
    }
}

下面; ContentView 中使用的整个 ViewModel。该应用程序非常简单,但我不明白什么是行不通的。

class SetGameViewModel: ObservableObject {
    @Published private(set) var game: SetGame
    
    
    init() {
        game = SetGame()
    }
    
    var cards: Array<SetGame.Card> {
        game.cards
    }
    
    func dealThreeCards() {
        game.dealThreeCards()
        print("I was called :)))")
    }
}

最小可重现示例

主要:

import SwiftUI

@main
struct mreApp: App {
    var body: some Scene {
        let game = ViewModel()
        WindowGroup {
            ContentView(viewModel: game)
        }
    }
}

型号:

import Foundation

struct CardGameModel {
    private(set) var cards: Array<Card>
    private(set) var deck: Array<Card>
    
    
    init() {
        //the init only takes care of creating the two arrays
        //deck and cards. It seems to be working correctly and nothing
        //is wrong here, I believe.
        deck = []
        cards = []
        var id = 1
        var color = CardContent.Color.red
        var fillType = CardContent.FillType.hollow
        var symbol = CardContent.Symbol.oval
        while deck.count < 81 {
            if deck.count % 27 == 0 {
                switch color {
                case .red:
                    color = .green
                case .green:
                    color = .purple
                case .purple:
                    color = .red
                }
            }
            if deck.count % 9 == 0 {
                switch symbol {
                case .oval:
                    symbol = .diamond
                case .diamond:
                    symbol = .squiggle
                case .squiggle:
                    symbol = .oval
                }
            }
            if deck.count % 3 == 0 {
                switch fillType {
                case .hollow:
                    fillType = .shaded
                case .shaded:
                    fillType = .filled
                case .filled:
                    fillType = .hollow
                }
            }
            deck.append(Card(id: id, content: CardContent(numberOfShapes: 1, color: color, fillType: fillType, symbol: symbol)))
            deck.append(Card(id: id+1, content: CardContent(numberOfShapes: 2, color: color, fillType: fillType, symbol: symbol)))
            deck.append(Card(id: id+2, content: CardContent(numberOfShapes: 3, color: color, fillType: fillType, symbol: symbol)))
            id += 3
        }
        //deck.shuffle()
        while cards.count < 81 {
            //When cards.count > 28; the view starts bugging.
            //However it also depends on the amount of columns in the
            //LazyVGrid. If more columns are included, the number of cards
            //displayable before bugs is greater.
            
            //Optional
            cards.append(deck.popLast()!)
        }
    }
    
    struct Card: Identifiable {
        let id: Int
        let content: CardContent
    }
    
    struct CardContent: Equatable {
        let numberOfShapes: Int
        let color: Color
        let fillType: FillType
        let symbol: Symbol
        enum Color { case red, green, purple }
        enum FillType: Double {
            case hollow = 0.0
            case shaded = 0.2
            case filled = 1.0
        }
        enum Symbol { case oval, diamond, squiggle }
    }
}

视图模型:

import Foundation

class ViewModel: ObservableObject {
    @Published private(set) var game: CardGameModel
    
    init() {
        game = CardGameModel()
    }
    
    var cards: Array<CardGameModel.Card> {
        game.cards
    }
}

查看:

import SwiftUI

struct ContentView: View {
    @ObservedObject var viewModel: ViewModel
    
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: [GridItem(),GridItem(),GridItem(),GridItem()]) {
                ForEach(viewModel.cards) { card in
                    CardView(card: card)
                        .aspectRatio(2/3,contentMode: .fill)
                }
            }
        }
    }
}


struct CardView: View {
    let card: CardGameModel.Card
    private var color: Color {
        switch card.content.color {
        case .green:
            return Color(.green)
        case .purple:
            return Color(.purple)
        case .red:
            return Color(.red)
        }
    }
    
    var body: some View {
        ZStack{
            RoundedRectangle(cornerRadius: 20)
                .stroke(lineWidth: 2.0)
                .foregroundColor(/*@START_MENU_TOKEN@*/.blue/*@END_MENU_TOKEN@*/)
            VStack {
                CardContentView(content: card.content)
                    //The contents of the cards are created
                    .foregroundColor(color)
                Text("\(card.id), n: \(card.content.numberOfShapes)")
            }
        }
    }
}

struct CardContentView: View {
    let content: CardGameModel.CardContent
    var body: some View {
        VStack {
            ForEach(0..<content.numberOfShapes) { _ in
                switch content.symbol {
                case .oval:
                    Circle()
                case .squiggle:
                    RoundedRectangle(cornerRadius: 35.0)
                case .diamond:
                    Rectangle()
                }
            }
        }
    }
    
}

【问题讨论】:

  • 你能包含 Card 和你的 viewModel 的代码吗?我认为问题在于——基于错误,我猜是 Card 是 Identifiable 但没有提供稳定的 ID,或者视图模型的数组正在改变。
  • 当然。我会将它们添加到原始问题中。
  • 包括minimal reproducible example。您所包含的内容不能由任何人编译或运行。
  • 好的,我也会加入。

标签: swift foreach swiftui scrollview


【解决方案1】:

CardContentView 中添加ForEach id 标识符作为错误消息的要求:

ForEach(0..<content.numberOfShapes, id: \.self) { _ in
       ...
}

经过测试并且可以正常工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-14
    • 2020-08-19
    • 2013-02-06
    • 2019-02-02
    相关资源
    最近更新 更多