567 lines
16 KiB
Swift
567 lines
16 KiB
Swift
//
|
||
// ChipButtons.swift
|
||
// Crush
|
||
//
|
||
// Created by Leon on 2025/7/18.
|
||
//
|
||
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
// MARK: - Base Class
|
||
|
||
class EPChipButton: CLControl {
|
||
// MARK: - Constants
|
||
|
||
let chipButtonHeight: CGFloat = 32
|
||
|
||
// MARK: - Properties
|
||
|
||
lazy var stack: UIStackView = {
|
||
let stack = UIStackView()
|
||
stack.alignment = .fill
|
||
stack.distribution = .fill
|
||
stack.alignment = .center
|
||
stack.axis = .horizontal
|
||
stack.spacing = 8
|
||
return stack
|
||
}()
|
||
|
||
lazy var imageView: UIImageView = {
|
||
let imageView = UIImageView()
|
||
imageView.contentMode = .scaleAspectFill
|
||
imageView.isUserInteractionEnabled = true
|
||
return imageView
|
||
}()
|
||
|
||
lazy var textLabel: UILabel = {
|
||
let label = UILabel()
|
||
label.font = CLSystemToken.font(token: .tlm)
|
||
label.isUserInteractionEnabled = true
|
||
return label
|
||
}()
|
||
|
||
lazy var iconView: UIImageView = {
|
||
let iconView = UIImageView()
|
||
iconView.contentMode = .scaleAspectFit
|
||
iconView.isUserInteractionEnabled = true
|
||
return iconView
|
||
}()
|
||
|
||
var text: String? {
|
||
didSet {
|
||
textLabel.text = text
|
||
setNeedsLayout()
|
||
}
|
||
}
|
||
|
||
var iconCode: IconCode = .sort {
|
||
didSet {
|
||
iconView.isHidden = (iconCode.rawValue <= 0)
|
||
iconView.image = defaultIcon
|
||
}
|
||
}
|
||
|
||
var defaultIconColor: UIColor? {
|
||
didSet {
|
||
iconView.image = defaultIcon
|
||
}
|
||
}
|
||
|
||
var defaultIcon: UIImage? {
|
||
let color = defaultIconColor ?? UIColor.c.ctsn
|
||
return MWIconFont.image(fromIconInt: iconCode.rawValue, size: iconSize, color: color, edgeInsets: .zero)
|
||
}
|
||
|
||
var selectedIcon: UIImage? {
|
||
nil // Subclasses override
|
||
}
|
||
|
||
var defaultImage: UIImage? {
|
||
nil // Subclasses override
|
||
}
|
||
|
||
var selectedImage: UIImage? {
|
||
nil // Subclasses override
|
||
}
|
||
|
||
var paddingInsets: UIEdgeInsets {
|
||
.zero // Subclasses override
|
||
}
|
||
|
||
var iconSize: CGSize {
|
||
.zero // Subclasses override
|
||
}
|
||
|
||
var imageSize: CGSize {
|
||
.zero // Subclasses override
|
||
}
|
||
|
||
// MARK: - Initialization
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
clipsToBounds = true
|
||
layer.cornerRadius = chipButtonHeight * 0.5
|
||
|
||
setupTheme()
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
// MARK: - Setup
|
||
|
||
func setupTheme() {
|
||
textLabel.textColor = .c.ctpn // EPSystemToken.color(.txtPrimaryNormal)
|
||
}
|
||
|
||
// MARK: - Hit Testing
|
||
|
||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||
guard isUserInteractionEnabled, alpha != 0, !isHidden else { return nil }
|
||
|
||
if self.point(inside: point, with: event) {
|
||
return self
|
||
}
|
||
return super.hitTest(point, with: event)
|
||
}
|
||
}
|
||
|
||
// MARK: - EPChipFilterButton
|
||
|
||
/// 只有文字,左右16。 height 32
|
||
class EPChipFilterButton: EPChipButton {
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
backgroundColor = .c.csen // EPSystemToken.color(.surfaceElementNormal)
|
||
layer.borderWidth = 0
|
||
|
||
addSubview(textLabel)
|
||
textLabel.snp.makeConstraints { make in
|
||
make.edges.equalTo(self).inset(paddingInsets).priority(.high)
|
||
make.height.equalTo(chipButtonHeight)
|
||
}
|
||
}
|
||
|
||
override func setupTheme() {
|
||
super.setupTheme()
|
||
layer.borderColor = UIColor.c.cpvn.cgColor // EPSystemToken.color(.primaryVariantNormal).cgColor
|
||
}
|
||
|
||
override var paddingInsets: UIEdgeInsets {
|
||
UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
|
||
}
|
||
|
||
override var intrinsicContentSize: CGSize{
|
||
return CGSize(width: textLabel.size.width + 32, height: chipButtonHeight)
|
||
}
|
||
|
||
override var isHighlighted: Bool {
|
||
didSet {
|
||
backgroundColor = isSelected
|
||
? (isHighlighted ? UIColor.c.cpp : UIColor.c.cpn)
|
||
: (isHighlighted ? UIColor.c.csep : UIColor.c.csen)
|
||
}
|
||
}
|
||
|
||
override var isSelected: Bool {
|
||
didSet {
|
||
backgroundColor = isSelected
|
||
? UIColor.c.cpn
|
||
: UIColor.c.csen
|
||
layer.borderWidth = isSelected ? 1 : 0
|
||
}
|
||
}
|
||
|
||
override var isEnabled: Bool {
|
||
didSet {
|
||
textLabel.textColor = isEnabled
|
||
? UIColor.c.ctpn
|
||
: UIColor.c.ctd
|
||
backgroundColor = isEnabled
|
||
? UIColor.c.csen
|
||
: UIColor.c.csed
|
||
}
|
||
}
|
||
}
|
||
|
||
class EPChipCenterIconButton: EPChipButton {
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
backgroundColor = .c.csen // EPSystemToken.color(.surfaceElementNormal)
|
||
layer.borderWidth = 0
|
||
defaultIconColor = .c.ctpn
|
||
|
||
addSubview(iconView)
|
||
iconView.snp.makeConstraints { make in
|
||
make.center.equalToSuperview()
|
||
// make.height.equalTo(chipButtonHeight)
|
||
}
|
||
}
|
||
|
||
override func setupTheme() {
|
||
super.setupTheme()
|
||
layer.borderColor = UIColor.c.cpvn.cgColor // EPSystemToken.color(.primaryVariantNormal).cgColor
|
||
}
|
||
|
||
override var iconSize: CGSize {
|
||
return .init(width: 20, height: 20)
|
||
}
|
||
|
||
override var isHighlighted: Bool {
|
||
didSet {
|
||
backgroundColor = isSelected
|
||
? (isHighlighted ? UIColor.c.cpp : UIColor.c.cpn)
|
||
: (isHighlighted ? UIColor.c.csep : UIColor.c.csen)
|
||
}
|
||
}
|
||
|
||
override var isSelected: Bool {
|
||
didSet {
|
||
backgroundColor = isSelected
|
||
? UIColor.c.cpn
|
||
: UIColor.c.csen
|
||
layer.borderWidth = isSelected ? 1 : 0
|
||
}
|
||
}
|
||
|
||
override var isEnabled: Bool {
|
||
didSet {
|
||
textLabel.textColor = isEnabled
|
||
? UIColor.c.ctpn
|
||
: UIColor.c.ctd
|
||
backgroundColor = isEnabled
|
||
? UIColor.c.csen
|
||
: UIColor.c.csed
|
||
}
|
||
}
|
||
|
||
override func layoutSubviews() {
|
||
super.layoutSubviews()
|
||
layer.cornerRadius = bounds.size.height * 0.5
|
||
}
|
||
}
|
||
|
||
// MARK: - EPChipDropdownButton
|
||
|
||
class EPChipDropdownButton: EPChipButton {
|
||
var needTransform: Bool = true
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
stack.spacing = 4
|
||
|
||
addSubview(stack)
|
||
stack.snp.makeConstraints { make in
|
||
make.edges.equalTo(self).inset(paddingInsets)
|
||
make.height.equalTo(chipButtonHeight)
|
||
}
|
||
|
||
stack.addArrangedSubview(textLabel)
|
||
stack.addArrangedSubview(iconView)
|
||
iconView.snp.makeConstraints { make in
|
||
make.size.equalTo(iconSize)
|
||
}
|
||
}
|
||
|
||
override func setupTheme() {
|
||
super.setupTheme()
|
||
textLabel.textColor = isSelected
|
||
? UIColor.c.cpvn
|
||
: UIColor.c.ctpn
|
||
iconView.image = isSelected ? selectedIcon : defaultIcon
|
||
}
|
||
|
||
override var paddingInsets: UIEdgeInsets {
|
||
UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 8)
|
||
}
|
||
|
||
override var iconSize: CGSize {
|
||
CGSize(width: 12, height: 12)
|
||
}
|
||
|
||
override var defaultIcon: UIImage? {
|
||
let color = UIColor.c.ctsn // EPSystemToken.color(.txtSecondaryNormal)
|
||
return MWIconFont.image(fromIconInt: iconCode.rawValue, size: iconSize, color: color, edgeInsets: .zero)
|
||
}
|
||
|
||
override var selectedIcon: UIImage? {
|
||
let color = UIColor.c.cpvn // EPSystemToken.color(.primaryVariantNormal)
|
||
return MWIconFont.image(fromIconInt: iconCode.rawValue, size: iconSize, color: color, edgeInsets: .zero)
|
||
}
|
||
|
||
override var isHighlighted: Bool {
|
||
didSet {
|
||
backgroundColor = isHighlighted ? UIColor.c.csep : nil
|
||
}
|
||
}
|
||
|
||
override var isSelected: Bool {
|
||
didSet {
|
||
textLabel.textColor = isSelected
|
||
? UIColor.c.cpvn
|
||
: UIColor.c.ctpn
|
||
|
||
UIView.animate(withDuration: 0.2) {
|
||
if self.needTransform {
|
||
self.iconView.transform = self.isSelected ? CGAffineTransform(rotationAngle: .pi) : .identity
|
||
}
|
||
self.iconView.image = self.isSelected ? self.selectedIcon : self.defaultIcon
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - EPChipRemovableButton
|
||
|
||
class EPChipRemovableButton: EPChipButton {
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
stack.spacing = 8
|
||
|
||
addSubview(stack)
|
||
stack.snp.makeConstraints { make in
|
||
make.edges.equalTo(self).inset(paddingInsets).priority(998)
|
||
make.height.equalTo(chipButtonHeight)
|
||
}
|
||
|
||
stack.addArrangedSubview(textLabel)
|
||
stack.addArrangedSubview(iconView)
|
||
iconView.snp.makeConstraints { make in
|
||
make.size.equalTo(iconSize)
|
||
}
|
||
}
|
||
|
||
override func setupTheme() {
|
||
super.setupTheme()
|
||
backgroundColor = .c.csen
|
||
iconView.image = defaultIcon
|
||
}
|
||
|
||
override var paddingInsets: UIEdgeInsets {
|
||
UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12)
|
||
}
|
||
|
||
override var iconSize: CGSize {
|
||
CGSize(width: 16, height: 16)
|
||
}
|
||
|
||
override var defaultIcon: UIImage? {
|
||
let color = UIColor.c.ctsn // EPSystemToken.color(.txtSecondaryNormal)
|
||
return MWIconFont.image(fromIconInt: iconCode.rawValue, size: iconSize, color: color, edgeInsets: .zero)
|
||
}
|
||
|
||
override var selectedIcon: UIImage? {
|
||
let color = UIColor.c.cpvn
|
||
return MWIconFont.image(fromIconInt: iconCode.rawValue, size: iconSize, color: color, edgeInsets: .zero)
|
||
}
|
||
|
||
override var isHighlighted: Bool {
|
||
didSet {
|
||
backgroundColor = isHighlighted
|
||
? UIColor.c.csep
|
||
: UIColor.c.csen
|
||
}
|
||
}
|
||
|
||
func widthPreCal(tagHeight: CGFloat) -> CGFloat {
|
||
let paddingInsets = self.paddingInsets
|
||
return textLabel.sizeThatFits(CGSize(width: .greatestFiniteMagnitude, height: tagHeight)).width +
|
||
paddingInsets.left + paddingInsets.right + iconSize.width + stack.spacing
|
||
}
|
||
|
||
func setupHasImageMode() {
|
||
stack.insertArrangedSubview(imageView, at: 0)
|
||
imageView.isHidden = false
|
||
imageView.snp.makeConstraints { make in
|
||
make.size.equalTo(imageSize)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - EPChipAssistButton
|
||
|
||
/// 图标在左,文字在右(用处eg:Interests添加页面Add按钮)
|
||
class EPChipAssistButton: EPChipButton {
|
||
var iconTextSpacing: CGFloat = 8 {
|
||
didSet {
|
||
stack.spacing = iconTextSpacing
|
||
}
|
||
}
|
||
|
||
var buttonHeight: CGFloat = 32 {
|
||
didSet {
|
||
layer.cornerRadius = buttonHeight * 0.5
|
||
stack.snp.updateConstraints { make in
|
||
make.height.equalTo(buttonHeight)
|
||
}
|
||
}
|
||
}
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
stack.spacing = 8
|
||
|
||
addSubview(stack)
|
||
stack.snp.makeConstraints { make in
|
||
make.edges.equalTo(self).inset(paddingInsets)
|
||
make.height.equalTo(chipButtonHeight)
|
||
}
|
||
|
||
stack.addArrangedSubview(iconView)
|
||
stack.addArrangedSubview(textLabel)
|
||
iconView.snp.makeConstraints { make in
|
||
make.size.equalTo(iconSize)
|
||
}
|
||
}
|
||
|
||
override func setupTheme() {
|
||
super.setupTheme()
|
||
backgroundColor = .c.csen // EPSystemToken.color(.surfaceElementNormal)
|
||
iconView.image = defaultIcon
|
||
}
|
||
|
||
override var paddingInsets: UIEdgeInsets {
|
||
UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12)
|
||
}
|
||
|
||
override var iconSize: CGSize {
|
||
CGSize(width: 16, height: 16)
|
||
}
|
||
|
||
override var defaultIcon: UIImage? {
|
||
let color = defaultIconColor ?? UIColor.c.ctsn // EPSystemToken.color(.txtSecondaryNormal)
|
||
return MWIconFont.image(fromIconInt: iconCode.rawValue, size: iconSize, color: color, edgeInsets: .zero)
|
||
}
|
||
|
||
// override var isHighlighted: Bool {
|
||
// didSet {
|
||
// backgroundColor = isHighlighted
|
||
// ? UIColor.c.csep
|
||
// : UIColor.c.csen
|
||
// }
|
||
// }
|
||
|
||
override var isSelected: Bool {
|
||
didSet {
|
||
backgroundColor = isSelected
|
||
? UIColor.c.cpn
|
||
: UIColor.c.csen
|
||
//layer.borderWidth = isSelected ? 1 : 0
|
||
}
|
||
}
|
||
|
||
func setDefaultImage(_ image: UIImage?) {
|
||
iconView.image = image
|
||
}
|
||
|
||
func setIconSize(_ size: CGSize) {
|
||
iconView.snp.updateConstraints { make in
|
||
make.size.equalTo(size)
|
||
}
|
||
}
|
||
|
||
func widthPreCal(tagHeight: CGFloat) -> CGFloat {
|
||
let paddingInsets = self.paddingInsets
|
||
return textLabel.sizeThatFits(CGSize(width: .greatestFiniteMagnitude, height: tagHeight)).width +
|
||
paddingInsets.left + paddingInsets.right + iconSize.width + stack.spacing
|
||
}
|
||
}
|
||
|
||
// MARK: - EPChipContrastButton:EPChipAssistButton
|
||
/// Like EPIconContrastTertiaryButton的加文字版本, 特别处理
|
||
class EPChipContrastButton:EPChipAssistButton {
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
backgroundColor = .c.csedn
|
||
buttonHeight = 48
|
||
defaultIconColor = .white
|
||
textLabel.font = .t.tll
|
||
}
|
||
|
||
override var paddingInsets: UIEdgeInsets {
|
||
UIEdgeInsets(top: 0, left: 32, bottom: 0, right: 32)
|
||
}
|
||
|
||
override var iconSize: CGSize {
|
||
CGSize(width: 24, height: 24)
|
||
}
|
||
}
|
||
|
||
|
||
// MARK: - EPChipImageIconButton
|
||
|
||
/// 左边image+中间文字+右边iconFont
|
||
class EPChipImageIconButton: EPChipFilterButton {
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
stack.spacing = 8
|
||
|
||
addSubview(stack)
|
||
stack.snp.makeConstraints { make in
|
||
make.edges.equalTo(self).inset(paddingInsets)
|
||
make.height.equalTo(chipButtonHeight)
|
||
}
|
||
|
||
stack.addArrangedSubview(imageView)
|
||
stack.addArrangedSubview(textLabel)
|
||
stack.addArrangedSubview(iconView)
|
||
imageView.snp.makeConstraints { make in
|
||
make.size.equalTo(imageSize)
|
||
}
|
||
iconView.snp.makeConstraints { make in
|
||
make.size.equalTo(iconSize)
|
||
}
|
||
|
||
imageView.isHidden = true
|
||
iconView.isHidden = true
|
||
}
|
||
|
||
override var paddingInsets: UIEdgeInsets {
|
||
UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
|
||
}
|
||
|
||
override var iconSize: CGSize {
|
||
CGSize(width: 16, height: 16)
|
||
}
|
||
|
||
override var imageSize: CGSize {
|
||
CGSize(width: 16, height: 16)
|
||
}
|
||
|
||
override var defaultIcon: UIImage? {
|
||
let color = UIColor.c.ctsn // EPSystemToken.color(.txtSecondaryNormal)
|
||
return MWIconFont.image(fromIconInt: iconCode.rawValue, size: iconSize, color: color, edgeInsets: .zero)
|
||
}
|
||
|
||
override var selectedIcon: UIImage? {
|
||
let color = UIColor.c.cswn // EPSystemToken.color(.surfaceWhiteNormal)
|
||
return MWIconFont.image(fromIconInt: iconCode.rawValue, size: iconSize, color: color, edgeInsets: .zero)
|
||
}
|
||
|
||
override var isSelected: Bool {
|
||
didSet {
|
||
iconView.image = isSelected ? selectedIcon : defaultIcon
|
||
imageView.image = isSelected ? selectedImage : defaultImage
|
||
}
|
||
}
|
||
|
||
func reloadPadding(showImage: Bool, showIcon: Bool) {
|
||
var leftPadding: CGFloat = 16
|
||
var rightPadding: CGFloat = 16
|
||
if showImage {
|
||
leftPadding = 12
|
||
}
|
||
if showIcon {
|
||
rightPadding = 12
|
||
}
|
||
stack.snp.remakeConstraints { make in
|
||
make.edges.equalTo(self).inset(UIEdgeInsets(top: 0, left: leftPadding, bottom: 0, right: rightPadding))
|
||
make.height.equalTo(chipButtonHeight)
|
||
}
|
||
}
|
||
}
|