Skip to content

Commit

Permalink
[Refacto-557] Button: Add UIControl
Browse files Browse the repository at this point in the history
  • Loading branch information
robergro committed Oct 30, 2023
1 parent d5bac71 commit 5ebc478
Show file tree
Hide file tree
Showing 6 changed files with 396 additions and 115 deletions.
153 changes: 138 additions & 15 deletions core/Sources/Components/Button/View/UIKit/ButtonUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Combine
import UIKit

/// The UIKit version for the button.
public final class ButtonUIView: UIView {
public final class ButtonUIView: UIControl {

// MARK: - Type alias

Expand Down Expand Up @@ -43,15 +43,15 @@ public final class ButtonUIView: UIView {
return view
}()

private var iconImageView: UIImageView = {
let imageView = UIImageView()
private var iconImageView: UIControlStateImageView = {
let imageView = UIControlStateImageView()
imageView.contentMode = .scaleAspectFit
imageView.accessibilityIdentifier = AccessibilityIdentifier.iconImage
return imageView
}()

private var titleLabel: UILabel = {
let label = UILabel()
private var titleLabel: UIControlStateLabel = {
let label = UIControlStateLabel()
label.numberOfLines = 1
label.lineBreakMode = .byWordWrapping
label.textAlignment = .left
Expand Down Expand Up @@ -159,36 +159,35 @@ public final class ButtonUIView: UIView {
}

/// The icon image of the button.
@available(*, deprecated, message: "Use setImage(_:, for:) and image(for:) instead")
public var iconImage: UIImage? {
get {
return self.viewModel.iconImage?.leftValue
return self.image(for: .normal)
}
set {
self.viewModel.set(iconImage: newValue.map { .left($0) })
self.setImage(newValue, for: .normal)
}
}

/// The text of the button.
@available(*, deprecated, message: "Use setTitle(_:, for:) and title(for:) instead")
public var text: String? {
get {
return self.titleLabel.text
return self.title(for: .normal)
}
set {
self.titleLabel.text = newValue
self.viewModel.set(title: newValue)
self.setTitle(newValue, for: .normal)
}
}

/// The attributed text of the button.
@available(*, deprecated, message: "Use setAttributedTitle(_:, for:) and attributedTitle(for:) instead")
public var attributedText: NSAttributedString? {
get {
return self.titleLabel.attributedText
return self.attributedTitle(for: .normal)
}
set {
self.titleLabel.attributedText = newValue
self.viewModel.set(attributedTitle: newValue.map { .left($0) })
self.setAttributedTitle(newValue, for: .normal)
}
}

Expand All @@ -203,13 +202,32 @@ public final class ButtonUIView: UIView {
}
}

/// The state of the button: enabled or not.
public var isEnabled: Bool {
/// A Boolean value indicating whether the button is in the enabled state.
public override var isEnabled: Bool {
get {
return self.viewModel.isEnabled
}
set {
super.isEnabled = newValue
self.viewModel.set(isEnabled: newValue)
self.titleLabel.updateContent(from: self)
self.iconImageView.updateContent(from: self)
}
}

/// A Boolean value indicating whether the button is in the selected state.
public override var isSelected: Bool {
didSet {
self.titleLabel.updateContent(from: self)
self.iconImageView.updateContent(from: self)
}
}

/// A Boolean value indicating whether the button draws a highlight.
public override var isHighlighted: Bool {
didSet {
self.titleLabel.updateContent(from: self)
self.iconImageView.updateContent(from: self)
}
}

Expand Down Expand Up @@ -244,6 +262,38 @@ public final class ButtonUIView: UIView {

// MARK: - Initialization

/// Initialize a new button view.
/// - Parameters:
/// - theme: The spark theme of the button.
/// - intent: The intent of the button.
/// - variant: The variant of the button.
/// - size: The size of the button.
/// - shape: The shape of the button.
/// - alignment: The alignment of the button.
/// - isEnabled: The state of the button: enabled or not.
public convenience init(
theme: Theme,
intent: ButtonIntent,
variant: ButtonVariant,
size: ButtonSize,
shape: ButtonShape,
alignment: ButtonAlignment,
isEnabled: Bool
) {
self.init(
theme,
intent: intent,
variant: variant,
size: size,
shape: shape,
alignment: alignment,
iconImage: nil,
text: nil,
attributedText: nil,
isEnabled: isEnabled
)
}

/// Initialize a new button view with a text.
/// - Parameters:
/// - theme: The spark theme of the button.
Expand All @@ -254,6 +304,7 @@ public final class ButtonUIView: UIView {
/// - alignment: The alignment of the button.
/// - text: The text of the button.
/// - isEnabled: The state of the button: enabled or not.
@available(*, deprecated, message: "Use init(theme: , intent: , variant: , size: , shape: , alignment: , isEnabled) instead")
public convenience init(
theme: Theme,
intent: ButtonIntent,
Expand Down Expand Up @@ -288,6 +339,7 @@ public final class ButtonUIView: UIView {
/// - alignment: The alignment of the button.
/// - attributedText: The attributed text of the button.
/// - isEnabled: The state of the button: enabled or not.
@available(*, deprecated, message: "Use init(theme: , intent: , variant: , size: , shape: , alignment: , isEnabled) instead")
public convenience init(
theme: Theme,
intent: ButtonIntent,
Expand Down Expand Up @@ -322,6 +374,7 @@ public final class ButtonUIView: UIView {
/// - alignment: The alignment of the button.
/// - iconImage: The icon image of the button.
/// - isEnabled: The state of the button: enabled or not.
@available(*, deprecated, message: "Use init(theme: , intent: , variant: , size: , shape: , alignment: , isEnabled) instead")
public convenience init(
theme: Theme,
intent: ButtonIntent,
Expand Down Expand Up @@ -357,6 +410,7 @@ public final class ButtonUIView: UIView {
/// - iconImage: The icon image of the button.
/// - text: The text of the button.
/// - isEnabled: The state of the button: enabled or not.
@available(*, deprecated, message: "Use init(theme: , intent: , variant: , size: , shape: , alignment: , isEnabled) instead")
public convenience init(
theme: Theme,
intent: ButtonIntent,
Expand Down Expand Up @@ -393,6 +447,7 @@ public final class ButtonUIView: UIView {
/// - iconImage: The icon image of the button.
/// - attributedText: The attributed text of the button.
/// - isEnabled: The state of the button: enabled or not.
@available(*, deprecated, message: "Use init(theme: , intent: , variant: , size: , shape: , alignment: , isEnabled) instead")
public convenience init(
theme: Theme,
intent: ButtonIntent,
Expand Down Expand Up @@ -555,6 +610,59 @@ public final class ButtonUIView: UIView {
NSLayoutConstraint.stickEdges(from: self.clearButton, to: self)
}

// MARK: - Setter & Getter

/// The image of the button for a state.
/// - parameter state: state of the image
public func image(for state: ControlState) -> UIImage? {
return self.iconImageView.image(for: state)
}

/// Set the image of the button for a state.
/// - parameter image: new image of the button
/// - parameter state: state of the image
public func setImage(_ image: UIImage?, for state: ControlState) {
if state == .normal {
self.viewModel.set(iconImage: image.map { .left($0) })
}

self.iconImageView.setImage(image, for: state, on: self)
}

/// The title of the button for a state.
/// - parameter state: state of the title
public func title(for state: ControlState) -> String? {
return self.titleLabel.text(for: state)
}

/// Set the title of the button for a state.
/// - parameter title: new title of the button
/// - parameter state: state of the title
public func setTitle(_ title: String?, for state: ControlState) {
if state == .normal {
self.viewModel.set(title: title)
}

self.titleLabel.setText(title, for: state, on: self)
}

/// The title of the button for a state.
/// - parameter state: state of the title
public func attributedTitle(for state: ControlState) -> NSAttributedString? {
return self.titleLabel.attributedText(for: state)
}

/// Set the attributedTitle of the button for a state.
/// - parameter attributedTitle: new attributedTitle of the button
/// - parameter state: state of the attributedTitle
public func setAttributedTitle(_ attributedTitle: NSAttributedString?, for state: ControlState) {
if state == .normal {
self.viewModel.set(attributedTitle: attributedTitle.map { .left($0) })
}

self.titleLabel.setAttributedText(attributedTitle, for: state, on: self)
}

// MARK: - Update UI

private func updateBorderRadius() {
Expand Down Expand Up @@ -750,25 +858,40 @@ public final class ButtonUIView: UIView {
// MARK: - Actions

@objc private func touchUpInsideAction() {
self.isHighlighted = false

self.unpressedAction()

self.delegate?.button(self, didReceive: .touchUpInside)
self.delegate?.buttonWasTapped(self)
self.sendActions(for: .touchUpInside)
}

@objc private func touchDownAction() {
self.isHighlighted = true

self.viewModel.pressedAction()

self.delegate?.button(self, didReceive: .touchDown)
self.sendActions(for: .touchDown)
}

@objc private func touchUpOutsideAction() {
self.isHighlighted = false

self.unpressedAction()

self.delegate?.button(self, didReceive: .touchUpOutside)
self.sendActions(for: .touchUpOutside)
}

@objc private func touchCancelAction() {
self.isHighlighted = false

self.unpressedAction()

self.delegate?.button(self, didReceive: .touchCancel)
self.sendActions(for: .touchCancel)
}

private func unpressedAction() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ struct ButtonComponentView: View {
@State private var alignment: ButtonAlignment = .leadingIcon
@State private var content: ButtonContentDefault = .text
@State private var isEnabled: CheckboxSelectionState = .selected
@State private var isSelected: CheckboxSelectionState = .selected
@State private var isAnimated: CheckboxSelectionState = .selected

@State private var shouldShowReverseBackgroundColor: Bool = false
Expand Down Expand Up @@ -91,6 +92,14 @@ struct ButtonComponentView: View {
selectionState: self.$isEnabled
)

CheckboxView(
text: "Is selected",
checkedImage: DemoIconography.shared.checkmark,
theme: self.theme,
state: .enabled,
selectionState: self.$isSelected
)

CheckboxView(
text: "Is animated",
checkedImage: DemoIconography.shared.checkmark,
Expand All @@ -112,6 +121,7 @@ struct ButtonComponentView: View {
alignment: self.$alignment.wrappedValue,
content: self.$content.wrappedValue,
isEnabled: self.$isEnabled.wrappedValue == .selected,
isSelected: self.$isSelected.wrappedValue == .selected,
isAnimated: self.$isAnimated.wrappedValue == .selected
)
.frame(width: geometry.size.width, height: self.uiKitViewHeight, alignment: .center)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct ButtonComponentItemsUIView: UIViewRepresentable {
private let alignment: ButtonAlignment
private let content: ButtonContentDefault
private let isEnabled: Bool
private let isSelected: Bool
private let isAnimated: Bool

// MARK: - Initialization
Expand All @@ -44,6 +45,7 @@ struct ButtonComponentItemsUIView: UIViewRepresentable {
alignment: ButtonAlignment,
content: ButtonContentDefault,
isEnabled: Bool,
isSelected: Bool,
isAnimated: Bool
) {
self.viewModel = viewModel
Expand All @@ -64,6 +66,7 @@ struct ButtonComponentItemsUIView: UIViewRepresentable {
self.alignment = alignment
self.content = content
self.isEnabled = isEnabled
self.isSelected = isSelected
self.isAnimated = isAnimated
}

Expand Down Expand Up @@ -201,6 +204,10 @@ struct ButtonComponentItemsUIView: UIViewRepresentable {
buttonView.isEnabled = self.isEnabled
}

if buttonView.isSelected != self.isSelected {
buttonView.isSelected = self.isSelected
}

if buttonView.isAnimated != self.isAnimated {
buttonView.isAnimated = self.isAnimated
}
Expand Down
Loading

0 comments on commit 5ebc478

Please sign in to comment.