Skip to content

[CM-1253] Add dim effect to buttons #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 35 additions & 22 deletions Sources/YStepper/SwiftUI/Views/Stepper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,19 @@ public struct Stepper {
@ObservedObject private var appearanceObserver = Stepper.AppearanceObserver()
@ObservedObject private var valueObserver = Stepper.ValueObserver()

/// Receive value change notification
var isIncrementDisabled: Bool {
value >= maximumValue
}
var isDecrementDisabled: Bool {
value <= minimumValue
}
var shouldShowDelete: Bool {
appearance.showDeleteImage
&& value <= stepValue
&& minimumValue == 0
}

/// Receive value change notification.
public weak var delegate: StepperDelegate?

/// Stepper appearance
Expand Down Expand Up @@ -58,19 +70,19 @@ public struct Stepper {
set { onValueChange(newValue: newValue) }
}

/// Decimal digits in current value
/// Decimal digits in current value.
public var decimalPlaces: Int {
get { valueObserver.decimalValue }
set { valueObserver.decimalValue = newValue }
}

/// Initializes a stepper view.
/// - Parameters:
/// - appearance: appearance for the stepper. Default is `.default`
/// - minimumValue: minimum value. Default is `0`
/// - maximumValue: maximum value. Default is `100`
/// - stepValue: Step value. Default is `1`
/// - value: Current value. Default is `0` or minimumValue
/// - appearance: appearance for the stepper. Default is `.default`.
/// - minimumValue: minimum value. Default is `0`.
/// - maximumValue: maximum value. Default is `100`.
/// - stepValue: Step value. Default is `1`.
/// - value: Current value. Default is `0` or minimumValue.
public init(
appearance: StepperControl.Appearance = .default,
minimumValue: Double = 0,
Expand Down Expand Up @@ -105,17 +117,25 @@ extension Stepper: View {
@ViewBuilder
func getIncrementButton() -> some View {
Button { buttonAction(buttonType: .increment) } label: {
getIncrementImage().renderingMode(.template)
getIncrementImage()
.foregroundColor(
Color(appearance.textStyle.textColor).opacity(isIncrementDisabled ? 0.5 : 1)
)
}
.accessibilityLabel(StepperControl.Strings.incrementA11yButton.localized)
.disabled(isIncrementDisabled)
}

@ViewBuilder
func getDecrementButton() -> some View {
Button { buttonAction(buttonType: .decrement) } label: {
getImageForDecrementButton()?.renderingMode(.template)
getImageForDecrementButton()
.foregroundColor(
Color(appearance.textStyle.textColor).opacity(isDecrementDisabled ? 0.5 : 1)
)
}
.accessibilityLabel(getAccessibilityText())
.disabled(isDecrementDisabled)
}

func getTextView() -> some View {
Expand All @@ -130,7 +150,7 @@ extension Stepper: View {
.frame(width: getStringSize(sizeCategory).width)
.accessibilityLabel(getAccessibilityLabelText())
}

@ViewBuilder
func getShape() -> some View {
switch appearance.layout.shape {
Expand Down Expand Up @@ -199,9 +219,7 @@ extension Stepper {
}

func getAccessibilityText() -> String {
if appearance.hasDeleteButton
&& value <= stepValue
&& minimumValue == 0 {
if shouldShowDelete {
return StepperControl.Strings.deleteA11yButton.localized
}
return StepperControl.Strings.decrementA11yButton.localized
Expand Down Expand Up @@ -245,11 +263,8 @@ extension Stepper {
}

extension Stepper {
func getDeleteImage() -> Image? {
if let image = appearance.deleteImage {
return Image(uiImage: image)
}
return nil
func getDeleteImage() -> Image {
Image(uiImage: appearance.deleteImage)
}

@ViewBuilder
Expand All @@ -262,10 +277,8 @@ extension Stepper {
Image(uiImage: appearance.decrementImage)
}

func getImageForDecrementButton() -> Image? {
if appearance.hasDeleteButton
&& value <= stepValue
&& minimumValue == 0 {
func getImageForDecrementButton() -> Image {
if shouldShowDelete {
return getDeleteImage()
}
return getDecrementImage()
Expand Down
10 changes: 5 additions & 5 deletions Sources/YStepper/UIKit/StepperControl+Appearance+Layout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
import UIKit

extension StepperControl.Appearance {
/// A collection of layout properties for the `StepperControl`
/// A collection of layout properties for the `StepperControl`.
public struct Layout: Equatable {
/// The content inset from edges. Stepper "content" consists of the two buttons and the text label between them.
/// Default is `{8, 16, 8, 16}`.
public var contentInset: NSDirectionalEdgeInsets
/// The horizontal spacing between the stepper buttons and label. Default is `8.0`
/// The horizontal spacing between the stepper buttons and label. Default is `8.0`.
public var gap: CGFloat
/// Stepper's shape
public var shape: Shape
Expand All @@ -24,9 +24,9 @@ extension StepperControl.Appearance {

/// Initializes a `Layout`.
/// - Parameters:
/// - contentInset: content inset from edges
/// - gap: horizontal spacing between icons and label
/// - shape: Stepper's shape. Default is `.capsule`
/// - contentInset: content inset from edges.
/// - gap: horizontal spacing between icons and label.
/// - shape: Stepper's shape. Default is `.capsule`.
public init(
contentInset: NSDirectionalEdgeInsets =
NSDirectionalEdgeInsets(topAndBottom: 8, leadingAndTrailing: 16),
Expand Down
74 changes: 40 additions & 34 deletions Sources/YStepper/UIKit/StepperControl+Appearance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,70 +10,76 @@ import UIKit
import YMatterType

extension StepperControl {
/// Appearance for stepper that contains typography and color properties
/// Appearance for stepper that contains typography and color properties.
public struct Appearance {
/// Typography of stepper value label
/// Typography of stepper value label.
public var textStyle: (textColor: UIColor, typography: Typography)
/// Background color for stepper view
/// Background color for stepper view.
public var backgroundColor: UIColor
/// Border color for stepper view
/// Border color for stepper view.
public var borderColor: UIColor
/// Border width for stepper view
/// Border width for stepper view.
public var borderWidth: CGFloat
/// Delete button image. Nil means no delete button
public var deleteImage: UIImage?
/// Delete button image
public var deleteImage: UIImage
/// Increment button image
public var incrementImage: UIImage
/// Decrement button image
public var decrementImage: UIImage
/// Stepper's layout properties such as spacing between views. Default is `.default`
/// Stepper's layout properties such as spacing between views. Default is `.default`.
public var layout: Layout
/// Whether to show delete button or not
var hasDeleteButton: Bool { deleteImage != nil }
/// Whether to show delete image or not.
var showDeleteImage: Bool

/// Initializer for appearance
/// Initializer for appearance.
/// - Parameters:
/// - textStyle: Typography and text color for valueText label.
/// Default is `(UIColor.label, Typography.systemLabel)`
/// - foregroundColor: Foreground color for valueText. Default is `.label`
/// - backgroundColor: Background color for stepper view. Default is `.systemBackground`
/// - borderColor: Border color for stepper view. Default is `UIColor.label`
/// - borderWidth: Border width for day view. Default is `1.0`
/// - deleteImage: Delete button image. Default is `Appearance.defaultDeleteImage`
/// - incrementImage: Increment button image. Default is `Appearance.defaultIncrementImage`
/// - decrementImage: Decrement button image. Default is `Appearance.defaultDecrementImage`
/// - layout: Stepper's layout properties like spacing between views

/// Default is `(UIColor.label, Typography.systemLabel)`.
/// - foregroundColor: Foreground color for valueText. Default is `.label`.
/// - backgroundColor: Background color for stepper view. Default is `.systemBackground`.
/// - borderColor: Border color for stepper view. Default is `UIColor.label`.
/// - borderWidth: Border width for day view. Default is `1.0`.
/// - deleteImage: Delete button image. Default is `nil`.
/// Passing `nil` means to use the default delete image.
/// - incrementImage: Increment button image. Default is `nil`.
/// Passing `nil` means to use the default increment image.
/// - decrementImage: Decrement button image. Default is `nil`.
/// Passing `nil` means to use the default decrement image.
/// - layout: Stepper's layout properties like spacing between views.
/// - showDeleteImage: Whether to show delete button or not. Default is`true`.

public init(
textStyle: (textColor: UIColor, typography: Typography) = (.label, .systemLabel),
foregroundColor: UIColor = .label,
backgroundColor: UIColor = .systemBackground,
borderColor: UIColor = .label,
borderWidth: CGFloat = 1.0,
deleteImage: UIImage? = Appearance.defaultDeleteImage,
incrementImage: UIImage = Appearance.defaultIncrementImage,
decrementImage: UIImage = Appearance.defaultDecrementImage,
layout: Layout = .default
deleteImage: UIImage? = nil,
incrementImage: UIImage? = nil,
decrementImage: UIImage? = nil,
layout: Layout = .default,
showDeleteImage: Bool = true
) {
self.textStyle = textStyle
self.backgroundColor = backgroundColor
self.borderColor = borderColor
self.borderWidth = borderWidth
self.deleteImage = deleteImage
self.incrementImage = incrementImage
self.decrementImage = decrementImage
self.deleteImage = deleteImage ?? Appearance.defaultDeleteImage
self.incrementImage = incrementImage ?? Appearance.defaultIncrementImage
self.decrementImage = decrementImage ?? Appearance.defaultDecrementImage
self.layout = layout
self.showDeleteImage = showDeleteImage
}
}
}

extension StepperControl.Appearance {
/// Default stepper appearance
public static let `default` = StepperControl.Appearance()
/// Default image for delete button. Is a `trash` from SF Symbols in template rendering mode
public static let defaultDeleteImage = StepperControl.Images.delete.image
/// Default image for increment button. Is a `plus` from SF Symbols in template rendering mode
public static let defaultIncrementImage = StepperControl.Images.increment.image
/// Default image for decrement button. Is a `minus` from SF Symbols in template rendering mode
public static let defaultDecrementImage = StepperControl.Images.decrement.image
/// Default image for delete button. Is a `trash` from SF Symbols in template rendering mode.
public static let defaultDeleteImage = StepperControl.Images.delete.image.withRenderingMode(.alwaysTemplate)
/// Default image for increment button. Is a `plus` from SF Symbols in template rendering mode.
public static let defaultIncrementImage = StepperControl.Images.increment.image.withRenderingMode(.alwaysTemplate)
/// Default image for decrement button. Is a `minus` from SF Symbols in template rendering mode.
public static let defaultDecrementImage = StepperControl.Images.decrement.image.withRenderingMode(.alwaysTemplate)
}
14 changes: 7 additions & 7 deletions Sources/YStepper/UIKit/StepperControl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,19 @@ public class StepperControl: UIControl {
get { stepperView.value }
set { stepperView.value = newValue }
}
/// Decimal places visible in current value
/// Decimal places visible in current value.
public var decimalPlaces: Int {
get { stepperView.decimalPlaces }
set { stepperView.decimalPlaces = newValue }
}

/// Initializes a stepper control
/// Initializes a stepper control.
/// - Parameters:
/// - appearance: appearance for the stepper. Default is `.default`
/// - minimumValue: minimum value. Default is `0`
/// - maximumValue: maximum value. Default is `100`
/// - stepValue: Step value. Default is `1`
/// - value: Current value. Default is `0` or minimumValue (if provided)
/// - appearance: appearance for the stepper. Default is `.default`.
/// - minimumValue: minimum value. Default is `0`.
/// - maximumValue: maximum value. Default is `100`.
/// - stepValue: Step value. Default is `1`.
/// - value: Current value. Default is `0` or minimumValue (if provided).
public required init(
appearance: StepperControl.Appearance = .default,
minimumValue: Double = 0,
Expand Down
6 changes: 3 additions & 3 deletions Sources/YStepper/UIKit/YStepper+Shapes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ extension StepperControl.Appearance {
public enum Shape: Equatable {
/// None
case none
/// Rectangle.
/// Rectangle
case rectangle
/// Rounded rectangle.
/// Rounded rectangle
case roundRect(cornerRadius: CGFloat)
/// Rounded rectangle that scales with Dynamic Type.
case scaledRoundRect(cornerRadius: CGFloat)
/// Capsule.
/// Capsule
case capsule
}
}
35 changes: 14 additions & 21 deletions Tests/YStepperTests/Views/StepperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,24 @@ final class StepperTests: XCTestCase {

func testImagesShouldNotBeNil() {
let sut = makeSUT(appearance: StepperControl.Appearance(
deleteImage: StepperControl.Appearance.defaultDeleteImage
deleteImage: nil,
incrementImage: nil,
decrementImage: nil
))

XCTAssertNotNil(sut.getIncrementImage())
XCTAssertNotNil(sut.getDecrementImage())
XCTAssertNotNil(sut.getDeleteImage())
}

func testDeleteImageShouldBeNil() {
let sut = makeSUT(appearance: StepperControl.Appearance(deleteImage: nil))
XCTAssertNil(sut.getDeleteImage())

let decrementImage = sut.getDecrementImage()
XCTAssertEqual(StepperControl.Appearance.defaultDeleteImage, sut.appearance.deleteImage)
XCTAssertEqual(StepperControl.Appearance.defaultDecrementImage, sut.appearance.decrementImage)
XCTAssertEqual(StepperControl.Appearance.defaultIncrementImage, sut.appearance.incrementImage)
}

XCTAssertEqual(decrementImage, sut.getImageForDecrementButton())
func testDecrementImageshouldNotReturnDeleteImage() {
let sut = makeSUT(appearance: StepperControl.Appearance(showDeleteImage: false))
let decrementImage = sut.getDeleteImage()
XCTAssertNotEqual(decrementImage, sut.getImageForDecrementButton())
}

func testValueGreaterThanMaxValue() {
Expand All @@ -123,17 +126,14 @@ final class StepperTests: XCTestCase {
}

func testImageUpdate() {
var sut = makeSUT(appearance: StepperControl.Appearance(deleteImage: StepperControl.Images.decrement.image))
var sut = makeSUT(appearance: StepperControl.Appearance(showDeleteImage: false))
let deleteImage = sut.getDeleteImage()
let decrementImage = sut.getDecrementImage()
XCTAssertEqual(sut.value, sut.minimumValue)
XCTAssertEqual(sut.getImageForDecrementButton(), decrementImage)

sut.value = sut.stepValue
sut.appearance.showDeleteImage = true
XCTAssertEqual(sut.getImageForDecrementButton(), deleteImage)

sut.appearance.deleteImage = nil
XCTAssertEqual(sut.getImageForDecrementButton(), decrementImage)
}

func testButtonAction() {
Expand All @@ -145,13 +145,6 @@ final class StepperTests: XCTestCase {
XCTAssertEqual(sut.value, sut.minimumValue)
}

func testBackgroundNotNil() {
let sut = makeSUT()
let button = sut.getDecrementButton()

XCTAssertNotNil(button)
}

func testShapesNotNil() {
var expectedAppearance = StepperControl.Appearance(layout: StepperControl.Appearance.Layout(shape: .rectangle))
var sut = makeSUT(appearance: expectedAppearance)
Expand Down Expand Up @@ -242,7 +235,7 @@ final class StepperTests: XCTestCase {
var sut = makeSUT()
XCTAssertEqual(sut.getAccessibilityText(), StepperControl.Strings.deleteA11yButton.localized)

sut.appearance.deleteImage = nil
sut.appearance.showDeleteImage = false
XCTAssertEqual(sut.getAccessibilityText(), StepperControl.Strings.decrementA11yButton.localized)
}
}
Expand Down