Here is an example:
struct DemoApp: View {
@State var viewModel = DemoAppViewModel()
var body: some View {
VStack {
DemoMonthView(date: viewModel.monthDate)
DemoDayView(date: viewModel.dayDate) // FIRST
.onTapGesture {
viewModel.dayDate = viewModel.dayDate.addingTimeInterval(86000)
}
DemoDayView(date: viewModel.monthDate) // SECOND
.onTapGesture {
viewModel.monthDate = viewModel.monthDate.addingTimeInterval(1400000)
}
}
}
}
@Observable
class DemoAppViewModel {
var dayDate: Date = Date()
var monthDate: Date = Date()
}
struct DemoMonthView: View {
var date: Date
@FetchRequest private var days: FetchedResults<Day> //you need to replace Day here with any Entity that will allow to reproduce the issue
init(date: Date) {
self.date = date
_days = FetchRequest(
sortDescriptors: [SortDescriptor(\.date, order: .reverse)],
predicate: NSPredicate(value: true)
)
print("DemoMonthView init is called") //should be called, but without body redraws
// heavy calculations for given month
}
var body: some View {
if #available(iOS 17.1, *) {
print("DemoMonthView body is called") //should not be called❓
}
return VStack {
Text(date.formatted(date: .long, time: .omitted)).font(.title.bold())
}
}
}
struct DemoDayView: View {
var date: Date
var body: some View {
Text(date.formatted(date: .long, time: .omitted))
}
}
#Preview {
DemoApp()
}
Simply, when you tap FIRST
button it should not redraw DemoMonthView, but it does. Why? I really need to avoid that by tapping every time FIRST
button. SECOND
button redraws DemoMonthView
view correctly, what I understand. But why the FIRST?
When I comment it out days
and _days
association in init, then everything is fine, it DOES NOT redraws...
But that situation is just a shortened problem of my real, more complicated app. There is a fetchRequest with heavy calculations which should not be called so frequently like tap on the button, like here in example, when tapping that button does not change anything related to DemoMonthView.
If it is the reason due to the lack of my knowledge, what should I know to avoid that?
Why it matters here? Because I need to update that DemoMonthView
ONLY when monthDate
changes, not each time when dayDate
changes.
In general, you can conform the view to Equatable
to prevent unwanted body
calls. If you only want body
to be called when date
changes, so you can write
extension DemoMonthView: Equatable {
static func ==(lhs: DemoMonthView, rhs: DemoMonthView) -> Bool {
lhs.date == rhs.date
}
}
It used to be the case that equatable()
also needs to be used, but now it seems this is no longer needed.