Search code examples
bindingswiftuicombine

How to translate bindings in a view?


I've created a view for setting a time (minutes and seconds). It uses two wheel pickers bound to two state variables.

Now I'd like to use that view at different places in the app, but I don't like the interface with two seperate variables for the time. Instead, I'd like to have just one bound variable holding the time in seconds (so time = 185 would translate to 3 minutes and 5 seconds).

Is it possible to have some sort of "adapter" between bindings?

Here's the view:

import SwiftUI

struct TimePicker: View {
    var minutes: Binding<Int>
    var seconds: Binding<Int>

    var body: some View {
        HStack() {
            Spacer()
            Picker(selection: minutes, label: EmptyView()) {
                ForEach((0...9), id: \.self) { ix in
                    Text("\(ix)").tag(ix)
                }
                }.pickerStyle(WheelPickerStyle()).frame(width: 50).clipped()
            Text("Min.")
            Picker(selection: seconds, label: EmptyView()) {
                ForEach((0...59), id: \.self) { ix in
                    Text("\(ix)").tag(ix)
                }
                }.pickerStyle(WheelPickerStyle()).frame(width: 50).clipped()
            Text("Sec.")
            Spacer()
        }
    }
}

Solution

  • Here is the approach based on the Binding(get:set:)

    struct TimePicker: View {
        @Binding var total: Int
        
        var minutes: Binding<Int> {
            Binding<Int>(get: { self._total.wrappedValue / 60 },
                         set: { self._total.wrappedValue = self._total.wrappedValue % 60 + $0 * 60 })
        }
    
        var seconds: Binding<Int> {
            Binding<Int>(get: { self._total.wrappedValue % 60 },
                         set: { self._total.wrappedValue = (self._total.wrappedValue / 60) * 60 + $0 })
        }
        
        var body: some View {
            HStack() {
                Spacer()
                Picker(selection: minutes, label: EmptyView()) {
                    ForEach((0...9), id: \.self) { ix in
                        Text("\(ix)").tag(ix)
                    }
                }.pickerStyle(WheelPickerStyle()).frame(width: 50).clipped()
                Text("Min.")
                Picker(selection: seconds, label: EmptyView()) {
                    ForEach((0...59), id: \.self) { ix in
                        Text("\(ix)").tag(ix)
                    }
                    }.pickerStyle(WheelPickerStyle()).frame(width: 50).clipped()
                Text("Sec.")
                Spacer()
            }.frame(height: 200)
        }
    }
    
    struct TestTimePicker: View {
        
        @State var seconds = 185
        var body: some View {
            VStack {
                Text("Current: \(seconds)")
                TimePicker(total: $seconds)
            }
        }
        
    }