0

I created a multi wheel picker in swiftUI and I'm my use case the user will set time values utilizing the picker.

I have created the picker and everything works as expected however I am not able to access the computed value which return the user selected time as a Sting. I know I should use @Binding however I have been banging my head against the wall for hours on this.

View for each individual column

struct HoursPickerColumn : View {
let range : ClosedRange<Int>
@Binding var selection : Int

var body: some View {
    Picker("" ,selection: $selection) {
        ForEach(range, id: \.self) { value in
            Text("\(value)")
            
        }
    }.pickerStyle(.wheel)
    .labelsHidden()
    .frame(width: 50,height: 80)
    .clipped()
} 
} 

Hours Picker View code

struct HoursPickerView: View {

@State private var hoursFirstDigit = 0
@State private var hoursSecondDigit = 0
@State private var hoursThirdDigit = 0
@State private var hoursForthDigit = 0
@State private var hoursLastDigit = 0

@State private var minutesFirstDigit = 0
@State private var minutesLastDigit = 0

@Binding var time : String


private var selectedTimeAsString : String {
    let hours = [hoursFirstDigit,hoursSecondDigit,hoursThirdDigit,hoursForthDigit,hoursLastDigit].map{"\($0)"}
    let minutes = [minutesFirstDigit,minutesLastDigit].map{"\($0)"}
    
    let hoursJoined = hours.joined()
    let minutesJoined = minutes.joined()
    
    let stringValue = hoursJoined + ":" + minutesJoined
    
    return stringValue
}


var body: some View {

    
    VStack {
        HStack(spacing : -15){
            
            HoursPickerColumn(range: 0...9, selection: $hoursFirstDigit)
            
            HoursPickerColumn(range: 0...9, selection: $hoursSecondDigit)
            
            HoursPickerColumn(range: 0...9, selection: $hoursThirdDigit)
            
            HoursPickerColumn(range: 0...9, selection: $hoursForthDigit)
            
            HoursPickerColumn(range: 0...9, selection: $hoursLastDigit)
            
            Text(":")
                .font(.title)
                .padding(.horizontal)
            
            HoursPickerColumn(range: 0...5, selection: $minutesFirstDigit)
            
            HoursPickerColumn(range: 0...9, selection: $minutesLastDigit)
            
        }.frame(width: 280, height: 50)
            .clipped()
            .background(
                RoundedRectangle(cornerRadius: 10)
                    .foregroundStyle(.white.shadow(.inner(color: .gray, radius: 1)))
            )
        
        Text(selectedTimeAsString).bold()
    }
}
}

The above code yields the following

Preview of code

I need to access the string value from the containerView or any other containing view. I considered creating a TimeValue model with two properties var hours : Int and var minutes : Int and Have a @Binding property inside the HoursPickerView and let the model object handle splitting the Int into individual digits then binding those values to the individual columns. But all the above seems like overkill just to retrieve a value that I already have.

Any guidance or ideas would be highly appreciated.

OverD
  • 2,612
  • 2
  • 14
  • 29
  • Turn all the variables in `HoursPickerView` into a custom `Binding` manually `get` and `set` to create the two way communication. – lorem ipsum Mar 12 '23 at 13:02
  • I don't believe that helps, as I only want to get the computed value as a string. I tried adding @Binding var time value : String but I am stuck with trying to attaching the binding value to the computed value – OverD Mar 12 '23 at 14:06

1 Answers1

1

The complexity comes with the fact that with a Binding, you need two-way communication. So, not only do you need to know how to turn the result from the picker into a String, you have to be able to parse String input for it as well.

This may not be the most efficient way of doing a couple of these steps, but it works as a proof-of-concept to get you started. I've borrowed from a couple of Stack Overflow answers (that I've linked to inline as well):

Note that these techniques, as used in this answer have no protection against things like a different length of String being used as the input, invalid input from the individual pickers, etc. Those will be up to you to implement.

struct ContentView: View {
    @State private var time = "12345:12"
    
    var body: some View {
        HoursPickerView(time: $time)
    }
}

struct HoursPickerColumn : View {
    let range : ClosedRange<Int>
    @Binding var selection : Int
    
    var body: some View {
        Picker("" ,selection: $selection) {
            ForEach(range, id: \.self) { value in
                Text("\(value)")
            }
        }.pickerStyle(.wheel)
            .labelsHidden()
            .frame(width: 50,height: 80)
            .clipped()
    }
}

struct HoursPickerView: View {
    
    @Binding var time: String
    
    func bindingForCharacterAt(index: Int) -> Binding<Int> {
        Binding<Int>(
            get: {
                // https://stackoverflow.com/a/24178255/560942
                Int("\(Array(time)[index])") ?? 0
            },
            set: { newValue in
                // https://stackoverflow.com/a/47964294/560942
                let character = (0...9).map{ Character(String($0)) }[newValue]
                var charArray = Array(time)
                charArray[index] = character
                print("Setting time to: ", String(charArray), character, newValue)
                time = String(charArray)
            })
    }
    
    var body: some View {
        
        
        VStack {
            HStack(spacing : -15){

                HoursPickerColumn(range: 0...9, selection: bindingForCharacterAt(index: 0))

                HoursPickerColumn(range: 0...9, selection: bindingForCharacterAt(index: 1))

                HoursPickerColumn(range: 0...9, selection: bindingForCharacterAt(index: 2))

                HoursPickerColumn(range: 0...9, selection: bindingForCharacterAt(index: 3))

                HoursPickerColumn(range: 0...9, selection: bindingForCharacterAt(index: 4))
                
                Text(":")
                    .font(.title)
                    .padding(.horizontal)
                
                HoursPickerColumn(range: 0...5, selection: bindingForCharacterAt(index: 6))

                HoursPickerColumn(range: 0...9, selection: bindingForCharacterAt(index: 7))
                
            }.frame(width: 280, height: 50)
                .clipped()
                .background(
                    RoundedRectangle(cornerRadius: 10)
                        .foregroundStyle(.white.shadow(.inner(color: .gray, radius: 1)))
                )
            
            Text(time).bold()
        }
    }
}
jnpdx
  • 45,847
  • 6
  • 64
  • 94