Search code examples
user-interfaceswiftuimodelpublish

SwiftUI: UI does not update on @Published var change


So I have this model, and for simplicity I have only included one element in the posts variable. In my view I am displaying all the elements in the array and when an element is clicked, the view changes to only display information about that one post.

In this new view I have a button that indicates wether the isLiked value in the model is liked or not. I toggle between the states by using the togglePostLike function in the class.

If isLiked == false the button should have no color, and be red if it its true. The problem is that the UI doesn’t update the color / isLiked value immediately. I also have the same functionality somewhere else in my code, but there it updates just fine.

How can I solve this?

Model:

struct Post: Identifiable{
    var id = UUID().uuidString
    var postImage: String
    var title: String
    var description: String
    var starRating: Int
    var numberOfRatings : Int
    var postRating : Double
    var postDuration : Int
    var postLanguage : String
    var publishedBy : String
    var publishedYear : Int
    var isLiked : Bool
    var author : String
}



class DataStore: ObservableObject {
    @Published var posts = [
        Post(postImage: "post1", title: "Black Widow", description: "some description", starRating: 4, numberOfRatings: 251, postRating: 7.1, postDuration: 134, postLanguage: "English", publishedBy: "Alina", publishedYear: 2001, isLiked: false, author: "someAuthor1"),

        
    ]
    
    func togglePostLike(post: Post) {
        if let index = posts.firstIndex(where: { $0.id == post.id }) {
            posts[index].isLiked.toggle()
           
        }
    }
}

View UPDATED VIEW UPDATED VIEW 2

I have added a code block to show how I use dataStore and display the posts.

 This update is to show how the two view work together. 
struct play: View {
    @State var selectedPost : Post?
    @EnvironmentObject private var dataStore : DataStore
    
    @State var switchView = false
    
    var body: some View {
        if switchView == false {
            // When iterating I call each element "post"
            Image(systemName: post.isLiked ? "heart.fill" : "heart")
                .font(.system(size: 30))
                .foregroundColor(.red)
                .offset(x: 100, y: 150)
                .onTapGesture {
                    if post.isLiked == true {
                        dataStore.togglePostLike(post: post)
                        let removeFromFavorite = Album(title: post.title, author: post.author, postImage: post.postImage)
                        if let index = dataStore.albums.firstIndex(of: removeFromFavorite) {
                            dataStore.albums.remove(at: index)
                        }
                        
                        
                    } else {
                        dataStore.togglePostLike(post: post)
                        let addToFavorite = Album(title: post.title, author: post.author, postImage: post.postImage)
                        dataStore.albums.append(addToFavorite)
                    }
                    
                    
                    
                }
            
            // Iterate all the posts and then give "selectedPost" the value of the clicked post
            ForEach(dataStore.posts, id:\.id) {post in
                Image("\(post.postImage)")
                    .onTapGesture {
                        selectedPost = post
                        switchView = true
                    }
            }
            
            switchView = true
        } else {
            NewView(selectedPost: selectedPost!)
                .environmentObject(dataStore)
        }
    }
}


struct NewView: View {

    @EnvironmentObject private var dataStore : DataStore
    
    var selectedPost : Post

    var body: some View {
        VStack {
            
            Text(String(selectedPost.isLiked))
            if selectedPost.isLiked {
                Circle()
                    .foregroundColor(.red)
                    .frame(width: 50, height: 50, alignment: .center)
                
                    .foregroundColor(.black)
                    .overlay(
                        Image(systemName: "heart")
                            .font(.system(size: 25))
                            .foregroundColor(.white)
                    )
                Text("Save")
            } else {
                Circle()
                    .strokeBorder(.black, lineWidth: 2.5)
                    .frame(width: 50, height: 50, alignment: .center)
                
                    .foregroundColor(.black)
                    .overlay(
                        Image(systemName: "heart")
                            .font(.system(size: 25))
                            .foregroundColor(.black)
                    )
                Text("Save")
            }
        
        }
        .onTapGesture {

            dataStore.togglePostLike(post: selectedPost)
            if selectedPost.isLiked == true {
                
                
                let removeFromFavorite = Album(title: selectedPost.title, author: selectedPost.author, postImage: selectedPost.postImage)
                if let index = dataStore.albums.firstIndex(of: removeFromFavorite) {
                    dataStore.albums.remove(at: index)
                }
                

            } else {
                
                let addToFavorite = Album(title: selectedPost.title, author: selectedPost.author, postImage: selectedPost.postImage)
                dataStore.albums.append(addToFavorite)
            }

        }
    }
}

Solution

  • I am answering my own question here, but I can’t really explain the "why´s" so feel free to add a comment with further explanation to my post.

    I managed to fix the problem by changing var selectedPost : Post to a @Binding, as such: @Binding var selectedPost : Post.

    I then also changed the .onTapGesture in the NewView to this:

    .onTapGesture {
    
                dataStore.togglePostLike(post: selectedPost)
                if selectedPost.isLiked == true {
    
                    // This line is new
                    selectedPost.isLiked = false
                    
                    let removeFromFavorite = Album(title: selectedPost.title, author: selectedPost.author, postImage: selectedPost.postImage)
                    if let index = dataStore.albums.firstIndex(of: removeFromFavorite) {
                        dataStore.albums.remove(at: index)
                    }
                    
    
                } else {
    
                    // This line is new
                    selectedPost.isLiked = true
    
                    let addToFavorite = Album(title: selectedPost.title, author: selectedPost.author, postImage: selectedPost.postImage)
                    dataStore.albums.append(addToFavorite)
                }
    
            }