SwiftUI:ScrollView 无法与 ForEach 一起正常工作
SwiftUI: ScrollView not working correctly with ForEach
我正在制作纸牌游戏,我正在尝试将纸牌放入 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
我错过了什么吗?这一定与 ScrollView
和 ForEach
交互的方式有关。 CardContentView
结构和其中的 ForEach
语句似乎有问题。我只是不知道。
这些是我在刷新后遇到的错误;
ForEach<Range, Int, ZStack<_ConditionalContent<_ConditionalContent<DrawCircle, DrawDiamond>, DrawSquiggle>>> count (3) != its initial count (2). ForEach(_:content:)
should only be used for constant data. Instead conform data to Identifiable
or use ForEach(_:id:content:)
and provide an explicit id
!
以下;包含 CardContent
和 Card
结构的整个 Model
以及创建卡片数组的初始化;
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()
}
}
}
}
}
在 CardContentView
中添加 ForEach
错误消息要求的 id
标识符:
ForEach(0..<content.numberOfShapes, id: \.self) { _ in
...
}
已测试并正常工作。
我正在制作纸牌游戏,我正在尝试将纸牌放入 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
我错过了什么吗?这一定与 ScrollView
和 ForEach
交互的方式有关。 CardContentView
结构和其中的 ForEach
语句似乎有问题。我只是不知道。
这些是我在刷新后遇到的错误;
ForEach<Range, Int, ZStack<_ConditionalContent<_ConditionalContent<DrawCircle, DrawDiamond>, DrawSquiggle>>> count (3) != its initial count (2).
ForEach(_:content:)
should only be used for constant data. Instead conform data toIdentifiable
or useForEach(_:id:content:)
and provide an explicitid
!
以下;包含 CardContent
和 Card
结构的整个 Model
以及创建卡片数组的初始化;
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()
}
}
}
}
}
在 CardContentView
中添加 ForEach
错误消息要求的 id
标识符:
ForEach(0..<content.numberOfShapes, id: \.self) { _ in
...
}
已测试并正常工作。