@@ -2,17 +2,34 @@ import FioriThemeManager
2
2
import Foundation
3
3
import SwiftUI
4
4
5
- private struct CardFooterLayout : Layout {
6
- enum ButtonWidthMode {
7
- /// same and fill its width to use available container width
8
- case sameAndFill
9
-
10
- /// intrinsic size's width
11
- case intrinsic
5
+ struct CardFooterButtonWidthModeKey : EnvironmentKey {
6
+ /// Default value is `.auto`
7
+ public static let defaultValue : CardFooterButtonWidthMode = . auto
8
+ }
9
+
10
+ public extension EnvironmentValues {
11
+ /// Sets the button width mode for `CardFooter`.
12
+ var cardFooterButtonWidthMode : CardFooterButtonWidthMode {
13
+ get { self [ CardFooterButtonWidthModeKey . self] }
14
+ set { self [ CardFooterButtonWidthModeKey . self] = newValue }
12
15
}
16
+ }
17
+
18
+ /// CardFooter button width mode
19
+ public enum CardFooterButtonWidthMode : Int {
20
+ /// auto size based on card footer's width. When it is regular size class, up to 3 buttons are shown with intrinsic width; when it is compact size class, up to 2 buttons are shown with equal width.
21
+ case auto
22
+
23
+ /// equal size and fill up the whole width except the overflow button
24
+ case equal
13
25
26
+ /// intrinsic size's width
27
+ case intrinsic
28
+ }
29
+
30
+ private struct CardFooterLayout : Layout {
14
31
struct LayoutMode : Equatable {
15
- let mode : ButtonWidthMode
32
+ let mode : CardFooterButtonWidthMode
16
33
let num : Int
17
34
}
18
35
@@ -28,21 +45,18 @@ private struct CardFooterLayout: Layout {
28
45
}
29
46
}
30
47
31
- @Binding var numButtonsDisplayInOverflow : Int
32
-
33
48
/// The distance between adjacent subviews.
34
49
var spacing : CGFloat ? = 8
35
50
36
51
/// Maximum width for each element in the container
37
52
var maxButtonWidth : CGFloat
38
53
39
- var horizontalSizeClass : UserInterfaceSizeClass ? = . compact
54
+ let cardFooterButtonWidthMode : CardFooterButtonWidthMode
40
55
41
- init ( numButtonsDisplayInOverflow: Binding < Int > , spacing: CGFloat ? = nil , maxButtonWidth: CGFloat ? = nil , horizontalSizeClass: UserInterfaceSizeClass ? = nil ) {
42
- self . _numButtonsDisplayInOverflow = numButtonsDisplayInOverflow
56
+ init ( spacing: CGFloat ? = nil , maxButtonWidth: CGFloat ? = nil , cardFooterButtonWidthMode: CardFooterButtonWidthMode = . auto) {
43
57
self . spacing = spacing
44
58
self . maxButtonWidth = max ( 100 , maxButtonWidth ?? CGFloat . greatestFiniteMagnitude)
45
- self . horizontalSizeClass = horizontalSizeClass
59
+ self . cardFooterButtonWidthMode = cardFooterButtonWidthMode
46
60
}
47
61
48
62
func sizeThatFits( proposal: ProposedViewSize , subviews: Subviews , cache: inout CacheData ) -> CGSize {
@@ -58,8 +72,17 @@ private struct CardFooterLayout: Layout {
58
72
}
59
73
60
74
func calculateLayout( proposal: ProposedViewSize , subviews: Subviews , cache: inout CacheData ) {
61
- let isRegular = proposal. width ?? 1024 > 667
62
- let layoutMode = LayoutMode ( mode: isRegular ? . intrinsic : . sameAndFill,
75
+ let isRegular : Bool
76
+ switch self . cardFooterButtonWidthMode {
77
+ case . auto:
78
+ isRegular = proposal. width ?? 1024 > 667
79
+ case . equal:
80
+ isRegular = false
81
+ case . intrinsic:
82
+ isRegular = true
83
+ }
84
+
85
+ let layoutMode = LayoutMode ( mode: isRegular ? . intrinsic : . equal,
63
86
num: isRegular ? 3 : 2 )
64
87
if !cache. frames. isEmpty, cache. fitSize. width == proposal. width, cache. layoutMode == layoutMode {
65
88
return
@@ -71,16 +94,30 @@ private struct CardFooterLayout: Layout {
71
94
$0. sizeThatFits ( . unspecified)
72
95
}
73
96
74
- let hideRect = CGRect ( x: - 2000 , y: 0 , width: 0 , height: 0 )
75
- self . calculateLayout ( proposalWidth: proposal. width, subViewSizes: subViewSizes, hideRect: hideRect, layoutMode: layoutMode, cache: & cache)
97
+ self . calculateLayout ( proposalWidth: proposal. width, subViewSizes: subViewSizes, layoutMode: layoutMode, cache: & cache)
76
98
}
77
99
78
- /// .compact, .sameAndFill, same size, up to 2 buttons
79
- /// .reguar, .intrinsic, up to 3 buttons
80
- func calculateLayout( proposalWidth: CGFloat ? , subViewSizes: [ CGSize ] , hideRect: CGRect , layoutMode: LayoutMode , cache: inout CacheData ) {
81
- let subViewNoOflSizes = subViewSizes. dropLast ( )
100
+ /**
101
+ case 1: totol is 5 buttons, 3 buttons (tertiary, secondary, primary), overflow menu with 2 buttons, overflow menu with 1 button
102
+ case 2: total is 3 buttons, 2 buttons (only two of tertiary, secondary and primary exist), overflow menu with 1 button
103
+ case 3: total is 1 button, 1 button, only one of tertiary, secondary or primary exist
104
+
105
+ In compact width, .equal, same size, up to 2 buttons
106
+ In regular width, .intrinsic, up to 3 buttons
107
+ */
108
+ func calculateLayout( proposalWidth: CGFloat ? , subViewSizes: [ CGSize ] , layoutMode: LayoutMode , cache: inout CacheData ) {
109
+ let subViewNoOflSizes : [ CGSize ]
110
+ switch subViewSizes. count {
111
+ case 5 :
112
+ subViewNoOflSizes = Array ( subViewSizes. dropLast ( 2 ) )
113
+ case 3 :
114
+ subViewNoOflSizes = Array ( subViewSizes. dropLast ( 1 ) )
115
+ default :
116
+ subViewNoOflSizes = subViewSizes
117
+ }
118
+
82
119
let numButtons = subViewNoOflSizes. count
83
- let overflowSize = subViewSizes [ numButtons]
120
+ let overflowSize = subViewNoOflSizes . count < subViewSizes. count ? subViewSizes [ numButtons] : CGSize . zero
84
121
let theSpacing : CGFloat = self . spacing ?? 0
85
122
var maxHeight : CGFloat = 0
86
123
var requiredFinalWidth : CGFloat = 0
@@ -92,7 +129,7 @@ private struct CardFooterLayout: Layout {
92
129
93
130
/// calculate numToShow, buttonWidth, requiredFinalWidth
94
131
if finalWidth == 0 {
95
- if layoutMode. mode == . sameAndFill {
132
+ if layoutMode. mode == . equal {
96
133
let tmpButtonWidth : CGFloat = subViewNoOflSizes. suffix ( numToShow) . reduce ( 0 ) { partialResult, size in
97
134
min ( self . maxButtonWidth, max ( partialResult, size. width) )
98
135
}
@@ -117,7 +154,7 @@ private struct CardFooterLayout: Layout {
117
154
} else { // there is a proposalWidth
118
155
var tmpButtonWidth : CGFloat = 0
119
156
for i in 0 ..< idealNumToShow {
120
- if layoutMode. mode == . sameAndFill {
157
+ if layoutMode. mode == . equal {
121
158
tmpButtonWidth = min ( self . maxButtonWidth, max ( tmpButtonWidth, subViewNoOflSizes [ i] . width) )
122
159
requiredFinalWidth = tmpButtonWidth * CGFloat( i + 1 ) + theSpacing * CGFloat( i)
123
160
} else {
@@ -131,7 +168,7 @@ private struct CardFooterLayout: Layout {
131
168
if numToShow > 1 {
132
169
numToShow -= 1
133
170
}
134
- if layoutMode. mode == . sameAndFill {
171
+ if layoutMode. mode == . equal {
135
172
var availableWidth = finalWidth - theSpacing * CGFloat( max ( 0 , numToShow - 1 ) )
136
173
if numButtons > 1 {
137
174
availableWidth -= theSpacing + overflowSize. width
@@ -142,7 +179,7 @@ private struct CardFooterLayout: Layout {
142
179
}
143
180
}
144
181
145
- if buttonWidth == nil , layoutMode. mode == . sameAndFill {
182
+ if buttonWidth == nil , layoutMode. mode == . equal {
146
183
var availableWidth = finalWidth - theSpacing * CGFloat( numToShow - 1 )
147
184
if numToShow < numButtons {
148
185
availableWidth -= min ( self . maxButtonWidth, overflowSize. width) + theSpacing
@@ -159,29 +196,27 @@ private struct CardFooterLayout: Layout {
159
196
/// set up frames for each subview
160
197
161
198
let y = maxHeight / 2
162
-
199
+ // Move the hidden buttons out of visible area
200
+ let hideRect = CGRect ( x: - 2000 , y: y, width: 0 , height: 0 )
163
201
var frames = [ CGRect] ( )
164
202
165
203
var x : CGFloat = 0
166
- for i in 0 ... numButtons {
204
+ for i in 0 ..< subViewSizes . count {
167
205
if i < numToShow {
168
206
let btWidth = buttonWidth ?? min ( finalWidth - ( numToHide > 0 ? theSpacing + overflowSize. width : 0 ) , self . maxButtonWidth, subViewNoOflSizes [ i] . width)
169
207
x += btWidth + ( i > 0 ? theSpacing : 0 )
170
208
frames. append ( CGRect ( origin: CGPoint ( x: finalWidth - x + btWidth / 2 , y: y) , size: CGSize ( width: btWidth, height: maxHeight) ) )
171
209
} else if i < numButtons { // rest button to hide
172
210
frames. append ( hideRect)
173
211
} else { // overflow
174
- if numToHide > 0 {
212
+ if numToHide > 0 , i == numButtons + numToHide - 1 { // last one to show overflow
175
213
frames. append ( CGRect ( x: overflowSize. width / 2 , y: y, width: min ( self . maxButtonWidth, overflowSize. width) , height: overflowSize. height) )
176
- } else {
214
+ } else { // hide the other overflow
177
215
frames. append ( hideRect)
178
216
}
179
217
}
180
218
}
181
-
182
- DispatchQueue . main. async {
183
- self . numButtonsDisplayInOverflow = numToHide
184
- }
219
+
185
220
cache. frames = frames. reversed ( )
186
221
cache. fitSize = CGSize ( width: finalWidth, height: maxHeight)
187
222
cache. layoutMode = layoutMode
@@ -214,29 +249,36 @@ private struct CardFooterLayout: Layout {
214
249
215
250
// Base Layout style
216
251
public struct CardFooterBaseStyle : CardFooterStyle {
217
- @Environment ( \. horizontalSizeClass) var horizontalSizeClass
218
- @State var numButtonsDisplayInOverflow : Int = 0
252
+ @Environment ( \. cardFooterButtonWidthMode) var cardFooterButtonWidthMode
219
253
220
254
@ViewBuilder
221
255
public func makeBody( _ configuration: CardFooterConfiguration ) -> some View {
222
256
// Add default layout here
223
- CardFooterLayout ( numButtonsDisplayInOverflow: self . $numButtonsDisplayInOverflow, spacing: 8 , maxButtonWidth: nil , horizontalSizeClass: self . horizontalSizeClass) {
224
- Menu {
225
- if self . numButtonsDisplayInOverflow == 1 {
257
+ CardFooterLayout ( spacing: 8 , maxButtonWidth: nil , cardFooterButtonWidthMode: self . cardFooterButtonWidthMode) {
258
+ if self . numOfButtons ( configuration) == 3 {
259
+ Menu {
260
+ configuration. secondaryAction. environment ( \. isInMenu, true )
261
+ configuration. tertiaryAction. environment ( \. isInMenu, true )
262
+ } label: {
263
+ configuration. overflowAction
264
+ }
265
+ /// set the accessibilityLabel as same as SF symbol "ellipsis" which is "More"
266
+ . accessibilityLabel ( Text ( Image ( systemName: " ellipsis " ) ) )
267
+ }
268
+
269
+ if self . numOfButtons ( configuration) > 1 {
270
+ Menu {
226
271
if !configuration. tertiaryAction. isEmpty {
227
272
configuration. tertiaryAction. environment ( \. isInMenu, true )
228
273
} else {
229
274
configuration. secondaryAction. environment ( \. isInMenu, true )
230
275
}
231
- } else if self . numButtonsDisplayInOverflow == 2 {
232
- configuration. secondaryAction. environment ( \. isInMenu, true )
233
- configuration. tertiaryAction. environment ( \. isInMenu, true )
276
+ } label: {
277
+ configuration. overflowAction
234
278
}
235
- } label : {
236
- configuration . overflowAction
279
+ /// set the accessibilityLabel as same as SF symbol "ellipsis" which is "More"
280
+ . accessibilityLabel ( Text ( Image ( systemName : " ellipsis " ) ) )
237
281
}
238
- /// set the accessibilityLabel as same as SF symbol "ellipsis" which is "More"
239
- . accessibilityLabel ( Text ( Image ( systemName: " ellipsis " ) ) )
240
282
241
283
if !configuration. tertiaryAction. isEmpty {
242
284
configuration. tertiaryAction
@@ -251,6 +293,27 @@ public struct CardFooterBaseStyle: CardFooterStyle {
251
293
}
252
294
}
253
295
}
296
+
297
+ /**
298
+ case 1: totol is 5 buttons, 3 buttons (tertiary, secondary, primary), overflow menu with 2 buttons, overflow menu with 1 button
299
+ case 2: total is 3 buttons, 2 buttons (only two of tertiary, secondary and primary exist), overflow menu with 1 button
300
+ case 3: total is 1 button, 1 button, only one of tertiary, secondary or primary exist
301
+ */
302
+ func numOfButtons( _ configuration: CardFooterConfiguration ) -> Int {
303
+ var value = 0
304
+ if !configuration. action. isEmpty {
305
+ value += 1
306
+ }
307
+
308
+ if !configuration. secondaryAction. isEmpty {
309
+ value += 1
310
+ }
311
+
312
+ if !configuration. tertiaryAction. isEmpty {
313
+ value += 1
314
+ }
315
+ return value
316
+ }
254
317
}
255
318
256
319
// Default fiori styles
@@ -316,6 +379,16 @@ public enum CardFooterTests {
316
379
public static let examples = [ footer0, footer1, footer2, footer3, footer4, footer5, footer6, footer7, footer8, footer9, footer10]
317
380
}
318
381
382
+ #Preview( " Empty " ) {
383
+ VStack {
384
+ CardFooter ( action: { EmptyView ( ) } , overflowAction: { EmptyView ( ) } ) . border ( Color . green)
385
+
386
+ Text ( " Empty " )
387
+
388
+ CardFooter ( action: { EmptyView ( ) } ) . border ( Color . green)
389
+ } . border ( Color . red)
390
+ }
391
+
319
392
#Preview( " P " ) {
320
393
CardFooter ( action: FioriButton ( title: " Primary " ) , overflowAction: FioriButton ( title: " Overflow " ) )
321
394
}
0 commit comments