Visual_Novel_iOS/crush/Crush/Src/Components/UI/TextViews/TitleTextField.swift

379 lines
12 KiB
Swift
Raw Normal View History

2025-10-09 10:29:35 +00:00
//
// 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()
}
}