-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathSegmentedControlImproved.swift
More file actions
145 lines (128 loc) Β· 4.29 KB
/
SegmentedControlImproved.swift
File metadata and controls
145 lines (128 loc) Β· 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//
// SegmentedControlImproved.swift
// CodeEdit
//
// Created by Wouter Hennen on 22/05/2023.
//
import SwiftUI
extension ButtonStyle where Self == XcodeButtonStyle {
static func xcodeButton(
isActive: Bool,
prominent: Bool,
isHovering: Bool,
namespace: Namespace.ID = Namespace().wrappedValue
) -> XcodeButtonStyle {
XcodeButtonStyle(isActive: isActive, prominent: prominent, isHovering: isHovering, namespace: namespace)
}
}
struct XcodeButtonStyle: ButtonStyle {
var isActive: Bool
var prominent: Bool
var isHovering: Bool
var namespace: Namespace.ID
@Environment(\.controlSize)
var controlSize
@Environment(\.colorScheme)
var colorScheme
@Environment(\.controlActiveState)
private var activeState
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding(.horizontal, controlSizePadding.horizontal)
.padding(.vertical, controlSizePadding.vertical)
.font(fontSize)
.foregroundColor(isActive ? .white : .primary)
.opacity(textOpacity)
.background {
if isActive {
RoundedRectangle(cornerRadius: 5)
.foregroundColor(.accentColor)
.opacity(configuration.isPressed ? (prominent ? 0.75 : 0.5) : (prominent ? 1 : 0.75))
.matchedGeometryEffect(id: "xcodebuttonbackground", in: namespace)
} else if isHovering {
RoundedRectangle(cornerRadius: 5)
.foregroundColor(.gray)
.opacity(0.2)
.transition(.opacity)
.animation(.easeInOut, value: isHovering)
}
}
.opacity(activeState == .inactive ? 0.6 : 1)
.animation(.interpolatingSpring(stiffness: 600, damping: 50), value: isActive)
}
var fontSize: Font {
switch controlSize {
case .mini:
return .footnote
case .small, .regular:
return .subheadline
default:
return .callout
}
}
var controlSizePadding: (vertical: CGFloat, horizontal: CGFloat) {
switch controlSize {
case .mini:
return (1, 2)
case .small:
return (2, 4)
case .regular:
return (3, 8)
case .large:
return (6, 12)
default:
return (4, 8)
}
}
private var textOpacity: Double {
if prominent {
return activeState != .inactive ? 1 : isActive ? 1 : 0.3
} else {
return activeState != .inactive ? 1 : isActive ? 0.5 : 0.3
}
}
}
private struct MyTag: _ViewTraitKey {
static var defaultValue: AnyHashable? = Optional<Int>.none
}
extension View {
func segmentedTag<Value: Hashable>(_ value: Value) -> some View {
_trait(MyTag.self, value)
}
}
struct SegmentedControlV2<Selection: Hashable, Content: View>: View {
@Binding var selection: Selection
var prominent: Bool
@ViewBuilder var content: Content
@State private var hoveringOver: Selection?
@Namespace var namespace
var body: some View {
content.variadic { children in
HStack(spacing: 8) {
ForEach(children, id: \.id) { option in
let tag: Selection? = option[MyTag.self].flatMap { $0 as? Selection }
Button {
hoveringOver = nil
if let tag {
selection = tag
}
} label: {
option
}
.buttonStyle(
.xcodeButton(
isActive: tag == selection,
prominent: prominent,
isHovering: tag == hoveringOver,
namespace: namespace
)
)
.onHover { hover in
hoveringOver = hover ? tag : nil
}
.animation(.interpolatingSpring(stiffness: 600, damping: 50), value: selection)
}
}
}
}
}