UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Communication with UIViewRepresentable / UITextField

Forums > SwiftUI

Another raw beginner's question, I'm afraid.

I'm writing a calculator which needs an age to be entered. I want to be able to do it by entering the age itself or by entering a date of birth (DoB) from which I can calculate the age. To make entering the DoB easy, I've stol, er, adapted the very helpful code from here: https://diamantidis.github.io/2020/06/21/keyboard-options-for-swiftui-fields.

It works reasonably well. If I change the DoB using the keyboard, I can read it and update the age. If I then tap again on the DoB field, the picker appears with the chosen DoB shown.

But if I enter an age, then calculate the DoB from it, the DoB display changes as I'd expect, but when I tap on the DoB field, the picker appears set to whatever date was previously chosen in the picker, not the DoB which is seen in the display.

I'm obviously missing some step, which is passing the newly-calculated DoB down into the picker. I've tried various things but all have failed. I'm sure it must be easy.

Help! Thanks.

Jeremy

2      

I am guessing that you are using @State variables for the DoB and age inputs.

Difficult to see how to help, without seeing your code here, but when you enter the age, do you use it to update the @State variable DoB to corresponding to it?

2      

Fair point. The material bit of my code is pretty simple. Within the ContentView struct are

@State private var dob = Date()
@State private var ageText = ""
@State private var age = 0.0

...

                HStack {
                    Text("birth date")
                    DateField("dob", date: $dob)

                    Text("Age:")
                    TextField("", text: $ageText) { startingEdit in
                        if startingEdit { ageText = "" } else { calcDoB() }
                    }
                    .keyboardType(.decimalPad)
                }

...

func calcDoB() {
    age = Double(ageText) ?? 0
    let cal = Calendar.current
    age *= 365.25
    dob = cal.date(byAdding: .day, value: -Int(age.rounded()), to: Date())!
}

When calcDoB is called, it correctly calculates the date of birth from the entered age and the DateField changes to show the newly-calculated date of birth. The issue is that tapping the DateField brings up a picker which is set to whatever date was last entered using the picker, not the calculated (and displayed) dob.

Jeremy

2      

Couple of changes for your code. First you need to update the ageText when the calendar date changes, using onChange, but there is a technique for applying it ro @State variables. You need to add an extension to Binding. Extensions are applied at the file level, so not within the struct.

extension Binding {
    func onChange(_ handler: @escaping () -> Void) -> Binding<Value> {
        Binding(
            get: { self.wrappedValue },
            set: { newValue in
                self.wrappedValue = newValue
                handler()
            }
        )
    }
}

In my case I had to change the definition of dob to be as below, but you may not have to, as it was throwing an error with DateField during compilation.

@State private var dob = Date?.init(Date())

Add this function into the main struct

func calcAge() {
    let diff = Calendar.current.dateComponents([.year], from: dob!, to: Date())
    ageText = String(Double(diff.year ?? 0))
}

If this line fails during running

let diff = Calendar.current.dateComponents([.year], from: dob!, to: Date())

then change it to this

let diff = Calendar.current.dateComponents([.year], from: dob ?? Date(), to: Date())

And finally use it like this, by changing the DateField operation.

DateField("dob", date: $dob.onChange(calcAge))

2      

Thanks for the help, but it doesn't work.

The issue wasn't with updating ageText when changing the dob property: that was OK (I had a function calcAge, which was functionally identical to the one you've written, but I hadn't included it as I didn't think it mattered).

The issue is the reverse: when the dob property is changed programmatically, in calcDoB(), the display of dob changes as expected; but when you then tap on the dob field, the date keyboard which appears has the last value set using the date keyboard, not the value set by dob.

Try this:

  • tap dob. use the date entry keyboard to set the dob to something like 5th May 2005. ageText will update, as you've observed.
  • tap ageText. set age to 40. the dob field will change to show 31st May 1981
  • tap dob. the date entry keyboard will appear but will show 5th May 2005, not 31st May 1981

The issue is with passing the correct, new value of dob into the datePickerView computed property of DateTextField. For some reason, it doesn't happen.

Jeremy

2      

What is DateField? That's not a built-in SwiftUI View. Please show that code; the problem could lie in there somewhere.

2      

See the link in my original post.

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.