Skip to content

Commit

Permalink
feat(Button): iOS-9080 button chevron
Browse files Browse the repository at this point in the history
* feat(Button): Added chevron for button in UIKit
* feat(Button): Added chevron for SwiftUI button

Co-authored-by: WanaldinoTelefonica <[email protected]>
Co-authored-by: Alejandro Megías <[email protected]>
  • Loading branch information
3 people authored Aug 18, 2023
1 parent c89c7c7 commit 977b1d9
Show file tree
Hide file tree
Showing 336 changed files with 263 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ class UICatalogButtonsViewController: UITableViewController {
Button(style: .primary, title: "OK"),
Button(style: .primary, title: "OK", isSmall: true),
Button(style: .link, title: "OK")
], .right)
], .right),
("Chevron", [
Button(style: .link, title: "Button Link", rightImage: .chevron)
], .center)
]
}

Expand Down Expand Up @@ -153,8 +156,8 @@ private extension UITableViewCell {
}

private class LoadSimulationButton: Button {
override init(style: Button.Style = .primary, title: String, loadingTitle: String? = nil, isSmall: Bool = false) {
super.init(style: style, title: title, loadingTitle: loadingTitle, isSmall: isSmall)
override init(style: Button.Style = .primary, title: String, rightImage: RightImage? = nil, loadingTitle: String? = nil, isSmall: Bool = false) {
super.init(style: style, title: title, rightImage: rightImage, loadingTitle: loadingTitle, isSmall: isSmall)
addTarget(self, action: #selector(simulateLoad), for: .touchUpInside)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ struct ButtonsCatalogView: View {
Style(buttonStyle: .misticaPrimaryInverse(small: true), title: "Primary Inverse Small", inverse: true),
Style(buttonStyle: .misticaSecondaryInverse(small: true), title: "Secondary Inverse Small", inverse: true),
Style(buttonStyle: .misticaLink(small: true), title: "Link Small", inverse: false),
Style(buttonStyle: .misticaLinkInverse(small: true), title: "Link Inverse Small", inverse: true)
Style(buttonStyle: .misticaLinkInverse(small: true), title: "Link Inverse Small", inverse: true),
Style(buttonStyle: .misticaLink(withChevron: true), title: "Link Chevron", inverse: false)
]
}
}
Expand Down
43 changes: 40 additions & 3 deletions Sources/Mistica/Components/Button/Button.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,18 @@ open class Button: UIControl {
public let insets: UIEdgeInsets
public let minimumWidth: CGFloat
public let font: UIFont

public init(insets: UIEdgeInsets, minimumWidth: CGFloat, font: UIFont) {
public let rightImageHeight: CGFloat?

public init(
insets: UIEdgeInsets,
minimumWidth: CGFloat,
font: UIFont,
rightImageHeight: CGFloat? = nil
) {
self.insets = insets
self.minimumWidth = minimumWidth
self.font = font
self.rightImageHeight = rightImageHeight
}
}

Expand All @@ -56,6 +63,29 @@ open class Button: UIControl {
}
}

public enum RightImage {
case chevron
case custom(image: UIImage)

private var _image: UIImage {
switch self {
case .chevron: return .chevron
case let .custom(image: image): return image
}
}

var image: UIImage {
_image.withRenderingMode(.alwaysTemplate)
}

var space: CGFloat {
switch self {
case .chevron: return 2
case .custom: return 8
}
}
}

public var style: Style {
didSet {
updateStyle()
Expand All @@ -78,6 +108,11 @@ open class Button: UIControl {
set { container.loadingTitle = newValue }
}

public var rightImage: RightImage? {
get { container.rightImage }
set { container.rightImage = newValue }
}

private var overridenAccessibilityLabel: String?
private var isShowingLoadingAnimation = false

Expand All @@ -93,13 +128,14 @@ open class Button: UIControl {
self.init(title: "")
}

public init(style: Style = .primary, title: String, loadingTitle: String? = nil, isSmall: Bool = false) {
public init(style: Style = .primary, title: String, rightImage: RightImage? = nil, loadingTitle: String? = nil, isSmall: Bool = false) {
self.style = style
self.isSmall = isSmall

super.init(frame: .zero)

self.title = title
self.rightImage = rightImage
self.loadingTitle = loadingTitle
commonInit()
}
Expand Down Expand Up @@ -232,6 +268,7 @@ private extension Button {
updateInsets()
container.minimumWidth = style.minimumWidth(isSmall: isSmall)
container.font = style.font(isSmall: isSmall)
container.rightImageHeight = style.rightImageHeight(isSmall: isSmall)
}

func commonInit() {
Expand Down
58 changes: 50 additions & 8 deletions Sources/Mistica/Components/Button/ButtonContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,38 @@ import UIKit

/// Internal class used by Button
class ButtonContentView: UIView {
private lazy var titleContentStackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [titleStackView])
stackView.axis = .vertical
stackView.alignment = .center
return stackView
}()

private lazy var titleStackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
rightImageView
])
stackView.alignment = .center
return stackView
}()

private lazy var titleLabel: UILabel = {
let titleLabel = UILabel()
titleLabel.textAlignment = .center
titleLabel.setContentHuggingPriority(.required, for: .vertical)
return titleLabel
}()

private let rightImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.isHidden = true
return imageView
}()

private lazy var rightImageHeightConstraint: NSLayoutConstraint = rightImageView.heightAnchor.constraint(equalToConstant: 1)

private lazy var loadingIndicator: UIActivityIndicatorView = {
let loadingIndicator = UIActivityIndicatorView(style: .medium)
loadingIndicator.startAnimating()
Expand Down Expand Up @@ -59,10 +84,25 @@ class ButtonContentView: UIView {
set {
loadingIndicator.color = newValue
titleLabel.textColor = newValue
rightImageView.tintColor = newValue
loadingTitleLabel.textColor = newValue
}
}

var rightImage: Button.RightImage? {
didSet {
rightImageView.isHidden = rightImage == nil
guard let rightImage else { return }
titleStackView.setCustomSpacing(rightImage.space, after: titleLabel)
rightImageView.image = rightImage.image
}
}

var rightImageHeight: CGFloat {
get { rightImageHeightConstraint.constant }
set { rightImageHeightConstraint.constant = newValue }
}

var minimumWidth: CGFloat = 0 {
didSet {
invalidateIntrinsicContentSize()
Expand Down Expand Up @@ -94,15 +134,17 @@ class ButtonContentView: UIView {

setContentCompressionResistancePriority(.required, for: .vertical)

addSubview(constrainedToLayoutMarginsGuideOf: titleLabel)
addSubview(constrainedToLayoutMarginsGuideOf: titleContentStackView)
addSubview(constrainedToLayoutMarginsGuideOf: loadingStackView)

rightImageHeightConstraint.isActive = true
}

override var intrinsicContentSize: CGSize {
let titleLabelSize = titleLabel.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
let titleSize = titleContentStackView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
let loadingStackViewSize = loadingStackView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
let width = max(titleLabelSize.width, loadingStackViewSize.width)
let height = max(titleLabelSize.height, loadingStackViewSize.height)
let width = max(titleSize.width, loadingStackViewSize.width)
let height = max(titleSize.height, loadingStackViewSize.height)
var size = CGSize(width: width, height: height).inset(by: layoutMargins)
size.width = max(size.width, minimumWidth)
return size
Expand All @@ -113,16 +155,16 @@ class ButtonContentView: UIView {
}

func transitionToLoading() {
titleLabel.alpha = 0
titleLabel.transform = CGAffineTransform(translationX: 0, y: -bounds.maxY)
titleContentStackView.alpha = 0
titleContentStackView.transform = CGAffineTransform(translationX: 0, y: -bounds.maxY)
loadingStackView.alpha = 1
loadingStackView.transform = CGAffineTransform(translationX: 0, y: 0)
loadingStackView.layoutIfNeeded()
}

func transitionToNormal() {
titleLabel.alpha = 1
titleLabel.transform = CGAffineTransform(translationX: 0, y: 0)
titleContentStackView.alpha = 1
titleContentStackView.transform = CGAffineTransform(translationX: 0, y: 0)
loadingStackView.alpha = 0
loadingStackView.transform = CGAffineTransform(translationX: 0, y: bounds.maxY)
loadingStackView.layoutIfNeeded()
Expand Down
16 changes: 15 additions & 1 deletion Sources/Mistica/Components/Button/ButtonStyle+Toolkit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public extension Button.Style {
private static var smallMinimumWidth: CGFloat = 104
private static var linkMinimumWidth: CGFloat = 0

private enum ImageHeight {
static let regular: CGFloat = 24
static let small: CGFloat = 20
}

static var primary: Button.Style {
Button.Style(
allowsBleedingAlignment: false,
Expand Down Expand Up @@ -143,14 +148,23 @@ public extension Button.Style {

return isSmall ? Button.Style.smallMinimumWidth : Button.Style.regularMinimumWidth
}

func rightImageHeight(isSmall: Bool) -> CGFloat {
if let rightImageHeight = overriddenSizes?.rightImageHeight {
return rightImageHeight
}

return isSmall ? Button.Style.ImageHeight.small : Button.Style.ImageHeight.regular
}
}

private extension Button.Style {
static var linkOverriddenSizes: OverriddenSizes {
OverriddenSizes(
insets: linkInsets,
minimumWidth: linkMinimumWidth,
font: linkFont
font: linkFont,
rightImageHeight: Button.Style.ImageHeight.small
)
}
}
4 changes: 4 additions & 0 deletions Sources/MisticaCommon/Assets/AssetToolkit+Image.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ public extension Image {
Image(uiImage: .arrowRight)
}

static var chevron: Image {
Image(uiImage: .chevron)
}

static var checkmarkIcon: Image {
Image(uiImage: .checkmarkIcon)
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/MisticaCommon/Assets/AssetToolkit+UIImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ public extension UIImage {
UIImage(named: "icn_arrow_right", type: .common)!
}

static var chevron: UIImage {
UIImage(named: "icn_chevron", type: .common)!
}

static var checkmarkIcon: UIImage {
UIImage(named: "icn_checkbox_check", type: .common)!
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "buttonLink_chevron.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
Binary file not shown.
37 changes: 32 additions & 5 deletions Sources/MisticaSwiftUI/Components/Button/MisticaButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ public enum ButtonBleedingAlignment {
case leading, trailing, none
}

enum MisticaButtonRightImage {
case chevron
case custom(Image)

var image: some View {
switch self {
case .chevron: return Image.chevron
case let .custom(image): return image
}
}

var space: CGFloat {
switch self {
case .chevron: return 2
case .custom: return 8
}
}
}

struct MisticaButton: View {
enum Constants {
static let minWidth: CGFloat = 76
Expand Down Expand Up @@ -64,6 +83,7 @@ struct MisticaButton: View {
let configuration: ButtonStyle.Configuration
let style: Style
let small: Bool
let rightImage: MisticaButtonRightImage?

@Environment(\.misticaButtonLoadingInfo) private var loadingInfo
@Environment(\.isEnabled) private var isEnabled
Expand All @@ -77,11 +97,18 @@ struct MisticaButton: View {
.accessibilityLabel(loadingInfo.loadingTitle)
.misticaBackport.accesibilityHidden(!loadingInfo.isLoading)

configuration.label
.font(textFont)
.offset(x: 0, y: loadingInfo.isLoading ? -Constants.transitionOffset : 0)
.animation(.misticaTimingCurve, value: loadingInfo.isLoading)
.misticaBackport.accesibilityHidden(loadingInfo.isLoading)
HStack {
configuration.label
.font(textFont)

if let rightImage {
Spacer().frame(width: rightImage.space)
rightImage.image
}
}
.offset(x: 0, y: loadingInfo.isLoading ? -Constants.transitionOffset : 0)
.animation(.misticaTimingCurve, value: loadingInfo.isLoading)
.misticaBackport.accesibilityHidden(loadingInfo.isLoading)
}
.frame(height: height)
.if(!small) { $0.expandHorizontally() }
Expand Down
Loading

0 comments on commit 977b1d9

Please sign in to comment.