Skip to content

Instantly share code, notes, and snippets.

@satishVekariya
Last active May 10, 2023 07:45
Show Gist options
  • Save satishVekariya/74cb6423643d621bbf200b787c7db6d0 to your computer and use it in GitHub Desktop.
Save satishVekariya/74cb6423643d621bbf200b787c7db6d0 to your computer and use it in GitHub Desktop.
A custom toggle style
import SwiftUI
/// Custom toggle style
///
/// Reference:
/// - https://www.bigmountainstudio.com/community/public/posts/11825-swiftui-togglestyle-customizing-the-toggle
struct ColoredToggleStyle: ToggleStyle {
let labelFont = Font.system(size: 14, weight: .regular)
let labelColor = Color.secondaryMain
let onColor = Color.semanticsSuccessMain
let offColor = Color.terrainHue2
let thumbColor = Color.commonWhite
@State private var isDragging = false
func makeBody(configuration: Self.Configuration) -> some View {
HStack {
configuration.label // The text (or view) portion of the Toggle
.font(labelFont)
.foregroundColor(labelColor)
Spacer()
RoundedRectangle(cornerRadius: Const.switchCornerRadius, style: .circular)
.fill(configuration.isOn ? onColor : offColor)
.frame(width: Const.switchSize.width, height: Const.switchSize.height)
.overlay(thumbView(configuration))
.animation(Animation.easeInOut(duration: 0.2), value: configuration.isOn)
.gesture(dragAndTapGesture(configuration))
}
}
func dragAndTapGesture(_ config: Self.Configuration) -> some Gesture {
ExclusiveGesture(
DragGesture(minimumDistance: 1)
.onChanged { value in
isDragging = true
handleDrag(config, value: value)
}
.onEnded { value in
isDragging = false
handleDrag(config, value: value)
}
,
TapGesture()
.onEnded {
config.isOn.toggle()
}
)
}
private func thumbView(_ configuration: Self.Configuration) -> some View {
Circle()
.fill(thumbColor)
.shadow(radius: 1, x: 0, y: 1)
.padding(1.5)
.offset(x: configuration.isOn ? Const.thumbXOffset: -Const.thumbXOffset)
.scaleEffect(x: isDragging ? 1.1 : 1, anchor: configuration.isOn ? .trailing : .leading)
.animation(Animation.easeInOut(duration: 0.1), value: isDragging)
}
private func handleDrag(_ config: Self.Configuration, value: DragGesture.Value) {
guard value.translation.width != .zero else {
return
}
/// Bounded value
let value = min(max(value.translation.width, -Const.switchSize.width), Const.switchSize.width)
/// Translation progress within view's bounds
let progress = value/Const.switchSize.width
/// Check against the minimum progress
guard abs(progress) > 0.5 else {
return
}
/// Check direction
switch progress.sign {
case .plus: /// Dragging towards left (on)
/// Check switch is not already in on state
if config.isOn == false {
config.isOn.toggle()
}
case .minus: /// Dragging towards right (off)
/// Check switch is not already in off state
if config.isOn == true {
config.isOn.toggle()
}
}
}
enum Const {
static let switchSize = CGSize(width: 50, height: 29)
static let switchCornerRadius: CGFloat = 16
static let thumbXOffset: CGFloat = 10
}
}
public struct ToggleSwitch: View {
let isOn: Binding<Bool>
let text: String?
let axID: String
public init(isOn: Binding<Bool>, text: String? = nil, axID: String = "Toggle_switch") {
self.isOn = isOn
self.text = text
self.axID = axID
}
public var body: some View {
Toggle(isOn: isOn) {
if let text {
Text(text)
.font(.system(size: 14, weight: .regular))
.foregroundColor(.secondaryMain)
.accessibilityIdentifier(axID + "_label")
}
}
.toggleStyle(ColoredToggleStyle())
.accessibilityIdentifier(axID)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment