Skip to content

Instantly share code, notes, and snippets.

@magnuskahr
Last active January 14, 2022 18:15
Show Gist options
  • Save magnuskahr/5dbe493a1b551db9afa2ab2e61c345eb to your computer and use it in GitHub Desktop.
Save magnuskahr/5dbe493a1b551db9afa2ab2e61c345eb to your computer and use it in GitHub Desktop.
A SwiftUI toggle to switch on/off a value for an optional state
struct ValueToggle<Value, Label: View>: View {
@Binding var value: Value?
let onValue: Value
let label: () -> Label
@State private var isOn = false
var body: some View {
Toggle(isOn: $isOn, label: label)
.onAppear {
isOn = (value != nil)
}
.onChange(of: isOn) {
value = $0 ? onValue : nil
}
}
}
extension ValueToggle where Label == Text {
internal init(value: Binding<Value?>, onValue: Value, label: String) {
self._value = value
self.onValue = onValue
self.label = { Text(label) }
}
}
@smic
Copy link

smic commented Jan 14, 2022

Wouldn't it be easier to write a custom binding like

extension Binding {
    public func withDefault<Wrapped>(_ defaultValue: Wrapped) -> Binding<Bool> where Wrapped? == Value {
        return Binding<Bool> {
            self.wrappedValue != nil
        } set: { newValue in
            self.wrappedValue = newValue ? defaultValue: nil
        }
      }
    }

Toogle("", isOn: myValue.withDefault(defaultValue))

@magnuskahr
Copy link
Author

Hey @smic, that is a great idea and totally valid as well!
However I think that ValueToggle reads better and conveys the meaning easier, but that is just me - thanks for your input!

@magnuskahr
Copy link
Author

.... though your suggestion did make me think; it could also be an extension on Toggle it self!

extension Toggle where Label == Text {
    public init<Value>(_ titleKey: LocalizedStringKey, for value: Binding<Value?>, default defaultValue: Value) {
        self.init(titleKey, isOn: Self.binding(value, default: defaultValue))
    }
    
    public init<Value, S: StringProtocol>(_ title: S, for value: Binding<Value?>, default defaultValue: Value) {
        self.init(title, isOn: Self.binding(value, default: defaultValue))
    }
}

extension Toggle {
    public init<Value>(for value: Binding<Value?>, default defaultValue: Value, @ViewBuilder label: () -> Label) {
        self.init(isOn: Self.binding(value, default: defaultValue), label: label)
    }
    
    private static func binding<Value>(_ value: Binding<Value?>, default defaultValue: Value) -> Binding<Bool> {
        Binding {
            value.wrappedValue != nil
        } set: { newValue in
            value.wrappedValue = newValue ? defaultValue: nil
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment