Last active
March 28, 2024 06:49
-
-
Save swiftui-lab/4469338fd099285aed2d1fd00f5da745 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SwiftUI Custom Styles (TripleToggleStyle) | |
// https://swiftui-lab.com | |
// https://swiftui-lab.com/custom-styling | |
import SwiftUI | |
// MARK: - TripleToggle View | |
public struct TripleToggle: View { | |
@Environment(\.tripleToggleStyle) var style: AnyTripleToggleStyle | |
let label: Text | |
@Binding var tripleState: TripleState | |
public var body: some View { | |
let configuration = TripleToggleStyleConfiguration(tripleState: self._tripleState, label: label) | |
return style.makeBody(configuration: configuration) | |
} | |
} | |
// MARK: - Custom Environment Key | |
extension EnvironmentValues { | |
var tripleToggleStyle: AnyTripleToggleStyle { | |
get { | |
return self[TripleToggleKey.self] | |
} | |
set { | |
self[TripleToggleKey.self] = newValue | |
} | |
} | |
} | |
public struct TripleToggleKey: EnvironmentKey { | |
public static let defaultValue: AnyTripleToggleStyle = AnyTripleToggleStyle(DefaultTripleToggleStyle()) | |
} | |
// MARK: - View Extension | |
extension View { | |
public func tripleToggleStyle<S>(_ style: S) -> some View where S : TripleToggleStyle { | |
self.environment(\.tripleToggleStyle, AnyTripleToggleStyle(style)) | |
} | |
} | |
// MARK: - Type Erased TripleToggleStyle | |
public struct AnyTripleToggleStyle: TripleToggleStyle { | |
private let _makeBody: (TripleToggleStyle.Configuration) -> AnyView | |
init<ST: TripleToggleStyle>(_ style: ST) { | |
self._makeBody = style.makeBodyTypeErased | |
} | |
public func makeBody(configuration: TripleToggleStyle.Configuration) -> AnyView { | |
return self._makeBody(configuration) | |
} | |
} | |
// MARK: - TripleToggleStyle Protocol | |
public protocol TripleToggleStyle { | |
associatedtype Body : View | |
func makeBody(configuration: Self.Configuration) -> Self.Body | |
typealias Configuration = TripleToggleStyleConfiguration | |
} | |
extension TripleToggleStyle { | |
func makeBodyTypeErased(configuration: Self.Configuration) -> AnyView { | |
AnyView(self.makeBody(configuration: configuration)) | |
} | |
} | |
public struct TripleToggleStyleConfiguration { | |
@Binding var tripleState: TripleState | |
var label: Text | |
} | |
public enum TripleState: Int { | |
case low | |
case med | |
case high | |
} | |
// MARK: - DefaultTripleToggleStyle | |
public struct DefaultTripleToggleStyle: TripleToggleStyle { | |
public func makeBody(configuration: Self.Configuration) -> DefaultTripleToggleStyle.DefaultTripleToggle { | |
DefaultTripleToggle(state: configuration.$tripleState, label: configuration.label) | |
} | |
public struct DefaultTripleToggle: View { | |
let width: CGFloat = 50 | |
@Binding var state: TripleState | |
var label: Text | |
var stateAlignment: Alignment { | |
switch self.state { | |
case .low: return .leading | |
case .med: return .center | |
case .high: return .trailing | |
} | |
} | |
var stateColor: Color { | |
switch self.state { | |
case .low: return .green | |
case .med: return .yellow | |
case .high: return .red | |
} | |
} | |
public var body: some View { | |
VStack(spacing: 10) { | |
label | |
ZStack(alignment: self.stateAlignment) { | |
RoundedRectangle(cornerRadius: 4) | |
.frame(width: self.width, height: self.width / 2) | |
.foregroundColor(self.stateColor) | |
RoundedRectangle(cornerRadius: 4) | |
.frame(width: (self.width / 2) - 4, height: self.width / 2 - 6) | |
.padding(4) | |
.foregroundColor(.white) | |
.onTapGesture { | |
withAnimation { | |
switch self.state { | |
case .low: | |
self.$state.wrappedValue = .med | |
case .med: | |
self.$state.wrappedValue = .high | |
case .high: | |
self.$state.wrappedValue = .low | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
// MARK: - KnobTripleToggleStyle | |
public struct KnobTripleToggleStyle: TripleToggleStyle { | |
let dotColor: Color | |
public func makeBody(configuration: Self.Configuration) -> KnobTripleToggleStyle.KnobTripleToggle { | |
KnobTripleToggle(dotColor: dotColor, state: configuration.$tripleState, label: configuration.label) | |
} | |
public struct KnobTripleToggle: View { | |
let dotColor: Color | |
@Binding var state: TripleState | |
var label: Text | |
var angle: Angle { | |
switch self.state { | |
case .low: return Angle(degrees: -30) | |
case .med: return Angle(degrees: 0) | |
case .high: return Angle(degrees: 30) | |
} | |
} | |
public var body: some View { | |
let g = Gradient(colors: [.white, .gray, .white, .gray, .white, .gray, .white]) | |
let knobGradient = AngularGradient(gradient: g, center: .center) | |
return VStack(spacing: 10) { | |
label | |
ZStack { | |
Circle() | |
.fill(knobGradient) | |
DotShape() | |
.fill(self.dotColor) | |
.rotationEffect(self.angle) | |
}.frame(width: 150, height: 150) | |
.onTapGesture { | |
withAnimation { | |
switch self.state { | |
case .low: | |
self.$state.wrappedValue = .med | |
case .med: | |
self.$state.wrappedValue = .high | |
case .high: | |
self.$state.wrappedValue = .low | |
} | |
} | |
} | |
} | |
} | |
} | |
struct DotShape: Shape { | |
func path(in rect: CGRect) -> Path { | |
return Path(ellipseIn: CGRect(x: rect.width / 2 - 8, y: 8, width: 16, height: 16)) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment