Reputation: 21
New to SwiftUI and trying to display these emojis on separate CardViews inside of a LazyVGrid. When I use a ForEach loop to set one emoji per CardView, I cannot use duplicate emojis - even though I used id: \.self
(actually, not entirely sure that matters for what I'm trying to achieve but thought it was worth mentioning).
Basically, I can show 5 cards but, when I try to show 6 (which is when the duplicate items begin), the preview crashes. I can do it with an HStack but, for some reason, cannot with a LazyVStack. I reviewed LazyVStack documentation but couldn't find an answer to this.
Additionally, tried using .indicies in ForEach but didn't seem to fix it either.
import SwiftUI
struct ContentView: View {
var emojis: [String] = ["😊", "😇", "😄", "😎",
"😝", "😊", "😇", "😄",
"😎", "😝", "😊", "😇",
"😎", "😝", "😊", "😇",
"😄", "😎", "😝", "😊",
"😇", "😄", "😎", "😝"]
@State var emojiCount = 5 //6
var gridLayout = [GridItem(), GridItem(), GridItem()]
var body: some View {
VStack {
LazyVGrid(columns: gridLayout) {
// HStack {
ForEach((emojis[0..<emojiCount]), id: \.self) { emoji in
CardView(content: emoji)
}
}
.foregroundColor(.red)
Spacer()
HStack {
remove
Spacer()
add
}
}
.padding(.horizontal)
}
var add: some View {
Button(action: {
emojiCount += 1
}, label: {
Text("⨁")
})
}
var remove: some View {
Button(action: {
if emojiCount > 0 {
emojiCount -= 1
}
}, label: {
Text("⊖")
})
}
}
struct CardView: View {
var content: String
@State var isFaceUp: Bool = true
var body: some View {
ZStack {
let shape = RoundedRectangle(cornerRadius: 20.0)
if isFaceUp {
shape.fill().foregroundColor(.white)
shape.stroke(lineWidth: 3.0)
Text(content)
} else {
shape.fill()
}
}
.onTapGesture {
isFaceUp = !isFaceUp
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.preferredColorScheme(.dark)
ContentView()
.preferredColorScheme(.light)
}
}
struct ContentView_Previews_2: PreviewProvider {
static var previews: some View {
/*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/
}
}
Upvotes: 2
Views: 2264
Reputation: 30451
You can't have duplicate id
s for views within LazyVGrid
(and others such as List
). Since you have duplicate emojis and you are identifying each CardView
by \.self
, you have views with duplicate IDs.
Only use this if you have constant data, otherwise you may get the problems mentioned below.
Use the index. This will always be unique. However, this may cause problems when the emojis
changes, because views will then be swapping IDs (which breaks animations, etc). The id
s here are not constant.
See solution #2 for a better way.
ForEach(0 ..< emojiCount, id: \.self) { emojiIndex in
CardView(content: emojis[emojiIndex])
}
You can use this on constant or mutating data.
Make each emoji uniquely identifiable. This is the best solution. The id
s here are constant.
struct MyEmoji: Identifiable {
let id = UUID()
let string: String
init(_ string: String) {
self.string = string
}
}
var emojis: [MyEmoji] = [MyEmoji("😊"), MyEmoji("😇"), MyEmoji("😄"), MyEmoji("😎"),
MyEmoji("😝"), MyEmoji("😊"), MyEmoji("😇"), MyEmoji("😄"),
MyEmoji("😎"), MyEmoji("😝"), MyEmoji("😊"), MyEmoji("😇"),
MyEmoji("😎"), MyEmoji("😝"), MyEmoji("😊"), MyEmoji("😇"),
MyEmoji("😄"), MyEmoji("😎"), MyEmoji("😝"), MyEmoji("😊"),
MyEmoji("😇"), MyEmoji("😄"), MyEmoji("😎"), MyEmoji("😝")]
ForEach(emojis[0 ..< emojiCount]) { emoji in
CardView(content: emoji.string)
}
Upvotes: 5
Reputation: 817
You didn't say how you used indices, but the following worked in my own tests:
ForEach(Array((emojis[0..<emojiCount]).enumerated()),id:\.offset) { index,emoji in {
....
}
I'm not sure why HStack works, but in general, SwiftUI uses ids to track changes so it's a best practice to keep them as close to real identifiers as possible. In the case where you're using \.self, it uses the emoji string itself which ends up with duplicate ids. There's a WWDC video from this year (2021) explaining the importance of ids and how misuse can cause odd SwiftUI behavior: https://developer.apple.com/videos/play/wwdc2021/10022/
Upvotes: 0