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

Odd 'Use of unresolved identifier '$var - surely Swift has a easier way for getting a reference to a value

Forums > SwiftUI

Can anyone explain why this is not allowed...

Given the following class:

class CandidateAttribute  {
    var attributeType : AttributeType
    var name : String
    var value = ""

    init(attributeType: AttributeType) {
        self.attributeType = attributeType
        self.name = attributeType.wrappedName
        self.value = ""
    }
}

And a class that has a Dictionary of these objects:

class Wrapper {
      var candidates : [String: CandidateAttribute]()

      // other code

    func getCandidateAttribute(attributeType: AttributeType) -> CandidateAttribute{
        if let candidate = attributesDict[attributeType.wrappedName] {
            return candidate
        }

        let newCandidate = CandidateAttribute(attributeType: attributeType)
        attributesDict[attributeType.wrappedName] = newCandidate
        return newCandidate
    }
}

Why can't I bind to a CandidateAttribute.value in the following code:

    private func addAttributeRow(attributeType: AttributeType) -> some View {
        let candidateAttr = candidateItem.getCandidateAttribute(attributeType: attributeType)
        return HLabelTextField(label: candidateAttr.name,
                         hint: "Set value",
                         value: $candidateAttr.value) //candidateAttr.value)
    }

With the code

        return HLabelTextField(label: candidateAttr.name,
                         hint: "Set value",
                         value: candidateAttr.value)

I get the error Cannot convert value of type 'String' to expected argument type 'Binding<String>'

With the code:

        return HLabelTextField(label: candidateAttr.name,
                         hint: "Set value",
                         value: $candidateAttr.value)

I get the error Use of unresolved identifier '$candidateAttr'

Is this down to:

  1. a bug in my code?
  2. a language limitation (Binding to objects stored in arrays is not allowed)?
  3. Something else (e.g. I'm holding it wrong)?

If the reason is 2 that seems like a pretty poor language feature to me. The more I use SwiftUI the more I find it requires a lot of work-arounds because things don't 'just work'

Note: I have also trie making CandidateAttribute a struct as well...

As a workaround I can modify CandidateAttribute and add the following function:

    func getValueBinding() -> Binding<String> {

        let binding = Binding<String>(get: { () -> String in
            return self.value
        }) { (newValue) in
            // This func updates the database
            self.value = newValue
        }
        return binding

    }

and change the referencing code to:

    private func addAttributeRow(attributeType: AttributeType) -> some View {
        let candidateAttr = candidateItem.getCandidateAttribute(attributeType: attributeType)
        return HLabelTextField(label: candidateAttr.name,
                         hint: "Set value",
                         value: candidateAttr.getValueBinding()) //candidateAttr.value)
    }

But that feels like way too much code to simply reference a value.

3      

Not at all difficult. At a basic level, the $ sign (when used for bindings) in SwiftUI in regards to Bindings is only used for @State and @Binding property wrappers which are declared at the top of the view you are in. Because you are using classes what you want to do is make your CandidateAttribute class and Wrapper class conform to ObservableObject -

class CandidateAttribute: ObservableObject

class Wrapper: ObservableObject

Now because the TextField requires the Binding<String> argument you want to declare that particular variable in your class with the property wrapper @Published -

@Published var value = ""

Your basically saying that I want this value to publish or notify any view using this value to that it has changed and furthermore allow that view to update based on the new value.

Now because this variable is part of the CandidateAttribute class which in turn is a value inside the dictionary in the Wrapper class, is why we need to make sure that class conforms to ObservableObject as well. Now this is done you declare that dictionary property in the Wrapper class with the @Published property wrapper -

@Published var candidates: [String : CandidateAttribute]

Finally in your view where you have declared properties for the view. I assume you have created a variable of type Wrapper? If you have you now have to decalare that as follows:

@ObservedObject var wrapper: Wrapper // however you want to declare it but make sure it has @ObservedObject

You are saying that this property is an Observed Object and i want to be notified of any @Published properties from that class. I am assuming that private function is part of the view you are using this on? If so then just get rid of the $ sign now at the front of candidateAttr.value and you should be good to go.

Its worth doing a bit of reading about the different property wrappers for SwiftUI. They are so handy and simple to use once you know which one to use for what situation.

Note: I have used a few assumptions about how you have your classes and code set up in doing this so i hope it works for your situation.

Dave

3      

Thanks for your response but it does not work - it is one of the various 'workarounds' I had already tried before settling on the 'getValueBinding()' function which solves the issue even though it is ugly.

The code is as follows:

class CandidateAttributeValue : ObservableObject {
    var attributeType : AttributeType
    var name : String
    @Published var textValue = ""

    init(attributeType: AttributeType) {
        self.attributeType = attributeType
        self.name = attributeType.wrappedName
        self.textValue = ""
    }

    func getValueBinding() -> Binding<String> {

         let binding = Binding<String>(get: { () -> String in
             return self.textValue
         }) { (newValue) in
             // This func updates the database
             self.textValue = newValue
         }
         return binding

    }
}

The class CandidateItem stores a number of CandidateAttributeValue objects in a dictionary:

class CandidateItem : ObservableObject {
    @Published var attributesDict = [String:CandidateAttributeValue]()
}

Elsewhere in the program I want to show a TextField that allows the user to set the values of athef CandidateAttributeValue objects store din the attributes dictionary:

struct AddItemAttributesView: View {
    @ObservedObject var candidateItem : CandidateItem

    // other code...
      private func addAttributeRow(attributeType: AttributeType) ->  some View {
        let candidate = candidateItem.getCandidateAttribute(attributeType: attributeType)
        return   VStack (alignment: .leading, spacing: 0) {
            Text(candidate.name)
                .labelTextSyle()
            TextField("Set value", text: candidate.textValue)
                .padding(.all, 10)
                .textFieldStyle(RoundedBorderTextFieldStyle())
        }
    }
}

The problem line is 'TextField("Set value", text: candidate.textValue)' as there appears to be no way to convert a @Published var textValue : String to a Binding<String> without the getBindingValue() workaround from above. I am not sure why a language would be designed this way or whether this is yet another shortfall with Swift which will be fixed in a future release.

3      

Sorry about that. I thought it would of worked. Have you tried just using a binding variable just for the TextField? Then when you need to update the name property in your class you just save whatever is in the binding to that property?

@State private var name = ""

// some other code

TextField("Enter name", text: $name)

// some other code

candidate.textValue = name

Might be better doing it this way.

Dave

3      

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.