Convert Value on Realm Property When Retrieved and Saved in SwiftUI

Viewed 102

I have a Flight Realm class with a duration property that is an Int. It stores the duration of a flight as the number of minutes.

class Flight: Object, ObjectKeyIdentifiable{
  @objc dynamic var duration = 0
}

I pass a Flight object as an ObservedRealmObject to a child view where it's edited. The user can enter the time as HHMM (130 = 1 hr 30 min) or #.# (1.5 hrs). I have some extensions that convert either of those times from a String to the number of minutes stored as an Int and back again.

But the problem I'm having is binding my TextField. I can't bind to the duration property directly because I need to convert it from the time format to the number of minutes.

struct MyView: View {
  @ObservedRealmObject var flight: Flight
  @AppStorage("timeDisplayHHMM") var timeDisplayHHMM = false
  
  var body: some View{
    TextField(timeDisplayHHMM ? "HHMM": "#.#", text: $flight.duration)
  }
}

I've tried a couple different approaches that don't quite work right.

Attempt 1

I created a @State variable which I set in .onAppear and then I save the value inside .onChange like this:

@State var duration = ""

TextField(...)
  .onChange(of: duration, perform: { value in
    //Save duration using my view model
    model.saveDuration(flight: $flight, duration: duration)
  })
  .onAppear(){
    //Get value
    duration = flight.duration.timeDisplay //"timeDisplay" is my conversion Int extension.
  }

This works great, but it relies on onAppear to keep its value updated. So if the duration changes in my Realm, the UI won't update unless the user causes the view to appear again.

Attempt 2

I've tried creating a custom binding and I set the binding's value from the Realm property, and then save it in the setter. This behaves erratically as the user types because the value is saved and updated before they can finish typing.

For example, if they type 1. then it quickly saves it as 1 hour and then the value updates to 1.0 before they can finish typing 1.5.

private var durationBinding: Binding<String> {
  Binding<String>(get: {
    //Set binding value
    flight.duration.timeDisplay
  }, set: {
    //Save
    model.saveDuration(flight: $flight, duration: $0)
  })
}

How can I get the benefits of binding to a String Realm property while still having the value convert to/from an Int?

0 Answers
Related