Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
UISwitch (or Toggle) recreated using SwiftUI with a more playful animation.
struct MyToggle: View {
@Binding var isOn: Bool
private enum SwitchState {
case notPressed
case pressed(percent: CGFloat)
}
@State private var state: SwitchState = .notPressed
@State private var startPercent: CGFloat?
@State private var pressed = false
private var percent: CGFloat {
if case let .pressed(percent) = state {
return percent
}
return isOn ? 1 : 0
}
var body: some View {
GeometryReader { geo in
HStack {
Circle()
.padding(2)
.frame(width: geo.size.height, height: geo.size.height)
.foregroundColor(.white)
.shadow(radius: 2)
.scaleEffect(self.pressed ? 0.8 : 1)
.alignmentGuide(HorizontalAlignment.leading, computeValue: { d in
d[.leading] - ((geo.size.width - d.width) * self.percent)
})
}
.frame(maxWidth: .infinity, alignment: .leading)
.background(self.percent > 0.5 ? Color.green : Color(white: 0.9))
.clipShape(RoundedRectangle(cornerRadius: geo.size.width / 2))
.gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local).onChanged { change in
if self.startPercent == nil { self.startPercent = self.percent }
let value = change.translation.width / (geo.size.width - geo.size.height)
self.state = .pressed(percent: min(max((self.startPercent ?? 0) + value, 0), 1))
withAnimation(Animation.easeIn(duration: 0.05)) {
self.pressed = true
}
}.onEnded { _ in
if abs(self.percent.distance(to: self.startPercent ?? 0)) < 0.1 {
self.isOn.toggle()
}
else {
self.isOn = self.percent > 0.5
}
withAnimation(Animation.easeOut(duration: 0.1)) {
self.startPercent = nil
self.state = .notPressed
}
withAnimation(Animation.spring(response: 0.12, dampingFraction: 0.23, blendDuration: 0)) {
self.pressed = false
}
})
}
.frame(width: 50, height: 30)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.