swiftui+combine: why isFavoriteO changed when scroll the LazyVGrid?

I have a LazyVGrid, every item with is favorite button. and use combine to debounce user input($isFavoriteI), when isFavoriteO changed, then modify the items.
it works fine, but when i scroll the list, log will print: "X, isFavorite changed as false/true)", what cause isFavoriteO changed and why? because of item reusing in list? how to avoid it?

Code Block index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true

Code Block import SwiftUI
import Combine
struct Item {
var index: Int
var favorite: Bool
}
var items = [
Item(index: 0, favorite: true),
Item(index: 1, favorite: false),
Item(index: 2, favorite: true),
Item(index: 3, favorite: false),
Item(index: 4, favorite: true),
Item(index: 5, favorite: false),
Item(index: 6, favorite: true),
Item(index: 7, favorite: false),
// Item(index: 8, favorite: true),
// Item(index: 9, favorite: false),
// Item(index: 10, favorite: true),
// Item(index: 11, favorite: false),
// Item(index: 12, favorite: true),
// Item(index: 13, favorite: false),
// Item(index: 14, favorite: true),
// Item(index: 15, favorite: false),
// Item(index: 16, favorite: true),
// Item(index: 17, favorite: false),
// Item(index: 18, favorite: true),
// Item(index: 19, favorite: false),
]
struct ViewModelInListTestView: View {
var body: some View {
ScrollView(showsIndicators: false) {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200), spacing: 4, alignment: .center)], spacing: 4) {
ForEach(items, id: \.index) { item in
ItemView(item: item)
}
}
}.navigationTitle("ViewModel In List")
}
}
struct ItemView: View {
let item: Item
@ObservedObject var viewModel: ViewModel
init(item: Item) {
print("ItemView.init, \(item.index)")
self.item = item
self.viewModel = ViewModel(item: item)
}
var body: some View {
HStack {
Text("index \(item.index)")
Spacer()
Image(systemName: viewModel.isFavoriteI ? "heart.fill" : "heart")
.foregroundColor(viewModel.isFavoriteI ? .red : .white)
.padding()
.onTapGesture { onFavoriteTapped() }
.onChange(of: viewModel.isFavoriteO) { isFavorite in
setFavorite(isFavorite)
}
}
.frame(width: 200, height: 150)
.background(Color.gray)
}
func onFavoriteTapped() {
viewModel.isFavoriteI.toggle()
}
func setFavorite(_ isFavorite: Bool) {
print("index \(item.index), isFavorite changed as \(isFavorite)")
items[item.index].favorite = isFavorite
}
class ViewModel: ObservableObject {
@Published var isFavoriteI: Bool = false
@Published var isFavoriteO: Bool = false
private var subscriptions: Set<AnyCancellable> = []
init(item: Item) {
print("ViewModel.init, \(item.index)")
let isFavorite = item.favorite
isFavoriteI = isFavorite; isFavoriteO = isFavorite
$isFavoriteI
.print("index \(item.index) isFavoriteI:")
.dropFirst()
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
.removeDuplicates()
.eraseToAnyPublisher()
.print("index \(item.index) isFavoriteO:")
.receive(on: DispatchQueue.main)
.assign(to: \.isFavoriteO, on: self)
.store(in: &subscriptions)
}
}
}


it might be because you have to use debounce with RunLoop.main

.debounce(for: .milliseconds(500), scheduler: RunLoop.main)

swiftui&#43;combine: why isFavoriteO changed when scroll the LazyVGrid?
 
 
Q