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

How to convert EnvironmentObject property into Binding?

Forums > SwiftUI

Hallo, I am trying to write a slightly generic view for user to enter weights in different units (there is a textfield and picker for the units), but I am not able to pass in the data in correct format. Can you please advise on how to resolve this?

import SwiftUI

//Mock data object, real one has more properties
class PersonalData: ObservableObject
{
    @Published var weight: Measurement<UnitMass>?

    init(_ weight: Double)
    {
        self.weight = Measurement(value: weight, unit: .kilograms)
    }

}

//Mock data object, real one has more properties
class ToolData: ObservableObject
{
    @Published var payload: Measurement<UnitMass>?
}

// My "generic" view to handle user input of weight
struct  EntryFieldView: View
{
    private let kg = UnitMass.kilograms
    private let pounds = UnitMass.pounds
    private var nf: NumberFormatter
                                    {
                                        let result = NumberFormatter()
                                        result.maximumFractionDigits = 1
                                        result.numberStyle = .none
                                        return result
                                    }

    private var mf: MeasurementFormatter
                                    {
                                        let result = MeasurementFormatter()
                                        result.unitOptions = .providedUnit
                                        result.unitStyle = .medium
                                        return result
                                    }

    @Binding var mass: Measurement<UnitMass>?   //this seems to be the issue
    @State var currentUnit : UnitMass = (Locale.current.usesMetricSystem ? .kilograms : .pounds)

    private var valueBinding: Binding<Double>
    {
        Binding(
            get:
                {
                    mass!.value
                },
            set:
                { newVal in
                    mass!.value = newVal
                })
    }

    var entry: some View
    {
        VStack{

            if self.mass == nil
            {
                Button("+"){
                    mass = Measurement(value: 0, unit: currentUnit)
                }
            }
            else
            {
                TextField("Weight", value: valueBinding, formatter: nf)
            }
        }
    }

    var unitPicker: some View
    {
        VStack{
            Picker("\(mf.string(from: currentUnit))", selection: $currentUnit){
                Text(mf.string(from: kg)).tag(kg)
                Text(mf.string(from: pounds)).tag(pounds)
            }.onChange(of: currentUnit, perform: { value in
                mass?.convert(to: currentUnit)
            }).pickerStyle(SegmentedPickerStyle())
            .disabled(mass == nil)

        }
    }

    var body: some View
    {
        HStack
        {
            entry
            unitPicker
        }
    }
}

struct MasterView: View
{
    @EnvironmentObject var person: PersonalData
    @EnvironmentObject var tool : ToolData

    var body: some View
    {
        NavigationView
        {
            VStack
            {
                NavigationLink(
                    destination: EntryFieldView(mass: Binding(person.weight), currentUnit: .kilograms), //+++++ HERE I GET ERROR
                    label: {

                        Text("Person weight: \(person.weight?.description ?? "none")")
                    })
                NavigationLink(
                    destination: EntryFieldView(mass: Binding(tool.payload), currentUnit: .kilograms), //+++++ HERE I GET ERROR
                    label: {
                        Text("Tool payload: \(tool.payload?.description ?? "none")")
                    })

            }.navigationTitle("Master")
        }
    }
}

struct MasterView_Previews: PreviewProvider {
    static var previews: some View {

        MasterView()
            .environmentObject(PersonalData(80))
            .environmentObject(ToolData())

    }
}

The error I get is: "cannot convert value of type 'Measurement<UnitMass>?' to expected argument type 'Binding<Measurement<UnitMass>>'"

thank you for any help, I am sure there has to be an elegant way how to do this but I could not find one.

3      

You can't just pass a value directly into the Binding initialiser to create one. To access the two-way binding value from an @EnvironmentObject property (in your case), you need to use the $ syntax like this:

EntryFieldView(mass: $person.weight, currentUnit: .kilograms)

Check out this page regarding the error message, and this page explaining two-way bindings.

3      

@njansari thank you for the answer, however it does not work either.

The error message is now

cannot convert value of type 'Binding<Published<Measurement<UnitMass>?>.Publisher>' to expected argument type 'Binding<Measurement<UnitMass>?>'

3      

You must have changed something else in your code because when I run your original code with the changes I suggested, everything compiles and I do not see you new error message.

Having looked at your new error message, though, it seems you may have wrapped one of your measurement properties in a Published struct. Is this the case, or is there something else?

3      

Sorry, you are probably right. I was experimenting a lot... I have resolved the issue now, thank you!

3      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

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.