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
}