Files
raspberry/iOS/Wallet/Sources/Common/Control/Button/CommonButtonAction.swift
T

260 lines
8.5 KiB
Swift

//
// CommonButtonAction.swift
// List
//
// Created by Igor Danich on 20.07.2020.
// Copyright © 2020 Igor Danich. All rights reserved.
//
import UIKit
extension UIButton {
enum Style: String {
case primary
case secondary
case tertiary
case outline
}
enum Size: CGFloat {
case small = 28
case large = 48
}
}
@IBDesignable
class CommonButtonAction: UIButton, CommonMenuSender {
private let indicator = UIActivityIndicatorView(style: .large)
@IBInspectable var styleName: String = UIButton.Style.primary.rawValue {
didSet { update() }
}
@IBInspectable var image: UIImage? {
didSet { update() }
}
var style: Style {
get { Style(rawValue: self.styleName) ?? .primary }
set {
self.styleName = newValue.rawValue
self.update()
}
}
override var lzTitle: String? {
didSet { update(force: true) }
}
var cornerRadius: CGFloat = 10 {
didSet { update() }
}
var action: CommonMenuAction? {
didSet { update(animated: false) }
}
init(frame: CGRect = .zero, action: CommonMenuAction? = nil, height: CGFloat? = 0, isEnabled: Bool = true) {
super.init(frame: frame)
self.action = action
self.isEnabled = isEnabled
if let height = height {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
addTarget(self, action: #selector(onTap), for: .touchUpInside)
if action == nil {
update()
} else {
update(animated: false)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
addTarget(self, action: #selector(onTap), for: .touchUpInside)
update()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addTarget(self, action: #selector(onTap), for: .touchUpInside)
update()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
update()
}
var isLoading: Bool = false {
didSet { update() }
}
private var lastSize: CGSize?
func update(animated: Bool) {
update(force: true)
}
private func update(force: Bool = true) {
var title = (action?.menu?.title ?? lzTitle)?.localized
let image = action?.menu?.image ?? self.image
if !force, lastSize == frame.size { return }
if contentEdgeInsets == .zero { contentEdgeInsets = .init(top: 0, left: 12, bottom: 0, right: 12) }
isUserInteractionEnabled = !isLoading
borderColor = _border().color
layer.borderWidth = _border().width
layer.cornerRadius = cornerRadius
layer.masksToBounds = true
lastSize = frame.size
setTitle(nil, for: .normal)
setImage(isLoading ? nil : image, for: .normal)
if !isLoading, image != nil {
titleEdgeInsets = .init(top: 0, left: 4, bottom: 0, right: 0)
imageEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: (title?.isEmpty ?? true) ? 0 : 4)
if !(title?.isEmpty ?? true) {
title = "\(title ?? "") "
}
} else {
titleEdgeInsets = .zero
imageEdgeInsets = .zero
}
let states: [UIControl.State] = [.normal, .highlighted, .selected, .disabled]
states.forEach({ setTitle(nil, for: $0) })
if isLoading {
indicator.startAnimating()
indicator.center = .init(x: frame.width/2, y: frame.height/2)
states.forEach({ setAttributedTitle(nil, for: $0) })
} else {
indicator.stopAnimating()
states.forEach({ setAttributedTitle(title?.attributed(style: .medium, size: 14, color: _color(for: $0)), for: $0) })
}
states.forEach({
self.backgroundColor = _background(for: $0)
})
layoutIfNeeded()
}
override var isEnabled: Bool {
willSet {
self.alpha = newValue ? 1 : 0.2
}
}
@objc private func onTap() {
action?.perform(sender: self)
}
func setAttributedTitle(_ title: AttributedString) {
let states: [UIControl.State] = [.normal, .highlighted, .selected, .disabled]
states.forEach({ self.setAttributedTitle(title, for: $0) })
}
}
extension CommonButtonAction {
fileprivate func _background(for state: State) -> UIColor? {
let styles = ([
.primary: [
.normal: Asset.deepWater.color,
.highlighted: Asset.granite.color,
.selected: Asset.granite.color,
.disabled: Asset.deepWater.color
], .secondary: [
.normal : Asset.fog.color,
.highlighted : Asset.pebble.color,
.selected: Asset.pebble.color,
.disabled: Asset.fog.color
], .tertiary: [
.highlighted: Asset.marble.color,
.selected: Asset.marble.color
], .outline: [
.highlighted: Asset.deepWater.color,
.selected: Asset.deepWater.color
]
] as [Style: [State: UIColor]])
return styles[self.style]?[state]
}
fileprivate func _image(for state: State) -> UIImage? {
([
.primary: [
.normal: Asset.deepWater.color,
.highlighted: Asset.granite.color,
.selected: Asset.granite.color,
.disabled: Asset.deepWater.color.withAlphaComponent(0.2)
], .secondary: [
.normal: Asset.fog.color,
.highlighted: Asset.pebble.color,
.selected: Asset.pebble.color,
.disabled: Asset.fog.color
], .tertiary: [
.highlighted: Asset.marble.color,
.selected: Asset.marble.color
], .outline: [
.highlighted: Asset.deepWater.color.withAlphaComponent(0.1),
.selected: Asset.deepWater.color.withAlphaComponent(0.1)
]
] as [Style:[State:UIColor]])[style]?[state].map({ .colored($0, size: frame.size, cornerRadius: cornerRadius) })
}
fileprivate func _color(for state: State) -> UIColor {
let color: [Style: [State: UIColor]] = [
.primary: [
.normal: Asset.textSnow.color,
.highlighted: Asset.textSnow.color,
.selected: Asset.textSnow.color,
.disabled: Asset.textSnow.color
], .secondary: [
.normal: Asset.textGranite.color,
.highlighted: Asset.textGranite.color,
.selected: Asset.textGranite.color,
.disabled: Asset.textGranite.color.withAlphaComponent(0.3)
], .tertiary: [
.normal: Asset.deepWater.color,
.highlighted: Asset.deepWater.color,
.selected: Asset.deepWater.color,
.disabled: Asset.deepWater.color.withAlphaComponent(0.4)
], .outline: [
.normal: Asset.textDeepWater.color,
.highlighted: Asset.textDeepWater.color,
.selected: Asset.textDeepWater.color,
.disabled: Asset.textDeepWater.color.withAlphaComponent(0.4)
]
]
return color[style]?[state] ?? .clear
}
fileprivate func _border() -> (width: CGFloat, color: UIColor?) { [Style.outline: (1, Asset.deepWater.color.withAlphaComponent(0.2))][style] ?? (0, nil) }
}
extension UIControl.State: Hashable {}
extension UIButton {
@IBInspectable var borderColor: UIColor? {
get { UIColor(cgColor: layer.borderColor ?? UIColor.clear.cgColor) }
set { layer.borderColor = newValue?.cgColor }
}
}
extension UIView {
static func button(
style: UIButton.Style = .primary,
title: String? = nil,
image: UIImage? = nil,
size: UIButton.Size = .large,
isEnabled: Bool = true,
action: @escaping () -> Void
) -> CommonButtonAction {
.view(frame: .init(x: 0, y: 0, width: 1, height: size.rawValue)) {
$0.style = style
$0.lzTitle = title
$0.image = image
$0.isEnabled = isEnabled
let heightConstraint = $0.heightAnchor.constraint(equalToConstant: size.rawValue)
heightConstraint.priority = .defaultHigh
heightConstraint.isActive = true
$0.action = .action() { _ in action() }
}
}
}