Home > Software engineering >  The UI styling of Button is not displayed after being clicked
The UI styling of Button is not displayed after being clicked

Time:07-23

I am new to SwiftUI and trying doing wrap-up challenge.

I do not know why when I pass a list as Environment Object, change the element's attribute of the list to update the UI of the button, the button cannot display the change despite its attribute has been changed successfully.
Here is my project structure: https://i.stack.imgur.com/7KEaK.png

Main file: BookLibraryAppApp

import SwiftUI

@main
struct BookLibraryAppApp: App {
    var body: some Scene {
        WindowGroup {
            BookListView()
                .environmentObject(BookModel())
        }
    }
}

Model file: Book

import Foundation
class Book : Decodable, Identifiable {
    var id = 1
    var title = "Title"
    var author = "Author"
    var content = ["I am a test book."]
    var isFavourite = false
    var rating = 2
    var currentPage = 0
}

On view model file: BookModel

import Foundation
class BookModel : ObservableObject {
    @Published var books = [Book]()
    init() {
        books = getLoadData()
    }
    func getLoadData() -> [Book] {
        let fileName = "Data"
        let fileExtension = "json"
        var books = [Book]()
        
        // Get link to data file
        let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension)
        
        guard url != nil else {
            print("Could not retrieve category data: \(fileName).\(fileExtension) not found.")
            return books
        }
            
        do {
            // Decode the data and return it
            let data = try Data(contentsOf: url!)
            books = try JSONDecoder().decode([Book].self, from: data)
            return books
            
        } catch {
            print("Error retrieving category data: \(error.localizedDescription)")
        }
        
        return books
    }
    func updateFavourite(forId: Int) {
        if let index = books.firstIndex(where: { $0.id == forId }) {
            books[index].isFavourite.toggle()
        }
    }
    func getBookFavourite(forId: Int) -> Bool {
        if let index = books.firstIndex(where: { $0.id == forId }) {
            return books[index].isFavourite
        }
        return false
    }
}

On View folder:

  • BookListView
import SwiftUI

struct BookListView: View {
    @EnvironmentObject var model : BookModel
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVStack(spacing: 50) {
                    ForEach(model.books, id: \.id) {
                        book in
                        
                        NavigationLink {
                            BookPreviewView(book: book)
                        } label: {
                            BookCardView(book: book)
                        }

                    }
                }
                .padding(25)
            }
            .navigationTitle("My Library")
        }
    }
}

struct BookListView_Previews: PreviewProvider {
    static var previews: some View {
        BookListView()
            .environmentObject(BookModel())
    }
}
  • On smaller view: BookCardView
import SwiftUI

struct BookCardView: View {
    var book : Book
    var body: some View {
        ZStack {
            // MARK: container
            Rectangle()
                .cornerRadius(20)
                .foregroundColor(.white)
                .shadow(color: Color(.sRGB, red: 0, green: 0, blue: 0, opacity: 0.5), radius: 10, x: -5, y: 5)
            
            // MARK: card book content
            VStack (alignment: .leading, spacing: 9) {
                // MARK: title and favourite
                HStack {
                    Text(book.title)
                        .font(.title2)
                        .bold()
                    
                    Spacer()
                    
                    if (book.isFavourite) {
//                        Image(systemName: "star.fill")
//                            .resizable()
//                            .aspectRatio(contentMode: .fit)
//                            .frame(width: 30)
//                            .foregroundColor(.yellow)
                    }
                }
                
                // MARK: author
                Text(book.author)
                    .font(.subheadline)
                    .italic()
                
                // MARK: image
                Image("cover\(book.id)")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            }
            .padding(.horizontal, 35)
            .padding(.vertical, 30)
        }
    }
}

struct BookCardView_Previews: PreviewProvider {
    static var previews: some View {
        BookCardView(book: BookModel().books[0])
    }
}
  • On smaller view: BookPreviewView
import SwiftUI

struct BookPreviewView: View {
    @EnvironmentObject var model : BookModel
//    @State var select =
    var book : Book
    var body: some View {
        VStack {
            // MARK: banner
            Text("Read Now!")
            Spacer()
            
            // MARK: cover image
            Image("cover\(book.id)")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 250)
            Spacer()
            
            // MARK: bookmark
            Text("Mark for later")
            Button {
                // destination
                self.model.updateFavourite(forId: book.id)
//                self.select.toggle()
                print(model.getBookFavourite(forId: book.id))
            } label: {
                // label
                Image(systemName: self.book.isFavourite ? "star.fill" : "star")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 28)
                    .foregroundColor(.yellow)
            }

            Spacer()
            
            // MARK: rating
            Text("Rate Amazing Words")
            Spacer()
                
            
        }
    }
}

struct BookPreviewView_Previews: PreviewProvider {
    static var previews: some View {
        BookPreviewView(book: BookModel().books[0])
    }
}

Here is Data.json file:

[
    {
        "title": "Amazing Words",
        "author": "Sir Prise Party",
        "isFavourite": true,
        "currentPage": 0,
        "rating": 2,
        "id": 1,
        "content": [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla eu congue                            lacus", 
            "ac laoreet felis. Integer eros tortor, blandit id magna non, pharetra                 sodalesurna."
        ]},
{
        "title": "Text and More",
        "author": "Sir Vey Sample",
        "isFavourite": false,
        "currentPage": 0,
        "rating": 2,
        "id": 3,
        "content": [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla eu congue                            lacus", 
            "ac laoreet felis. Integer eros tortor, blandit id magna non, pharetra                 sodalesurna.
        ]}
]

When I try to click the Yellow start on the BookPreviewView, instead of changing from "star" to "star.fill", it shows nothing. May I ask what's wrong with my code? Thank you very much!

CodePudding user response:

This is a pretty simple error. The @Published property wrapper will send a notification to the view to update itself as soon as its value changes.

But in your case this never happens. You defined Book as a class (reference type), so changing one of its property doesn´t force the array (valuetype) to change, so @Published doesn´t pick up the change.

Two solutions here:

  • If you insist on keeping the class use:

    func updateFavourite(forId: Int) {
        if let index = books.firstIndex(where: { $0.id == forId }) {
            objectWillChange.send() // add this
            books[index].isFavourite.toggle()
        }
    }
    

    this will send the notification by hand.

  • the prefered solution should be to make your model Book a struct and it will pick up the changes.

  • Related