Disclaimer: Android developer here
Been trying to make an existing android app work on iOS via KMM and I'd like to know if there's a way to force a view update in SwiftUI when only an update is made to an Item's paramaters in an array. (Something like the array[index].copy() function in Jetpack Compose
Here's the set up:
class MyViewModel: ObservableObject {
...
@Published var itemsArray = [Item]()
...
func increaseCount(itemIndex: Int) {
let count = itemsArray[itemIndex].quantity 1
itemsArray[itemIndex].quantity = count
print("item count is \(count)")
}
func decreaseCount(itemIndex: Int) {
var count: Int
if itemsArray[itemIndex].quantity < 2 {
count = 1
} else {
count = Int(itemsArray[itemIndex].quantity - 1)
}
itemsArray[itemIndex].quantity = Int32(count) //Xcode insisted on Int32
}
...
}
On the View in question:
struct SecondView: View {
@ObservedObject var viewModel: MyViewModel
...
var body: some View {
...
ForEach (viewModel.itemsArray, id: \.self) {
item in
let index = viewModel.itemsArray.firstIndex(of: item)
HStack {
Button(action: {viewModel.decreaseCount(itemIndex: index!
)}) {
Image(systemName: "minus.circle")
.foregroundColor(Color.gray)
}
Text("\(viewModel.itemsArray[index!].quantity)")
Button(action: {viewModel.increaseCount(itemIndex: index!)}) {
Image(systemName: "plus.circle")
.foregroundColor(Color.black)
}
}
}
}
}
I am also setting
extension Item: Identifiable{}
on the file containing the ViewModel
The print statements in the viewmodel get updated according but the UI does not. Any help will be appreciated.
EDIT: Just realised that if I remove the element at the specified index and re-insert it, the UI updates. Surely there has to be a more efficient/ elegant way to this is on Swift, right?
CodePudding user response:
without all relevent the code, I can only guess. Try something like this:
struct SecondView: View {
@ObservedObject var viewModel: MyViewModel
...
var body: some View {
...
ForEach ($viewModel.itemsArray, id: \.self) { $item in // <-- here
HStack {
Button(action: {
if item.quantity < 2 { // <-- here
item.quantity = 1
} else {
item.quantity -= 1
}
}) {
Image(systemName: "minus.circle").foregroundColor(Color.gray)
}
Text("\(item.quantity)")
Button(action: { item.quantity = 1 }) { // <-- here
Image(systemName: "plus.circle").foregroundColor(Color.black)
}
}
}
}
}
and no need for your increaseCount
and decreaseCount
code in your MyViewModel
.
EDIT-1: here is my test code that shows the UI is updated when the buttons are tapped/clicked.
struct Item: Identifiable, Hashable {
let id = UUID()
var quantity = 0
}
class MyViewModel: ObservableObject {
@Published var itemsArray = [Item(),Item(),Item()] // for testing some Items in the array
}
struct ContentView: View {
@StateObject var viewModel = MyViewModel() // <-- here, or let
var body: some View {
SecondView(viewModel: viewModel)
}
}
struct SecondView: View {
@ObservedObject var viewModel: MyViewModel
var body: some View {
VStack(spacing: 33) {
ForEach ($viewModel.itemsArray, id: \.self) { $item in // <-- here
HStack {
Button(action: {
if item.quantity < 1 { // <-- here
item.quantity = 0
} else {
item.quantity -= 1
}
}) {
Image(systemName: "minus.circle").foregroundColor(Color.gray)
}
Text("\(item.quantity)")
Button(action: { item.quantity = 1 }) { // <-- here
Image(systemName: "plus.circle").foregroundColor(Color.black)
}
}
}
}
}
}