352 lines
11 KiB
Swift
352 lines
11 KiB
Swift
//
|
||
// CLTextField.swift
|
||
// Crush
|
||
//
|
||
// Created by Leon on 2025/7/15.
|
||
//
|
||
|
||
import UIKit
|
||
|
||
class CLTextField: UITextField {
|
||
// MARK: - Properties
|
||
private var focusBorderColor: UIColor = .orange
|
||
private var errorBorderColor: UIColor = .red
|
||
private var nowBorderColor: UIColor = .clear
|
||
|
||
private var tapTopButtonAction: (() -> Void)?
|
||
|
||
/// 在此模式下,errorBorder 在新修改内容之前都会显示 border。
|
||
private var errorBorderShowMode: Bool = false
|
||
|
||
private var alwaysShowRightViewConfig: Bool = false
|
||
private var alwaysShowClearButtonIfHave: Bool = false
|
||
private var disableAllBorder: Bool = false
|
||
private var fakeTextFieldForInputTrigger: Bool = false
|
||
|
||
var cornerRadiusConfig:CGFloat = 8
|
||
|
||
// private var defaultTxt: String?
|
||
|
||
// Closures for delegate-like events (assumed from Objective-C)
|
||
var textDidChanged: ((CLTextField) -> Void)?
|
||
var textDidBeginEditing: ((CLTextField) -> Void)?
|
||
var textDidEndEditing: ((CLTextField) -> Void)?
|
||
var textShouldBeginEditing: ((CLTextField) -> Bool)?
|
||
|
||
// MARK: - Initialization
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
baseCommonSetup()
|
||
}
|
||
|
||
required init?(coder: NSCoder) {
|
||
super.init(coder: coder)
|
||
baseCommonSetup()
|
||
}
|
||
|
||
private func baseCommonSetup() {
|
||
// keyboardAppearance = .dark
|
||
// reloadInputViews()
|
||
setContentHuggingPriority(UILayoutPriority(244), for: .horizontal)
|
||
|
||
|
||
let leftView = UIView(frame: CGRect(x: 0, y: 0, width: 16, height: 16))
|
||
leftView.backgroundColor = .clear
|
||
self.leftViewMode = .always
|
||
self.leftView = leftView
|
||
|
||
// --- 3 events
|
||
addTarget(self, action: #selector(textDidChanged(_:)), for: .editingChanged)
|
||
NotificationCenter.default.addObserver(self, selector: #selector(notiTextDidBeginEditing), name: UITextField.textDidBeginEditingNotification, object: self)
|
||
NotificationCenter.default.addObserver(self, selector: #selector(notiTextDidEndEditing), name: UITextField.textDidEndEditingNotification, object: self)
|
||
NotificationCenter.default.addObserver(self, selector: #selector(notiTextShouldBeginEditing(_:)), name: UITextField.textDidBeginEditingNotification, object: self)
|
||
|
||
setupStandard()
|
||
}
|
||
|
||
// MARK: - Public Methods
|
||
func setupStandard() {
|
||
autocorrectionType = .no
|
||
backgroundColor = .c.csen
|
||
font = CLSystemToken.font(token: .tbl)
|
||
textColor = .white
|
||
|
||
focusBorderColor = .theme
|
||
errorBorderColor = .red
|
||
layer.borderWidth = 1
|
||
layer.borderColor = UIColor.clear.cgColor
|
||
|
||
layer.cornerRadius = cornerRadiusConfig
|
||
|
||
// NSLayoutConstraint.activate([
|
||
// heightAnchor.constraint(equalToConstant: 48)
|
||
// ])
|
||
|
||
snp.makeConstraints { make in
|
||
make.height.equalTo(48)
|
||
}
|
||
|
||
// default
|
||
setupRightClearButton()
|
||
}
|
||
|
||
func setupPlaceholder(_ placeholder: String) {
|
||
let font = UIFont.systemFont(ofSize: 15)
|
||
let color = UIColor.gray
|
||
let attributes: [NSAttributedString.Key: Any] = [
|
||
.font: font,
|
||
.foregroundColor: color
|
||
]
|
||
attributedPlaceholder = NSAttributedString(string: placeholder, attributes: attributes)
|
||
}
|
||
|
||
func setupTopButton(with action: (() -> Void)?) {
|
||
let topButton = UIButton(type: .custom)
|
||
topButton.backgroundColor = .clear
|
||
addSubview(topButton)
|
||
topButton.addTarget(self, action: #selector(tapTopButton), for: .touchUpInside)
|
||
tapTopButtonAction = action
|
||
|
||
// Using SnapKit or native Auto Layout
|
||
topButton.translatesAutoresizingMaskIntoConstraints = false
|
||
NSLayoutConstraint.activate([
|
||
topButton.topAnchor.constraint(equalTo: topAnchor),
|
||
topButton.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||
topButton.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||
topButton.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||
])
|
||
}
|
||
|
||
func showAlwayErrorBorder() {
|
||
errorBorderShowMode = true
|
||
layer.borderWidth = 1
|
||
layer.borderColor = errorBorderColor.cgColor
|
||
}
|
||
|
||
func hideErrorBorder() {
|
||
errorBorderShowMode = false
|
||
layer.borderColor = UIColor.clear.cgColor
|
||
}
|
||
|
||
func sendTextChangedNoti(){
|
||
NotificationCenter.default.post(
|
||
name: UITextField.textDidChangeNotification,
|
||
object: self)
|
||
}
|
||
|
||
// MARK: - Helper Methods
|
||
func setupRightClearButton() {
|
||
rightViewMode = .whileEditing
|
||
let rightView = UIView(frame: CGRect(x: 0, y: 0, width: 48, height: 48))
|
||
self.rightView = rightView
|
||
|
||
let clearButton = EPIconGhostSecondaryButton(radius: .none, iconSize: .small, iconCode: .delete02)
|
||
rightView.addSubview(clearButton)
|
||
clearButton.addTarget(self, action: #selector(tapClearButton), for: .touchUpInside)
|
||
clearButton.snp.makeConstraints { make in
|
||
make.size.equalTo(clearButton.bgImageSize())
|
||
make.centerY.equalToSuperview()
|
||
make.trailing.equalToSuperview().offset(-8)
|
||
}
|
||
}
|
||
|
||
func setupRightEyeButton() {
|
||
isSecureTextEntry = true
|
||
rightViewMode = .always
|
||
alwaysShowRightViewConfig = true
|
||
|
||
let rightView = UIView(frame: CGRect(x: 0, y: 0, width: 48, height: 48))
|
||
self.rightView = rightView
|
||
}
|
||
|
||
func setupRightRightArrowButton() -> UIButton? {
|
||
rightViewMode = .always
|
||
alwaysShowRightViewConfig = true
|
||
|
||
let rightView = UIView(frame: CGRect(x: 0, y: 0, width: 48, height: 48))
|
||
self.rightView = rightView
|
||
return nil
|
||
}
|
||
|
||
func setupRightViewEmpty() {
|
||
rightView = nil
|
||
}
|
||
|
||
func setupLeftCoinIconView() {
|
||
leftViewMode = .always
|
||
let leftView = UIView(frame: CGRect(x: 0, y: 0, width: 44, height: 40))
|
||
let iconView = UIImageView(frame: CGRect(x: 16, y: 12, width: 16, height: 16))
|
||
iconView.image = UIImage(named: "icon_16_diamond")
|
||
leftView.addSubview(iconView)
|
||
self.leftView = leftView
|
||
}
|
||
|
||
func setupLeftBlankView() {
|
||
leftViewMode = .always
|
||
let leftView = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 40))
|
||
self.leftView = leftView
|
||
}
|
||
|
||
func adjustRightViewMode() {
|
||
if alwaysShowRightViewConfig {
|
||
rightViewMode = .always
|
||
return
|
||
}
|
||
|
||
// 空数据不显示删除按钮
|
||
if text?.isEmpty ?? true {
|
||
rightViewMode = .never
|
||
} else {
|
||
rightViewMode = alwaysShowClearButtonIfHave ? .always : .whileEditing
|
||
}
|
||
}
|
||
|
||
// MARK: - Actions
|
||
@objc private func tapTopButton() {
|
||
tapTopButtonAction?()
|
||
}
|
||
|
||
@objc private func tapClearButton() {
|
||
text = ""
|
||
textDidChanged(self)
|
||
NotificationCenter.default.post(
|
||
name: UITextField.textDidChangeNotification,
|
||
object: self)
|
||
}
|
||
|
||
@objc private func tapRightArrowButton(_ button: UIButton) {
|
||
// Empty implementation as in original
|
||
}
|
||
|
||
// MARK: - Textfield Events
|
||
@objc private func textDidChanged(_ textField: UITextField) {
|
||
errorBorderShowMode = false
|
||
nowBorderColor = focusBorderColor
|
||
|
||
if !disableAllBorder && textField.isFirstResponder {
|
||
layer.borderColor = nowBorderColor.cgColor
|
||
}
|
||
|
||
adjustRightViewMode()
|
||
|
||
textDidChanged?(self)
|
||
}
|
||
|
||
@objc private func notiTextDidBeginEditing() {
|
||
if !errorBorderShowMode && !disableAllBorder {
|
||
layer.borderColor = focusBorderColor.cgColor
|
||
layer.borderWidth = 1
|
||
}
|
||
|
||
textDidBeginEditing?(self)
|
||
|
||
adjustRightViewMode()
|
||
}
|
||
|
||
@objc private func notiTextDidEndEditing() {
|
||
if !errorBorderShowMode {
|
||
layer.borderColor = nil
|
||
layer.borderWidth = 0
|
||
}
|
||
|
||
textDidEndEditing?(self)
|
||
}
|
||
|
||
@objc private func notiTextShouldBeginEditing(_ notification: Notification) {
|
||
if fakeTextFieldForInputTrigger {
|
||
resignFirstResponder()
|
||
}
|
||
|
||
textShouldBeginEditing?(self)
|
||
}
|
||
|
||
// MARK: - Setters
|
||
override var delegate: UITextFieldDelegate? {
|
||
get { super.delegate }
|
||
set {
|
||
if newValue !== self {
|
||
// print("请用头文件中的block回调") // Uncomment if assertion is needed
|
||
}
|
||
super.delegate = newValue
|
||
}
|
||
}
|
||
|
||
override var placeholder: String? {
|
||
didSet {
|
||
if let placeholder = placeholder {
|
||
setupPlaceholder(placeholder)
|
||
}
|
||
}
|
||
}
|
||
|
||
// var defaultTxt: String? {
|
||
// get { _defaultTxt }
|
||
// set {
|
||
// _defaultTxt = newValue
|
||
// super.text = newValue
|
||
// }
|
||
// }
|
||
|
||
override var text: String? {
|
||
didSet {
|
||
if text != oldValue {
|
||
textDidChanged(self)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Layout
|
||
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
|
||
var rightViewRect = super.rightViewRect(forBounds: bounds)
|
||
|
||
if #available(iOS 13.0, *) {
|
||
// fix iOS 13 rightView显示在最左边,和异常显示问题
|
||
let left = frame.size.width - (rightView?.frame.size.width ?? 0)
|
||
let top = round((frame.size.height - (rightView?.frame.size.height ?? 0)) / 2.0)
|
||
rightViewRect = CGRect(x: left, y: top, width: rightView?.frame.size.width ?? 0, height: rightView?.frame.size.height ?? 0)
|
||
}
|
||
|
||
return rightViewRect
|
||
}
|
||
|
||
override func layoutSubviews() {
|
||
super.layoutSubviews()
|
||
layer.cornerRadius = self.cornerRadiusConfig
|
||
}
|
||
|
||
deinit {
|
||
NotificationCenter.default.removeObserver(self)
|
||
}
|
||
}
|
||
|
||
extension CLTextField{
|
||
func switchToNaviSearchField(){
|
||
setupPlaceholder("Search")
|
||
font = .t.tbm
|
||
cornerRadiusConfig = 16// height: 32
|
||
snp.updateConstraints { make in
|
||
make.height.equalTo(32)
|
||
}
|
||
|
||
leftViewMode = .always
|
||
let leftView = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 32))
|
||
leftView.snp.makeConstraints { make in
|
||
make.size.equalTo(CGSize(width: 40, height: 32))
|
||
}
|
||
self.leftView = leftView
|
||
|
||
let image = MWIconFont.image(fromIcon: .search, size: CGSize(width: 12, height: 12), color: .white)
|
||
let icon = UIImageView(image: image)
|
||
leftView.addSubview(icon)
|
||
icon.snp.makeConstraints { make in
|
||
make.size.equalTo(CGSize(width: 12, height: 12))
|
||
make.centerY.equalToSuperview()
|
||
make.leading.equalToSuperview().offset(12)
|
||
}
|
||
|
||
setNeedsLayout()
|
||
layoutIfNeeded()
|
||
}
|
||
|
||
}
|