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()
}
}
}
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()