-2

This is my code:

import SwiftUI

struct VPickerView<T: Hashable>: View {
    private let elements: [T]
    private let selection: Binding<T>
    
    init(elements: [T], selection: Binding<T>) {
        self.elements = elements
        self.selection = selection
    }
    
    var body: some View {
        HStack(spacing: 5) {
            ZStack {
                Color.green
                    .frame(width: 45, height: 30)
                    .cornerRadius(15)
                Picker("", selection: $selection) { // Cannot find '$selection' in scope
                    ForEach(elements, id: \.self) {
                        Text(String($0))
                            .font(.title)
                            .foregroundColor($0 == selection as! T ? .blue : .red)
                    }
                }.onChange(of: selection) { _ in
                    print("changed")
                }
                .labelsHidden()
                .pickerStyle(.wheel)
                .border(.black, width: 5)
                .frame(width: 55)
            }
        }
    }
}

But I receive an error Cannot find '$selection' in scope and I don't know why.

How do I want to use it?

private let hours: [Int]
@State private var selectedHour: Int = 0

var body: some View {
    VPickerView(elements: hours, selection: $selectedHour)
}

Conditions:

  • selected element should be blue, otherwise red.
  • type of elements might be Int, Double, String, Int64
3
  • Does this answer your question? How to initialize a Binding : Bool variable in SwiftUI? Commented Mar 23, 2023 at 13:28
  • @loremipsum No, it is not the same, because there is defined type, and here we have generic type. That difference is the key here. Commented Mar 23, 2023 at 16:08
  • The issue is the missing wrapper, if you add it as normal and init like that question self._selection = selection it will work. To make it work now just remove the $ it is already a Binding you don't need it but you will have UI update issues, you need the @Binding Commented Mar 23, 2023 at 16:20

1 Answer 1

1

Remember, the SwiftUI's Binding property lets us declare one value actually comes from elsewhere and should be shared in both the one who shares the property and the one who is shared the property to. And, Binding property can be MODIFIED by both the sharer and the sharee (https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-binding-property-wrapper).

That being said, "private let selection: Binding" must be changed to "@Binding var selection: T" as the Binding property should be both VARIABLE and SHARABLE (not private).

Also, as Binding attribute (@Binding) wraps the Binding instance, you must access what's inside and initialize it, by doing the following.

init(elements: [T], selection: Binding<T>) {
    self.elements = elements
    self._selection = selection
}

The underbar from "_selection" allows us to reach INSIDE the attribute and set the binding property.

Once those are done, compiler will still not allow you to build the provided code as <T: Hashable> is not enough to let Swift know that $0 (first argument) is convertible to String.

To fix that, you might consider adding another protocol conformance such as CustomStringConvertible.

struct VPickerView<T: Hashable & CustomStringConvertible>: View {
    ...codes
}

So the full code might be like this:

struct VPickerView<T: Hashable & CustomStringConvertible>: View {
    private let elements: [T]
    @Binding private var selection: T
    
    init(elements: [T], selection: Binding<T>) {
        self.elements = elements
        self._selection = selection
    }
    
    var body: some View {
        HStack(spacing: 5) {
            ZStack {
                Color.green
                    .frame(width: 45, height: 30)
                    .cornerRadius(15)
                Picker("", selection: $selection) { // Cannot find '$selection' in scope
                    ForEach(elements, id: \.self) {
                        Text($0.description)
                            .font(.title)
                            .foregroundColor($0 == selection ? .blue : .red)
                    }
                }.onChange(of: selection) { _ in
                    print("changed")
                }
                .labelsHidden()
                .pickerStyle(.wheel)
                .border(.black, width: 5)
                .frame(width: 55)
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for your answer. It helped. I have one more issue related to this but this is topic for different question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.