|
import UIKit |
|
|
|
extension UITableViewCell { |
|
// MARK: - Public API |
|
public var fw_onClearButtonTap: (() -> Void)? { |
|
get { return objc_getAssociatedObject(self, AssociatedKeys.onClearButtonTap) as? () -> Void } |
|
set { |
|
objc_setAssociatedObject(self, |
|
AssociatedKeys.onClearButtonTap, |
|
newValue, |
|
.OBJC_ASSOCIATION_RETAIN_NONATOMIC) |
|
} |
|
} |
|
|
|
@objc dynamic public var fw_isClearButtonHidden: Bool { |
|
get { return fw_clearButton.superview == nil } |
|
set { setClearButtonHidden(newValue) } |
|
} |
|
|
|
// MARK: - Private API |
|
private enum AssociatedKeys { |
|
// Use stable, immutable keys to avoid concurrency warnings |
|
@MainActor |
|
static let clearButton: UnsafeRawPointer = { |
|
UnsafeRawPointer(bitPattern: "fw_clearButton".hashValue)! |
|
}() |
|
@MainActor |
|
static let onClearButtonTap: UnsafeRawPointer = { |
|
UnsafeRawPointer(bitPattern: "fw_onClearButtonTap".hashValue)! |
|
}() |
|
} |
|
|
|
private enum Constants { |
|
static let imageInset: CGFloat = 10 |
|
static let sideMeasure: CGFloat = 40 |
|
static let clearImage = #imageLiteral(resourceName: "ico_cross_circle").withRenderingMode(.alwaysTemplate) |
|
} |
|
|
|
private var fw_clearButton: UIButton { |
|
if let readonlyButton = objc_getAssociatedObject(self, AssociatedKeys.clearButton) as? UIButton { |
|
return readonlyButton |
|
} |
|
|
|
let button = createClearButton() |
|
objc_setAssociatedObject(self, |
|
AssociatedKeys.clearButton, |
|
button, |
|
.OBJC_ASSOCIATION_RETAIN_NONATOMIC) |
|
return button |
|
} |
|
|
|
private func createClearButton() -> UIButton { |
|
let button = UIButton(type: .custom) |
|
|
|
if #available(iOS 15.0, *) { |
|
var config = UIButton.Configuration.plain() |
|
// Make the button square and let Auto Layout size it via constraints later |
|
config.contentInsets = NSDirectionalEdgeInsets( |
|
top: Constants.imageInset, |
|
leading: Constants.imageInset, |
|
bottom: Constants.imageInset, |
|
trailing: Constants.imageInset |
|
) |
|
config.image = Constants.clearImage |
|
config.baseForegroundColor = UIColor(named: "clr_fieldworkGreen") ?? .systemGreen |
|
// Keep aspect fit-like appearance |
|
config.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(scale: .medium) |
|
button.configuration = config |
|
// Ensure template rendering uses tintColor |
|
button.tintColor = UIColor(named: "clr_fieldworkGreen") ?? .systemGreen |
|
} else { |
|
button.setImage(Constants.clearImage, for: .normal) |
|
button.imageView?.tintColor = UIColor(named: "clr_fieldworkGreen") ?? .systemGreen |
|
// Deprecated on iOS 15+, but still valid before that |
|
button.imageEdgeInsets = UIEdgeInsets(top: Constants.imageInset, |
|
left: Constants.imageInset, |
|
bottom: Constants.imageInset, |
|
right: Constants.imageInset) |
|
button.contentMode = .scaleAspectFit |
|
button.tintColor = UIColor(named: "clr_fieldworkGreen") ?? .systemGreen |
|
} |
|
|
|
button.addTarget(self, action: #selector(clearButtonTap), for: .touchUpInside) |
|
return button |
|
} |
|
|
|
private func setClearButtonHidden(_ isHidden: Bool) { |
|
let constraints: [NSLayoutConstraint] = [ |
|
fw_clearButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), |
|
fw_clearButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), |
|
fw_clearButton.widthAnchor.constraint(equalToConstant: Constants.sideMeasure), |
|
fw_clearButton.heightAnchor.constraint(equalToConstant: Constants.sideMeasure) |
|
] |
|
fw_clearButton.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
if isHidden { |
|
fw_clearButton.removeFromSuperview() |
|
NSLayoutConstraint.deactivate(constraints) |
|
} else { |
|
if fw_clearButton.superview == nil { |
|
contentView.addSubview(fw_clearButton) |
|
NSLayoutConstraint.activate(constraints) |
|
} |
|
} |
|
} |
|
|
|
@objc private func clearButtonTap() { fw_onClearButtonTap?() } |
|
} |