@@ -11,18 +11,14 @@ import YMatterType
1111
1212/// A SwiftUI stepper control.
1313public struct Stepper {
14- enum ButtonType {
15- case increment
16- case decrement
17- }
18-
19- let minimumSize : CGSize = CGSize ( width: 44 , height: 44 )
20-
14+ @Environment ( \. sizeCategory) var sizeCategory
15+ let buttonSize : CGSize = CGSize ( width: 44 , height: 44 )
2116 @ObservedObject private var appearanceObserver = Stepper . AppearanceObserver ( )
2217 @ObservedObject private var valueObserver = Stepper . ValueObserver ( )
2318
2419 /// Receive value change notification
2520 public weak var delegate : StepperDelegate ?
21+
2622 /// Stepper appearance
2723 public var appearance : StepperControl . Appearance {
2824 get {
@@ -32,21 +28,24 @@ public struct Stepper {
3228 self . appearanceObserver. appearance = newValue
3329 }
3430 }
35- /// Optional minimum vale. Minimum possible value for the stepper.
31+
32+ /// Minimum value. Minimum possible value for the stepper.
3633 public var minimumValue : Double {
3734 get { valueObserver. minimumValue }
3835 set {
3936 onMinimumValueChange ( newValue: newValue)
4037 }
4138 }
42- /// Optional maximum value. Maximum possible value for the stepper.
39+
40+ /// Maximum value. Maximum possible value for the stepper.
4341 public var maximumValue : Double {
4442 get { valueObserver. maximumValue }
4543 set {
4644 onMaximumValueChange ( newValue: newValue)
4745 }
4846 }
49- /// Optional step value. The step, or increment, value for the stepper.
47+
48+ /// Step value. The step, or increment, value for the stepper.
5049 public var stepValue : Double {
5150 get { valueObserver. stepValue }
5251 set { valueObserver. stepValue = newValue }
@@ -57,18 +56,20 @@ public struct Stepper {
5756 get { valueObserver. value }
5857 set { onValueChange ( newValue: newValue) }
5958 }
59+
6060 /// Decimal digits in current value
6161 public var decimalPlaces : Int {
6262 get { valueObserver. decimalValue }
6363 set { valueObserver. decimalValue = newValue }
6464 }
65- /// Initializes Stepper
65+
66+ /// Initializes a stepper view.
6667 /// - Parameters:
6768 /// - appearance: appearance for the stepper. Default is `.default`
6869 /// - minimumValue: minimum value. Default is `0`
6970 /// - maximumValue: maximum value. Default is `100`
7071 /// - stepValue: Step value. Default is `1`
71- /// - value: Current value. Default is `0` or minimumValue (if provided)
72+ /// - value: Current value. Default is `0` or minimumValue
7273 public init (
7374 appearance: StepperControl . Appearance = . default,
7475 minimumValue: Double = 0 ,
@@ -81,56 +82,82 @@ public struct Stepper {
8182 self . maximumValue = maximumValue
8283 self . stepValue = stepValue
8384 self . value = ( minimumValue... maximumValue) . contains ( value) ?
84- minimumValue : value
85+ value : minimumValue
8586 }
8687}
8788
8889extension Stepper : View {
8990 /// :nodoc:
9091 public var body : some View {
9192 HStack ( spacing: 0 ) {
92- generateButton ( buttonType: . decrement) {
93- valueObserver. value -= stepValue
94- updateCurrentValue ( newValue: valueObserver. value)
95- }
96-
97- TextStyleLabel ( getValueText ( ) , typography: appearance. textStyle. typography) { label in
98- label. textAlignment = . center
99- } . frame ( minWidth: minimumSize. width, idealWidth: minimumSize. height)
100-
101- generateButton ( buttonType: . increment) {
102- valueObserver. value += stepValue
103- updateCurrentValue ( newValue: valueObserver. value)
104- }
93+ getDecrementButton ( )
94+ getTextView ( )
95+ getIncrementButton ( )
10596 }
97+ . frame ( width: ( 2 * buttonSize. width) + getStringSize( sizeCategory) . width)
10698 . background (
10799 Capsule ( )
108100 . strokeBorder ( Color ( appearance. borderColor) , lineWidth: appearance. borderWidth)
109101 . background ( Capsule ( ) . foregroundColor ( Color ( appearance. backgroundColor) ) )
110102 )
111103 }
112104
113- func generateButton(
114- buttonType: ButtonType ,
115- action: @escaping ( ) -> Void
116- ) -> some View {
117- let button = Button ( action: action) {
118- switch buttonType {
119- case . increment:
120- getIncrementImage ( )
121- case . decrement:
122- getImageForDecrementButton ( )
123- }
105+ @ViewBuilder
106+ func getIncrementButton( ) -> some View {
107+ Button { buttonAction ( buttonType: . increment) } label: {
108+ getIncrementImage ( ) . renderingMode ( . template) . foregroundColor ( Color ( appearance. textStyle. textColor) )
109+ }
110+ . frame ( width: buttonSize. width, height: buttonSize. height)
111+ . accessibilityLabel ( StepperControl . Strings. incrementA11yButton. localized)
112+ }
113+
114+ @ViewBuilder
115+ func getDecrementButton( ) -> some View {
116+ Button { buttonAction ( buttonType: . decrement) } label: {
117+ getImageForDecrementButton ( ) ? . renderingMode ( . template) . foregroundColor (
118+ Color ( appearance. textStyle. textColor)
119+ )
120+ }
121+ . frame ( width: buttonSize. width, height: buttonSize. height)
122+ . accessibilityLabel ( getAccessibilityText ( ) )
123+ }
124+
125+ func getTextView( ) -> some View {
126+ TextStyleLabel (
127+ getValueText ( ) ,
128+ typography: appearance. textStyle. typography
129+ ) { label in
130+ label. textAlignment = . center
131+ label. numberOfLines = 1
124132 }
125- return button. frame ( minWidth: minimumSize. width, minHeight: minimumSize. height)
133+ . frame ( width: getStringSize ( sizeCategory) . width)
134+ . accessibilityLabel ( getAccessibilityLabelText ( ) )
126135 }
127136}
128137
129138extension Stepper {
139+ enum ButtonType {
140+ case increment
141+ case decrement
142+ }
143+
130144 func getValueText( ) -> String {
145+ formatText ( for: value)
146+ }
147+
148+ func formatText( for value: Double ) -> String {
131149 String ( format: " %. \( decimalPlaces) f " , value)
132150 }
133151
152+ func getAccessibilityText( ) -> String {
153+ if appearance. hasDeleteButton
154+ && value <= stepValue
155+ && minimumValue == 0 {
156+ return StepperControl . Strings. deleteA11yButton. localized
157+ }
158+ return StepperControl . Strings. decrementA11yButton. localized
159+ }
160+
134161 func updateCurrentValue( newValue: Double ) {
135162 if newValue < valueObserver. minimumValue {
136163 valueObserver. value = valueObserver. minimumValue
@@ -141,22 +168,52 @@ extension Stepper {
141168 }
142169 delegate? . valueDidChange ( newValue: valueObserver. value)
143170 }
171+
172+ func buttonAction( buttonType: ButtonType ) {
173+ switch buttonType {
174+ case . increment:
175+ valueObserver. value += stepValue
176+ case . decrement:
177+ valueObserver. value -= stepValue
178+ }
179+ updateCurrentValue ( newValue: valueObserver. value)
180+ }
181+
182+ func getStringSize( _ size: ContentSizeCategory ) -> CGSize {
183+ let traits = UITraitCollection ( preferredContentSizeCategory: UIContentSizeCategory ( size) )
184+ let layout = appearance. textStyle. typography. generateLayout ( compatibleWith: traits)
185+ let valueSize = getValueText ( ) . size ( withFont: layout. font)
186+ let maxSize = formatText ( for: maximumValue) . size ( withFont: layout. font)
187+ return CGSize (
188+ width: max ( valueSize. width, maxSize. width) ,
189+ height: max ( valueSize. height, layout. lineHeight)
190+ )
191+ }
192+
193+ func getAccessibilityLabelText( ) -> String {
194+ StepperControl . Strings. valueA11yLabel. localized + getValueText( )
195+ }
144196}
145197
146198extension Stepper {
147- func getDeleteImage( ) -> Image {
148- Image ( uiImage: appearance. deleteImage ?? StepperControl . Appearance. defaultDeleteImage)
199+ func getDeleteImage( ) -> Image ? {
200+ if let image = appearance. deleteImage {
201+ return Image ( uiImage: image)
202+ }
203+ return nil
149204 }
150205
206+ @ViewBuilder
151207 func getIncrementImage( ) -> Image {
152- Image ( uiImage: appearance. incrementImage ?? StepperControl . Appearance . defaultIncrementImage )
208+ Image ( uiImage: appearance. incrementImage)
153209 }
154210
211+ @ViewBuilder
155212 func getDecrementImage( ) -> Image {
156- Image ( uiImage: appearance. decrementImage ?? StepperControl . Appearance . defaultDecrementImage )
213+ Image ( uiImage: appearance. decrementImage)
157214 }
158215
159- func getImageForDecrementButton( ) -> Image {
216+ func getImageForDecrementButton( ) -> Image ? {
160217 if appearance. hasDeleteButton
161218 && value <= stepValue
162219 && minimumValue == 0 {
@@ -191,10 +248,6 @@ private extension Stepper {
191248
192249struct Stepper_Previews : PreviewProvider {
193250 static var previews : some View {
194- HStack {
195- Spacer ( ) . frame ( maxWidth: . infinity)
196- Stepper ( )
197- Spacer ( ) . frame ( maxWidth: . infinity)
198- }
251+ Stepper ( )
199252 }
200253}
0 commit comments