@@ -13,8 +13,46 @@ public final class PopUpButton: UIControl {
13
13
private var coverView : UIView ? {
14
14
willSet { coverView? . removeFromSuperview ( ) }
15
15
}
16
+ private var _selectedIndex : Int ? {
17
+ didSet {
18
+ guard oldValue != _selectedIndex else { return }
19
+ if window != nil {
20
+ if let old = oldValue {
21
+ let oldView = views [ old]
22
+ if !isPresented {
23
+ oldView. removeFromSuperview ( )
24
+ } else {
25
+ oldView. backgroundColor = itemsColor ?? backgroundColor
26
+ }
27
+ }
28
+ if let new = _selectedIndex {
29
+ let newView = views [ new]
30
+ if !isPresented {
31
+ addSubview ( newView)
32
+ }
33
+ newView. backgroundColor = selectedItemColor ?? tintColor
34
+ }
35
+ }
36
+ }
37
+ }
38
+ private var isPresented : Bool {
39
+ #if targetEnvironment(macCatalyst)
40
+ return isFirstResponder
41
+ #else
42
+ return isTracking
43
+ #endif
44
+ }
16
45
17
46
public override var canBecomeFirstResponder : Bool { true }
47
+ #if targetEnvironment(macCatalyst)
48
+ public override var frame : CGRect {
49
+ didSet {
50
+ if oldValue. origin != frame. origin, isPresented {
51
+ layoutViews ( from: convert ( bounds, to: coverView) , index: _selectedIndex ?? currentIndex)
52
+ }
53
+ }
54
+ }
55
+ #endif
18
56
19
57
public var itemsColor : UIColor ?
20
58
public var selectedItemColor : UIColor ?
@@ -33,22 +71,16 @@ public final class PopUpButton: UIControl {
33
71
}
34
72
public var currentIndex : Int = 0 {
35
73
didSet {
36
- guard oldValue != currentIndex else { return }
37
- if window != nil {
38
- let oldView = views [ oldValue]
39
- if !isTracking {
40
- oldView. removeFromSuperview ( )
41
- } else {
42
- oldView. backgroundColor = itemsColor ?? backgroundColor
43
- }
44
- let newView = views [ currentIndex]
45
- if !isTracking {
46
- addSubview ( newView)
47
- }
48
- newView. backgroundColor = selectedItemColor ?? tintColor
74
+ guard window != nil , views. count > 0 else { return }
75
+ let newView = views [ currentIndex]
76
+ if !isPresented {
77
+ views [ oldValue] . removeFromSuperview ( )
78
+ addSubview ( newView)
49
79
}
80
+ newView. backgroundColor = nil
50
81
}
51
82
}
83
+ public var selectionTouchInsideOnly : Bool = false
52
84
53
85
public convenience init ( items: [ Item ] , frame: CGRect = . zero) {
54
86
precondition ( items. count > 0 , " Items cannot be empty " )
@@ -76,9 +108,9 @@ public final class PopUpButton: UIControl {
76
108
77
109
public override func layoutSubviews( ) {
78
110
super. layoutSubviews ( )
79
- if isTracking , window != nil {
80
- let currentView = views [ currentIndex ]
81
- layoutViews ( from: currentView. frame)
111
+ if isPresented , window != nil , let index = _selectedIndex {
112
+ let currentView = views [ index ]
113
+ layoutViews ( from: currentView. frame, index : index )
82
114
} else if views. count > currentIndex {
83
115
views [ currentIndex] . frame = bounds
84
116
}
@@ -88,14 +120,22 @@ public final class PopUpButton: UIControl {
88
120
guard let spaceView = anchor. view ( for: self ) else { return false }
89
121
guard super. beginTracking ( touch, with: event) && super. becomeFirstResponder ( ) else { return false }
90
122
123
+ _selectedIndex = currentIndex
124
+
91
125
let ( cover, content) = self . cover. build ( )
126
+ #if targetEnvironment(macCatalyst)
127
+ let tapCover = UITapGestureRecognizer ( target: self , action: #selector( _tapInCover ( _: ) ) )
128
+ let hoverCover = UIHoverGestureRecognizer ( target: self , action: #selector( _hoverInCover ( _: ) ) )
129
+ cover. addGestureRecognizer ( tapCover)
130
+ cover. addGestureRecognizer ( hoverCover)
131
+ #endif
92
132
cover. frame = spaceView. bounds
93
133
spaceView. addSubview ( cover)
94
134
95
135
let current = views [ currentIndex]
96
136
current. backgroundColor = selectedItemColor ?? tintColor
97
137
let anchorRect = cover. convert ( current. frame, from: self )
98
- layoutViews ( from: anchorRect)
138
+ layoutViews ( from: anchorRect, index : currentIndex )
99
139
views. enumerated ( ) . forEach ( { i, v in
100
140
if i != currentIndex {
101
141
v. backgroundColor = itemsColor ?? backgroundColor
@@ -111,38 +151,64 @@ public final class PopUpButton: UIControl {
111
151
super. continueTracking ( touch, with: event)
112
152
guard let cover = coverView else { return false }
113
153
let pointInCover = convert ( touch. location ( in: self ) , to: cover)
114
- guard let index = views. firstIndex ( where: { $0. frame. minY < pointInCover. y && $0. frame. maxY > pointInCover. y } ) else { return true }
115
- currentIndex = index
154
+ _onTracking ( in: cover, point: pointInCover)
155
+ return true
156
+ }
157
+
158
+ public override func endTracking( _ touch: UITouch ? , with event: UIEvent ? ) {
159
+ super. endTracking ( touch, with: event)
160
+ #if !targetEnvironment(macCatalyst)
161
+ resignFirstResponder ( )
162
+ #endif
163
+ }
164
+
165
+ public override func becomeFirstResponder( ) -> Bool { false }
166
+
167
+ @discardableResult
168
+ public override func resignFirstResponder( ) -> Bool {
169
+ guard super. resignFirstResponder ( ) else { return false }
170
+ _onResignFirstResponder ( )
171
+ return true
172
+ }
173
+
174
+ private func _onTracking( in cover: UIView , point pointInCover: CGPoint ) {
175
+ guard let index = views. firstIndex ( where: { $0. frame. minY < pointInCover. y && $0. frame. maxY > pointInCover. y } ) else { return }
176
+ let selected = views [ index]
177
+ guard !selectionTouchInsideOnly || selected. frame. contains ( pointInCover) else {
178
+ _selectedIndex = nil
179
+ return
180
+ }
181
+ _selectedIndex = index
116
182
var nextIndex = index
117
183
if pointInCover. y <= ( frame. height + cover. safeAreaInsets. top) {
118
184
nextIndex = max ( 0 , index - 1 )
119
185
} else if pointInCover. y >= ( cover. bounds. maxY - frame. height - cover. safeAreaInsets. bottom) {
120
186
nextIndex = min ( views. count - 1 , index + 1 )
121
187
}
122
- if nextIndex != currentIndex {
123
- currentIndex = nextIndex
124
- layoutViews ( from: views [ index] . frame)
188
+ if nextIndex != _selectedIndex {
189
+ _selectedIndex = nextIndex
190
+ layoutViews ( from: views [ index] . frame, index : nextIndex )
125
191
}
126
- return true
127
192
}
128
193
129
- public override func endTracking( _ touch: UITouch ? , with event: UIEvent ? ) {
130
- super. endTracking ( touch, with: event)
131
- super. resignFirstResponder ( )
194
+ private func _onResignFirstResponder( ) {
132
195
coverView = nil
133
- let current = views [ currentIndex]
134
- current. backgroundColor = nil
135
- addSubview ( current)
196
+ guard let newIndex = _selectedIndex, newIndex != currentIndex else {
197
+ let current = views [ currentIndex]
198
+ current. backgroundColor = nil
199
+ addSubview ( current)
200
+ return
201
+ }
202
+ _selectedIndex = nil
203
+ currentIndex = newIndex
136
204
sendActions ( for: . valueChanged)
137
205
}
138
206
139
- public override func becomeFirstResponder( ) -> Bool { false }
140
-
141
- private func layoutViews( from rect: CGRect ) {
207
+ private func layoutViews( from rect: CGRect , index: Int ) {
142
208
views. enumerated ( ) . forEach { i, view in
143
209
view. frame = CGRect (
144
210
x: rect. minX,
145
- y: rect. minY + ( CGFloat ( i - currentIndex ) * rect. height) ,
211
+ y: rect. minY + ( CGFloat ( i - index ) * rect. height) ,
146
212
width: rect. width, height: rect. height
147
213
)
148
214
}
@@ -171,6 +237,21 @@ public final class PopUpButton: UIControl {
171
237
}
172
238
}
173
239
}
240
+
241
+ #if targetEnvironment(macCatalyst)
242
+ @objc func _tapInCover( _ gesture: UITapGestureRecognizer ) {
243
+ defer { resignFirstResponder ( ) }
244
+ guard views. count > 0 else { return }
245
+ let location = gesture. location ( in: gesture. view)
246
+ guard views [ 0 ] . frame. minX < location. x, views [ 0 ] . frame. minY < location. y else { return }
247
+ guard views. last!. frame. maxX > location. x, views. last!. frame. maxY > location. y else { return }
248
+ _selectedIndex = Int ( ( location. y - views[ 0 ] . frame. minY) / views[ 0 ] . frame. height)
249
+ }
250
+
251
+ @objc func _hoverInCover( _ gesture: UIHoverGestureRecognizer ) {
252
+ _onTracking ( in: gesture. view!, point: gesture. location ( in: gesture. view) )
253
+ }
254
+ #endif
174
255
}
175
256
extension PopUpButton {
176
257
public struct Item {
@@ -220,9 +301,11 @@ extension PopUpButton {
220
301
case . color( let color) :
221
302
let view = UIView ( )
222
303
view. backgroundColor = color
304
+ view. autoresizingMask = [ . flexibleHeight, . flexibleWidth]
223
305
return ( view, view)
224
306
case . blur( let style) :
225
307
let view = UIVisualEffectView ( effect: UIBlurEffect ( style: style) )
308
+ view. autoresizingMask = [ . flexibleHeight, . flexibleWidth]
226
309
return ( view, view. contentView)
227
310
}
228
311
}
0 commit comments