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()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|