Home > database >  SwiftUI list view not reflecting removal of correct view model object?
SwiftUI list view not reflecting removal of correct view model object?

Time:03-04

I am creating a feature where a user can remove a post from array after deleting it from our API, I am able to successfully remove the correct post from the array but my NewsFeed view always removes the wrong PostCell from the view, even when the correct object is being removed from the array???

The NewsFeed view seems to remove the last PostCell in it's list instead of the object that was successfully removed from the array??

I am performing a check that the correct object was removed from the view model after removing it by printing out all of the object id's and it is definitely gone. However the Newsfeed view removes a totally different view (the last one) and still shows the view that was supposed to be removed?

Any idea why this is happening?

Newsfeed view showing all posts

struct NewsFeed: View {
    @StateObject var newsfeedVM = NewsFeedViewModel()

    var body: some View {
        NavigationView {
            List(0..<newsfeedVM.posts.count, id: \.self) { index in
               PostCell(post: newsfeedVM.posts[index] )
            }
        }
    }
}

View Model

class NewsFeedViewModel: ObservableObject {
    @Published var posts: [Post] = []
           
    var subscriptions = Set<AnyCancellable>()

    static let removePostFromArray = PassthroughSubject<String, Never>()
    
    init() {
        Self.removePostFromArray.sink { postId in
            print(postId)
            if let index = self.posts.firstIndex(where: { $0.id == postId }) {
                 // removes correct object
                self.posts.remove(at: index)
                 // confirms correct object was removed
                self.posts.forEach { post in
                    print(post.id)
                }
            }
        }.store(in: &subscriptions)
    }
}

Each post cell has an action-sheet to remove the post.

struct PostCell: View {
    @StateObject var postVM = PostViewModel()
    @State var post: Post
    @State var showActionSheet: Bool = false

    var body: some View {
        VStack(spacing: 5) {
            PostMedia(post: $post)
        }
        .actionSheet(isPresented: $showActionSheet) { "REPORT POST" }
    }

    // remove post with Combine
    func reportPost(post: Post) {
        NewsFeedViewModel.removePostFromArray.send(report.postId)
    }
}

CodePudding user response:

This can be solved without static members and Combine. I would pass a closure to each PostCell which is the action that gets performed when the user deletes/reports the post:

struct NewsFeed: View {
    @StateObject var newsfeedVM = NewsFeedViewModel()

    var body: some View {
        NavigationView {
            List(newsfeedVM.posts, id: \.id) { post in
               PostCell(post: post, removePost: {vm.removePost(post.id)} ) // use the ID not the index
            }
        }
    }
}
class NewsFeedViewModel: ObservableObject {
    @Published var posts: [Post] = []
           
    // var subscriptions = Set<AnyCancellable>()

    // none of the static stuff: static let removePostFromArray = PassthroughSubject<String, Never>()
    
    func removePost(id: Int) {
        posts.removeAll(where: {$0.id == id})
    }
}
struct PostCell: View {
    @StateObject var postVM = PostViewModel()
    @State var post: Post
    @State var showActionSheet: Bool = false
    var removePost: func () -> ()

    var body: some View {
        VStack(spacing: 5) {
            PostMedia(post: $post)
        }
        .actionSheet(isPresented: $showActionSheet) { "REPORT POST" } // call report() here
    }

    // use a closure instead
    func report() {
        // not this: NewsFeedViewModel.removePostFromArray.send(report.postId)
        removePost()
    }
}

edit: if you want to have access to index, you can pass an array of tuples (index, item) to List (only tested this with ForEach. should work though):

List(Array(newsfeedVM.posts.enumerated()), id: \.index) { index, item in
    // ... now you have both index and item. you use item to draw the views
}
  • Related