0

My problem is with SwiftData not updating the object which is used with @Query. here are my code This is my View which used to show list of books,

struct ReadingView: View {
    
@Environment(\.modelContext) private var modelContext
@Query(filter: #Predicate<SDBook> { book in
    book.status == "reading"
}) var itemsBooks: [SDBook]

@Query var items: [SDBook]


var body: some View {
    ScrollView {
        LazyVStack {
            ForEach(itemsBooks, id: \.id) { value in
                ReadingRowView(book: value)
            }
        }
    }
    
    
}

}

and from ReadingRowView, i am presenting alert to update the current page in book, but this change is not updating in ReadingView().

Here is the code for ReadingRowView

@Observable
class ReadingBooksModel {
var book: SDBook

  init(book: SDBook) {
    self.book = book
  }
}
struct ReadingRowView: View {
 @Environment(\.modelContext) private var modelContext
 private var itemModel: ReadingBooksModel
 @State var progress = 0.0
 @State private var showingAlert = false
 @State private var pageRead = ""
 @State private var showError = false

 init(book: SDBook) {
     self.itemModel = ReadingBooksModel(book: book)
 }

 var body: some View {
    HStack {
        if let selectedPhotoData = itemModel.book.image, let uiImage = UIImage(data: selectedPhotoData) {
            Image(uiImage: uiImage)
                .resizable()
                //.frame(minWidth: 0, maxWidth: .infinity)
                .frame(width: 100, height: 150)
                .aspectRatio(contentMode: .fit)
                .shadow(color:Color.accentColor, radius: 10)
        }
        VStack (alignment:.leading){
            Text(itemModel.book.name)
            
            HStack (alignment: .center){
                ProgressView(value: progress, total: 100.0)
                Text("\(String(format: "%.0f", progress)) %")
                    .font(.system(size: 13))
                    .fontWeight(.medium)
            }.padding(.horizontal, 3)
            
            Button {
                showingAlert.toggle()
            } label: {
                Text("Update Progress")
            }
            .buttonStyle(ProgressButtonModifier())

        }
    }
    .alert(itemModel.book.name, isPresented: $showingAlert) {
        TextField("Page read", text: $pageRead)
            .modifier(TextFieldModifier())
            .keyboardType(.numberPad)
        Button("Submit", action: submit)
    } message: {
        Text("what is your current page ?")
    }
    
    .toast(isPresenting: $showError){
        AlertToast(displayMode: .banner(.pop), type: .error(Color.accentColor), title: "Current page should not be more than total number of pages")
    }
    
    .padding()
    .onAppear(perform: {
        progress = getPercentage()
    })
    .modelContainer(for: [SDBook.self])
   
}

func getPercentage() -> Double{
    return Double((100 * itemModel.book.currentPage) / itemModel.book.numberOfPages)
}

func submit() {
    print("You entered \(pageRead)")
    if pageRead.toInt() > itemModel.book.numberOfPages {
        showError.toggle()
    }else{
        itemModel.book.currentPage = pageRead.toInt()
        showingAlert.toggle()

    }
}

}

2
  • You don't need to wrap your model in an observable class since it is already conforming to Observable Commented Jun 28, 2024 at 7:25
  • Remove ReadingRowView's init and you should be good Commented Jun 28, 2024 at 13:18

1 Answer 1

1

Some issues in your code

  • As mentioned your model object already conforms to Observable so you can drop the ReadingBooksModel class and work directly with the SDBook type. So in ReadingRowView replace itemModel declaration with

    var book: SDBook

  • Of course now you need to go through the view and change any references to itemModel to book instead

  • You are creating another ModelContainer in ReadingRowView, you should never do that and only work with a single container so remove

    .modelContainer(for: [SDBook.self])

  • Since you are working with local @State properties in your view you need to update the progress when the number of read pages changes, you could use an .onChange modifier or as I did add a call in the submit function after the book was updated

    progress = getPercentage()

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.