SwiftUI – A note about onAppear()
This morning I made a custom version of a picker for the events form. I needed a way for events to select a different timeline. The default picker in SwiftUI had some limitations so I set out to make my own. The only main difference is that it uses a sectioned list with timelines sorted into non-archived and archived sections. I made a binding variable to pass in the selected timeline. This allowed me to update the selected timeline from the picker view, then just close it when done.
struct TimelinePicker: View {
@Binding var selectedTimeline: Timeline?
@ObservedObject var dataSource = RADDataSource<Timeline>()
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body : some View {
List {
ForEach(dataSource.loadSectionedDataSource(sort: self.getSort(), predicate: nil, sectionKeyPathName: "isArchived"), id: \.name) { listSection in
Section(header:
HStack {
Text(listSection.name == "1" ? "Archived Timelines" : "Timelines")
}
.font(.system(.headline, design: .rounded))
) {
ForEach(self.dataSource.objects(inSection: listSection)) { timeline in
Button(action: ({
self.selectedTimeline = timeline
self.presentationMode.wrappedValue.dismiss()
})) {
HStack {
TimelineListRowView(timeline: timeline)
.foregroundColor(.primary)
Spacer()
if(self.selectedTimeline == timeline) {
Image(systemName: "checkmark")
}
}
}
}
}
}
}
}
public func getSort() -> [NSSortDescriptor] {
...
}
}
It just would not work though. I tried several ways of passing in the timeline, binding, observed object, environment object, etc. Nothing would work. Every time I closed the picker the initial value remained set on the events form.
Turns out that’s because I’m an idiot. I was using a call to onAppear to load data from the event record into a View Model class. This was running every time I closed the timeline picker. I would set the initial value, go to the picker, select a new value, and then close the picker which would call onAppear to set the initial value again.
The fix for this was super simple. I just added a new state variable called onAppearCalled and initialized it to false. Then I could check this condition in the function that populates my form when onAppear is called, setting it to true when it is called.
func populateOnAppear() {
if(self.onAppearCalled == false) {
...
self.onAppearCalled = true
}
}