简体   繁体   中英

SwiftUI ForEach not updating following changes to the ObservableObject class array

Background

In my app I have an array of friends that can be added to from another view. I've stored this array of friends in a class which is an ObservableObject . Within my main view, FriendListView , I am using a ForEach to display a custom view for each friend that a user has added.

I have not been able to add to the array and have the list display the new item. I have tried two different attempts based upon what I was able to understand and put together. Any help that someone is able to provide would be greatly appreciated. I currently have the method to display the 2nd view where I will be adding a new friend commented out and a method to automatically save a new one exists as a navigation bar item.

Attempts

Attempt #1

I was successful in displaying the list of friends if they were appended to the array during the class initializer. Calling the class method to add to the array from another view seems to work based upon me being able to print the contents of the array but the UI does not update.

This was completed by using a ForEach(0..<Array.count) approach but I received the following console error after adding a new item.

Error

ForEach<Range<Int>, Int, HStack<TupleView<(Spacer, FriendView, Spacer)>>> count (4) != its initial count (3).
`ForEach(_:content:)` should only be used for *constant* data.
Instead conform data to `Identifiable` or use `ForEach(_:id:content:)` and provide an explicit `id`!

Model Code

struct Pet: Identifiable {
    var id = UUID().uuidString
    var name: String
    var breed: String
}

class FriendModel: ObservableObject {
    
    @Published var petList = [Pet]()
    
    func addFriend(name: String, breed: String) {
        petList.append(Pet(name: name, breed: breed))
    }
    
    init() {
        petList.append(Pet(name: "Woofer", breed: "Dawg"))
        petList.append(Pet(name: "Floofer", breed: "Dawg"))
        petList.append(Pet(name: "Super", breed: "Dawg"))
//        petList.append(Pet(name: "Dooper", breed: "Dawg"))
//        petList.append(Pet(name: "Booper", breed: "Dawg"))
    }
}

UI Code

struct FriendListView: View {   
    
    @EnvironmentObject var dataModel: FriendModel
    
    @State private var displayAddFriendSheet = false
    
    var body: some View {
        GeometryReader { geo in
            NavigationView {
                ScrollView {
                    ForEach(0..<self.dataModel.petList.count) { pet in
                        HStack {
                            Spacer()
                            FriendView(
                                name: self.dataModel.petList[pet].name,
                                breed: self.dataModel.petList[pet].breed,
                                screenWidth: geo.size.width,
                                randomPic: Int.random(in: 1...2)
                            )
//                            FriendView(
//                                name: self.dataModel.petList[pet].name,
//                                breed: self.dataModel.petList[pet].breed,
//                                screenWidth: geo.size.width)
                            Spacer()
                        }
                    }
                }// End of ScrollView
                .navigationBarTitle(Text("Furiends"))
                    .navigationBarItems(trailing: Button(action: self.showAddFriend) {
                    Image(systemName: "plus")
                }
                
                )
                .sheet(isPresented: self.$displayAddFriendSheet) {
                        AddFriendView()
                }
                
            }// End of NavigationView
        }// End of GeometryReader geo1
    }// End of body
    
    func showAddFriend() {
//        self.displayAddFriendSheet.toggle()
        self.dataModel.addFriend(name: "Jax", breed: "Doodle")
        print(dataModel.petList)
        
    }// Enf of showAddFriend
    
}// End of FriendListView

Attempt #2

After seeing the error from the first attempt, I read how I could skip the range and provide just the array as a part of the ForEach since I am adhering to the Identifiable protocol with my UUID in the Pet struct. With the model code remaining the same as my first attempt, my updated UI code is below. Note the only change was to the ForEach but I received the following error:

Error Cannot convert value of type '[Pet]' to expected argument type 'Range<Int>'

UI Code

struct FriendListView: View {   
    
    @EnvironmentObject var dataModel: FriendModel
    
    @State private var displayAddFriendSheet = false
    
    var body: some View {
        GeometryReader { geo in
            NavigationView {
                ScrollView {
                    ForEach(self.dataModel.petList) { pet in  // <--- Changed to directly reference the array
                        HStack {
                            Spacer()
                            FriendView(
                                name: self.dataModel.petList[pet].name,
                                breed: self.dataModel.petList[pet].breed,
                                screenWidth: geo.size.width,
                                randomPic: Int.random(in: 1...2)
                            )
//                            FriendView(
//                                name: self.dataModel.petList[pet].name,
//                                breed: self.dataModel.petList[pet].breed,
//                                screenWidth: geo.size.width)
                            Spacer()
                        }
                    }
                }// End of ScrollView
                .navigationBarTitle(Text("Furiends"))
                    .navigationBarItems(trailing: Button(action: self.showAddFriend) {
                    Image(systemName: "plus")
                }
                
                )
                .sheet(isPresented: self.$displayAddFriendSheet) {
                        AddFriendView()
                }
                
            }// End of NavigationView
        }// End of GeometryReader geo1
    }// End of body
    
    func showAddFriend() {
//        self.displayAddFriendSheet.toggle()
        self.dataModel.addFriend(name: "Jax", breed: "Doodle")
        print(dataModel.petList)
        
    }// Enf of showAddFriend
    
}// End of FriendListView

I assume the remaining try to access by subscription confuses compiler, so as far as Pet is Identifiable the following should work

ForEach(self.dataModel.petList) { pet in
    HStack {
        Spacer()
        FriendView(
            name: pet.name,        // << pet is not index
            breed: pet.breed,      // << so access by property
            screenWidth: geo.size.width,
            randomPic: Int.random(in: 1...2)
        )
 // .. other code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM