379 lines
12 KiB
Swift
379 lines
12 KiB
Swift
//
|
|
// TitleTextField.swift
|
|
// Crush
|
|
//
|
|
// Created by Leon on 2025/7/18.
|
|
//
|
|
|
|
import SnapKit
|
|
import UIKit
|
|
|
|
class TitleTextField: UIView {
|
|
// MARK: - Properties
|
|
|
|
var titleLabel: UILabel
|
|
var textfield: CLTextField
|
|
var supportLabel: UILabel
|
|
|
|
var placeholder: String? {
|
|
didSet {
|
|
textfield.placeholder = placeholder
|
|
}
|
|
}
|
|
var errorMsg: String?
|
|
var minLimit: Int = 0
|
|
var maxLimit: Int = 0 {
|
|
didSet{
|
|
textfield.limit.maxCharacterNumber = maxLimit
|
|
}
|
|
}
|
|
|
|
private var stackV: UIStackView
|
|
private var tapQueryBlock: (() -> Void)?
|
|
|
|
// MARK: - Initialization
|
|
|
|
override init(frame: CGRect) {
|
|
titleLabel = UILabel()
|
|
textfield = CLTextField()
|
|
supportLabel = UILabel()
|
|
stackV = UIStackView()
|
|
|
|
super.init(frame: frame)
|
|
|
|
initialViews()
|
|
setupEvent()
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// MARK: - Setup
|
|
|
|
private func initialViews() {
|
|
// Configure stack view
|
|
stackV.axis = .vertical
|
|
stackV.alignment = .leading
|
|
stackV.spacing = 12
|
|
addSubview(stackV)
|
|
stackV.snp.makeConstraints { make in
|
|
make.top.bottom.leading.trailing.equalTo(self)
|
|
}
|
|
|
|
// Configure title label
|
|
titleLabel.font = CLSystemToken.typography(token: .tlm).font! // EPSystemToken.typography(.txtLabelM).font
|
|
titleLabel.textColor = .c.ctpn
|
|
|
|
// Configure text field
|
|
|
|
|
|
// Configure support label
|
|
supportLabel.numberOfLines = 0
|
|
supportLabel.font = CLSystemToken.typography(token: .tbs).font!
|
|
supportLabel.textColor = .c.ctsn
|
|
|
|
// Default state
|
|
supportLabel.isHidden = true
|
|
|
|
// Add subviews to stack
|
|
stackV.addArrangedSubview(titleLabel)
|
|
stackV.addArrangedSubview(textfield)
|
|
stackV.addArrangedSubview(supportLabel)
|
|
|
|
// Additional constraints
|
|
titleLabel.snp.makeConstraints { make in
|
|
make.right.lessThanOrEqualTo(stackV)
|
|
}
|
|
|
|
textfield.snp.makeConstraints { make in
|
|
make.leading.trailing.equalTo(stackV)
|
|
}
|
|
}
|
|
|
|
private func setupEvent() {
|
|
// No events in the original, kept as a placeholder
|
|
|
|
textfield.addTarget(self, action: #selector(textDidChanged(_:)), for: .editingChanged)
|
|
}
|
|
|
|
// MARK: - Public Methods
|
|
|
|
func showNormalSupportMsg(_ string: String) {
|
|
supportLabel.isHidden = false
|
|
supportLabel.textColor = .c.ctsn // EPSystemToken.tsn
|
|
supportLabel.text = string
|
|
}
|
|
|
|
func showWarningMsg(_ string: String) {
|
|
supportLabel.isHidden = false
|
|
supportLabel.textColor = .c.cwvn // EPSystemToken.wvn
|
|
supportLabel.text = string
|
|
}
|
|
|
|
func showErrorMsg(_ string: String) {
|
|
supportLabel.isHidden = false
|
|
supportLabel.textColor = .c.civn // EPSystemToken.color(.importantVariantNormal)
|
|
supportLabel.text = string
|
|
textfield.showAlwayErrorBorder()
|
|
}
|
|
|
|
func hideErrorInfo() {
|
|
supportLabel.isHidden = true
|
|
textfield.hideErrorBorder()
|
|
}
|
|
|
|
func titleAppendOptionalLabel() {
|
|
// ...
|
|
}
|
|
|
|
func setupQueryBtn(block: @escaping () -> Void) {
|
|
tapQueryBlock = block
|
|
let button = UIButton(type: .custom)
|
|
button.setImage(UIImage(named: "query_lightpurple"), for: .normal)
|
|
button.contentMode = .center
|
|
addSubview(button)
|
|
|
|
let label = titleLabel
|
|
button.snp.makeConstraints { make in
|
|
make.centerY.equalTo(label)
|
|
make.left.equalTo(label.snp.right).offset(-4)
|
|
make.size.equalTo(CGSize(width: 44, height: 44))
|
|
}
|
|
|
|
button.addTarget(self, action: #selector(queryButtonTapped), for: .touchUpInside)
|
|
}
|
|
|
|
func setupEmailMode() {
|
|
textfield.autocapitalizationType = .none
|
|
textfield.autocorrectionType = .no
|
|
textfield.keyboardType = .emailAddress
|
|
textfield.placeholder = NSLocalizedString("email", comment: "")
|
|
textfield.limit.maxCharacterNumber = 50
|
|
}
|
|
|
|
func setupPasswordMode() {
|
|
textfield.autocapitalizationType = .none
|
|
textfield.autocorrectionType = .no
|
|
textfield.isSecureTextEntry = true
|
|
textfield.keyboardType = .asciiCapable
|
|
textfield.placeholder = NSLocalizedString("password", comment: "")
|
|
textfield.limit.maxCharacterNumber = 15
|
|
textfield.setupRightEyeButton()
|
|
clipsToBounds = true
|
|
}
|
|
|
|
func verifyPsswordDataValid(isFinalStep: Bool) -> Bool {
|
|
let text = textfield.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
|
|
if text.isEmpty {
|
|
if isFinalStep || !textfield.isFirstResponder {
|
|
showErrorMsg(NSLocalizedString("hint_enter_password", comment: ""))
|
|
} else {
|
|
hideErrorInfo()
|
|
}
|
|
return false
|
|
}
|
|
|
|
if text.count < 6 || text.count > 15 {
|
|
if isFinalStep || !textfield.isFirstResponder || text.count > 15 {
|
|
showErrorMsg(NSLocalizedString("password_error_info", comment: ""))
|
|
} else {
|
|
hideErrorInfo()
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Check for digits
|
|
let numPattern = ".*\\d+.*"
|
|
let numPred = NSPredicate(format: "SELF MATCHES %@", numPattern)
|
|
if !numPred.evaluate(with: text) {
|
|
print("Password must contain digits")
|
|
if isFinalStep || !textfield.isFirstResponder {
|
|
showErrorMsg(NSLocalizedString("toast_password_need_6_15_characters", comment: ""))
|
|
} else {
|
|
hideErrorInfo()
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Check for lowercase letters
|
|
let lowerPattern = ".*[a-z]+.*"
|
|
let lowerPred = NSPredicate(format: "SELF MATCHES %@", lowerPattern)
|
|
if !lowerPred.evaluate(with: text) {
|
|
print("Password must contain lowercase letters")
|
|
if isFinalStep || !textfield.isFirstResponder {
|
|
showErrorMsg(NSLocalizedString("toast_password_need_6_15_characters", comment: ""))
|
|
} else {
|
|
hideErrorInfo()
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Check for uppercase letters
|
|
let upperPattern = ".*[A-Z]+.*"
|
|
let upperPred = NSPredicate(format: "SELF MATCHES %@", upperPattern)
|
|
if !upperPred.evaluate(with: text) {
|
|
print("Password must contain uppercase letters")
|
|
if isFinalStep || !textfield.isFirstResponder {
|
|
showErrorMsg(NSLocalizedString("toast_password_need_6_15_characters", comment: ""))
|
|
} else {
|
|
hideErrorInfo()
|
|
}
|
|
return false
|
|
}
|
|
|
|
hideErrorInfo()
|
|
return true
|
|
}
|
|
|
|
func verifyOldPsswordDataValid(isFinalStep: Bool) -> Bool {
|
|
let text = textfield.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
|
|
if text.isEmpty {
|
|
if isFinalStep || !textfield.isFirstResponder {
|
|
showErrorMsg(NSLocalizedString("hint_enter_password", comment: ""))
|
|
} else {
|
|
hideErrorInfo()
|
|
}
|
|
return false
|
|
}
|
|
|
|
if text.count < 6 || text.count > 15 {
|
|
if isFinalStep || !textfield.isFirstResponder {
|
|
showErrorMsg(NSLocalizedString("password_error_info", comment: ""))
|
|
} else {
|
|
hideErrorInfo()
|
|
}
|
|
return false
|
|
}
|
|
|
|
hideErrorInfo()
|
|
return true
|
|
}
|
|
|
|
func verifyEmailDataValid(isFinalStep: Bool) -> Bool {
|
|
let text = textfield.text ?? ""
|
|
|
|
if text.isEmpty {
|
|
if isFinalStep || !textfield.isFirstResponder {
|
|
showErrorMsg(NSLocalizedString("hint_enter_email", comment: ""))
|
|
} else {
|
|
hideErrorInfo()
|
|
}
|
|
return false
|
|
}
|
|
|
|
if !text.isValidEmail {
|
|
if isFinalStep || !textfield.isFirstResponder {
|
|
showErrorMsg(NSLocalizedString("email_format_incorrect", comment: ""))
|
|
} else {
|
|
hideErrorInfo()
|
|
}
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func verifyNewStylePassword(isFinalStep: Bool) -> Bool {
|
|
let text = textfield.text ?? ""
|
|
let textEmpty = text.isEmpty
|
|
|
|
var condition1 = true
|
|
var condition2 = true
|
|
var condition3 = true
|
|
|
|
// Check length
|
|
if text.count < 6 || text.count > 15 {
|
|
condition1 = false
|
|
}
|
|
|
|
// Check for digits
|
|
let numPattern = ".*\\d+.*"
|
|
let numPred = NSPredicate(format: "SELF MATCHES %@", numPattern)
|
|
if !numPred.evaluate(with: text) {
|
|
print("Password must contain digits")
|
|
condition2 = false
|
|
}
|
|
|
|
// Check for lowercase letters
|
|
let lowerPattern = ".*[a-z]+.*"
|
|
let lowerPred = NSPredicate(format: "SELF MATCHES %@", lowerPattern)
|
|
if !lowerPred.evaluate(with: text) {
|
|
print("Password must contain lowercase letters")
|
|
condition3 = false
|
|
}
|
|
|
|
// Check for uppercase letters
|
|
let upperPattern = ".*[A-Z]+.*"
|
|
let upperPred = NSPredicate(format: "SELF MATCHES %@", upperPattern)
|
|
if !upperPred.evaluate(with: text) {
|
|
print("Password must contain uppercase letters")
|
|
condition3 = false
|
|
}
|
|
|
|
let paragraphStyle = NSMutableParagraphStyle()
|
|
paragraphStyle.lineSpacing = 4
|
|
|
|
let normalAttributes: [NSAttributedString.Key: Any] = [
|
|
.foregroundColor: UIColor.c.ctsn,
|
|
.font: CLSystemToken.typography(token: .tbs).font!,
|
|
.paragraphStyle: paragraphStyle,
|
|
]
|
|
|
|
let condition1String = "• \(NSLocalizedString("password_count_greater_than_6", comment: ""))"
|
|
let condition2String = "• \(NSLocalizedString("password_contain_numbers", comment: ""))"
|
|
let condition3String = "• \(NSLocalizedString("password_contain_upper_lower_letters", comment: ""))"
|
|
let full = "\(condition1String)\n\(condition2String)\n\(condition3String)"
|
|
|
|
let attributedString = NSMutableAttributedString(string: full, attributes: normalAttributes)
|
|
|
|
if !textEmpty {
|
|
let greenAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.c.cpvn] // EPSystemToken.color(.positiveVariantNormal)
|
|
let redAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.c.civn] // EPSystemToken.color(.importantVariantNormal)
|
|
|
|
let range1 = (full as NSString).range(of: condition1String)
|
|
let range2 = (full as NSString).range(of: condition2String)
|
|
let range3 = (full as NSString).range(of: condition3String)
|
|
|
|
attributedString.addAttributes(condition1 ? greenAttributes : redAttributes, range: range1)
|
|
attributedString.addAttributes(condition2 ? greenAttributes : redAttributes, range: range2)
|
|
attributedString.addAttributes(condition3 ? greenAttributes : redAttributes, range: range3)
|
|
}
|
|
|
|
supportLabel.isHidden = false
|
|
supportLabel.attributedText = attributedString
|
|
|
|
return condition1 && condition2 && condition3
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
@objc private func textDidChanged(_ textField: UITextField) {
|
|
|
|
if (textField.text ?? "").count < minLimit{
|
|
if let msg = errorMsg{
|
|
showErrorMsg(msg)
|
|
}
|
|
}else{
|
|
hideErrorInfo()
|
|
}
|
|
}
|
|
|
|
@objc private func queryButtonTapped() {
|
|
tapQueryBlock?()
|
|
}
|
|
|
|
// MARK: - Responder
|
|
|
|
override func becomeFirstResponder() -> Bool {
|
|
textfield.becomeFirstResponder()
|
|
}
|
|
|
|
override func resignFirstResponder() -> Bool {
|
|
textfield.resignFirstResponder()
|
|
}
|
|
}
|