Files
real-e-party-iOS/YuMi/E-P/NewLogin/Views/EPLoginInputView.swift
edwinQQQ 9466b65b40 refactor: 更新 EPLoginTypesViewController 以简化表单验证和错误处理
主要变更:
1. 将 EPLoginTypesViewController 继承自 BaseViewController,提升代码结构。
2. 简化表单验证逻辑,仅检查输入是否为空,减少对 EPLoginValidator 的依赖。
3. 更新错误处理方式,使用 showErrorToast 替代 showAlert,提升用户体验。
4. 在 EPLoginService 中直接使用字符串常量替代 grantType 变量,简化代码。

此更新旨在提升代码可读性和用户交互体验,确保登录流程更加流畅。
2025-10-14 16:47:47 +08:00

319 lines
9.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// EPLoginInputView.swift
// YuMi
//
// Created by AI on 2025-01-27.
// -
//
import UIKit
import SnapKit
///
struct EPLoginInputConfig {
var showAreaCode: Bool = false
var showCodeButton: Bool = false
var isSecure: Bool = false
var icon: String?
var placeholder: String
var keyboardType: UIKeyboardType = .default
}
///
protocol EPLoginInputViewDelegate: AnyObject {
func inputViewDidRequestCode(_ inputView: EPLoginInputView)
func inputViewDidSelectArea(_ inputView: EPLoginInputView)
}
///
class EPLoginInputView: UIView {
// MARK: - Properties
weak var delegate: EPLoginInputViewDelegate?
///
var onTextChanged: ((String) -> Void)?
private let stackView = UIStackView()
//
private let areaStackView = UIStackView()
private let areaCodeButton = UIButton(type: .custom)
private let areaArrowImageView = UIImageView()
private let areaTapButton = UIButton(type: .custom)
//
private let inputTextField = UITextField()
private let iconImageView = UIImageView()
//
private let eyeButton = UIButton(type: .custom)
//
private let codeButton = UIButton(type: .custom)
//
private var timer: DispatchSourceTimer?
private var countdownSeconds = 60
private var isCountingDown = false
//
private var config: EPLoginInputConfig?
///
var text: String {
return inputTextField.text ?? ""
}
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
stopCountdown()
}
// MARK: - Setup
private func setupUI() {
backgroundColor = EPLoginConfig.Colors.inputBackground
layer.cornerRadius = EPLoginConfig.Layout.inputCornerRadius
layer.borderWidth = EPLoginConfig.Layout.inputBorderWidth
layer.borderColor = EPLoginConfig.Colors.inputBorder.cgColor
// Main StackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
stackView.spacing = 8
addSubview(stackView)
setupAreaCodeView()
setupInputTextField()
setupEyeButton()
setupCodeButton()
stackView.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(EPLoginConfig.Layout.inputHorizontalPadding)
make.trailing.equalToSuperview().offset(-EPLoginConfig.Layout.inputHorizontalPadding)
make.top.bottom.equalToSuperview()
}
//
areaStackView.isHidden = true
eyeButton.isHidden = true
codeButton.isHidden = true
iconImageView.isHidden = true
}
private func setupAreaCodeView() {
// StackView
areaStackView.axis = .horizontal
areaStackView.alignment = .center
areaStackView.distribution = .fill
areaStackView.spacing = 8
//
areaCodeButton.setTitle("+86", for: .normal)
areaCodeButton.setTitleColor(EPLoginConfig.Colors.inputText, for: .normal)
areaCodeButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
areaCodeButton.isUserInteractionEnabled = false
//
areaArrowImageView.image = kImage("login_area_arrow")
areaArrowImageView.contentMode = .scaleAspectFit
areaArrowImageView.isUserInteractionEnabled = false
//
areaTapButton.addTarget(self, action: #selector(handleAreaTap), for: .touchUpInside)
areaStackView.addSubview(areaTapButton)
areaStackView.addArrangedSubview(areaCodeButton)
areaStackView.addArrangedSubview(areaArrowImageView)
stackView.addArrangedSubview(areaStackView)
areaTapButton.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
areaCodeButton.snp.makeConstraints { make in
make.width.lessThanOrEqualTo(60)
make.height.equalTo(stackView)
}
areaArrowImageView.snp.makeConstraints { make in
make.width.equalTo(12)
make.height.equalTo(8)
}
}
private func setupInputTextField() {
// Icon ()
iconImageView.contentMode = .scaleAspectFit
iconImageView.tintColor = EPLoginConfig.Colors.icon
stackView.addArrangedSubview(iconImageView)
iconImageView.snp.makeConstraints { make in
make.size.equalTo(EPLoginConfig.Layout.inputIconSize)
}
// TextField
inputTextField.textColor = EPLoginConfig.Colors.textLight
inputTextField.font = .systemFont(ofSize: 14)
inputTextField.tintColor = EPLoginConfig.Colors.textLight
inputTextField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
stackView.addArrangedSubview(inputTextField)
inputTextField.snp.makeConstraints { make in
make.height.equalTo(stackView)
}
}
@objc private func textFieldDidChange() {
onTextChanged?(inputTextField.text ?? "")
}
private func setupEyeButton() {
eyeButton.setImage(kImage(EPLoginConfig.Images.iconPasswordUnsee), for: .normal)
eyeButton.setImage(kImage(EPLoginConfig.Images.iconPasswordSee), for: .selected)
eyeButton.addTarget(self, action: #selector(handleEyeTap), for: .touchUpInside)
stackView.addArrangedSubview(eyeButton)
eyeButton.snp.makeConstraints { make in
make.size.equalTo(24)
}
}
private func setupCodeButton() {
codeButton.setTitle(YMLocalizedString("XPLoginInputView0"), for: .normal)
codeButton.setTitleColor(.white, for: .normal)
codeButton.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
codeButton.titleLabel?.textAlignment = .center
codeButton.titleLabel?.numberOfLines = 2
codeButton.layer.cornerRadius = EPLoginConfig.Layout.codeButtonHeight / 2
codeButton.backgroundColor = EPLoginConfig.Colors.codeButtonBackground
codeButton.addTarget(self, action: #selector(handleCodeTap), for: .touchUpInside)
stackView.addArrangedSubview(codeButton)
codeButton.snp.makeConstraints { make in
make.width.equalTo(EPLoginConfig.Layout.codeButtonWidth)
make.height.equalTo(EPLoginConfig.Layout.codeButtonHeight)
}
}
// MARK: - Configuration
///
func configure(with config: EPLoginInputConfig) {
self.config = config
//
areaStackView.isHidden = !config.showAreaCode
// Icon - 使
iconImageView.isHidden = true
// Placeholder60%
inputTextField.attributedPlaceholder = NSAttributedString(
string: config.placeholder,
attributes: [NSAttributedString.Key.foregroundColor: UIColor.white.withAlphaComponent(0.6)]
)
//
inputTextField.keyboardType = config.keyboardType
//
inputTextField.isSecureTextEntry = config.isSecure
eyeButton.isHidden = !config.isSecure
//
codeButton.isHidden = !config.showCodeButton
}
///
func setAreaCode(_ code: String) {
areaCodeButton.setTitle(code, for: .normal)
}
///
func clearInput() {
inputTextField.text = ""
}
///
func displayKeyboard() {
inputTextField.becomeFirstResponder()
}
// MARK: - Actions
@objc private func handleAreaTap() {
delegate?.inputViewDidSelectArea(self)
}
@objc private func handleEyeTap() {
eyeButton.isSelected.toggle()
inputTextField.isSecureTextEntry = !eyeButton.isSelected
}
@objc private func handleCodeTap() {
guard !isCountingDown else { return }
delegate?.inputViewDidRequestCode(self)
}
// MARK: - Countdown
///
func startCountdown() {
guard !isCountingDown else { return }
isCountingDown = true
countdownSeconds = 60
codeButton.isEnabled = false
codeButton.backgroundColor = EPLoginConfig.Colors.iconDisabled
let queue = DispatchQueue.main
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now(), repeating: 1.0)
timer.setEventHandler { [weak self] in
guard let self = self else { return }
self.countdownSeconds -= 1
if self.countdownSeconds <= 0 {
self.stopCountdown()
self.codeButton.setTitle(YMLocalizedString("XPLoginInputView1"), for: .normal)
} else {
self.codeButton.setTitle("\(self.countdownSeconds)s", for: .normal)
}
}
timer.resume()
self.timer = timer
}
///
func stopCountdown() {
guard let timer = timer else { return }
timer.cancel()
self.timer = nil
isCountingDown = false
codeButton.isEnabled = true
codeButton.backgroundColor = EPLoginConfig.Colors.codeButtonBackground
codeButton.setTitle(YMLocalizedString("XPLoginInputView0"), for: .normal)
}
}