
主要变更: 1. 新增 EPLoginViewController 和 EPLoginTypesViewController,提供新的登录界面和功能。 2. 引入 EPLoginInputView 和 EPLoginButton 组件,支持输入框和按钮的自定义。 3. 实现 EPLoginService 和 EPLoginManager,封装登录逻辑和 API 请求。 4. 添加 EPLoginConfig 和 EPLoginState,统一配置和状态管理。 5. 更新 Bridging Header,确保 Swift 和 Objective-C 代码的互操作性。 此更新旨在提升用户登录体验,简化登录流程,并提供更好的代码结构和可维护性。
303 lines
9.2 KiB
Swift
303 lines
9.2 KiB
Swift
//
|
||
// 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
|
||
}
|
||
|
||
/// 输入框代理
|
||
protocol EPLoginInputViewDelegate: AnyObject {
|
||
func inputViewDidRequestCode(_ inputView: EPLoginInputView)
|
||
func inputViewDidSelectArea(_ inputView: EPLoginInputView)
|
||
}
|
||
|
||
/// 登录输入框组件
|
||
class EPLoginInputView: UIView {
|
||
|
||
// MARK: - Properties
|
||
|
||
weak var delegate: EPLoginInputViewDelegate?
|
||
|
||
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
|
||
|
||
// 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.inputText
|
||
inputTextField.font = .systemFont(ofSize: 14)
|
||
inputTextField.tintColor = EPLoginConfig.Colors.primary
|
||
stackView.addArrangedSubview(inputTextField)
|
||
|
||
inputTextField.snp.makeConstraints { make in
|
||
make.height.equalTo(stackView)
|
||
}
|
||
}
|
||
|
||
private func setupEyeButton() {
|
||
eyeButton.setImage(UIImage(systemName: "eye.slash"), for: .normal)
|
||
eyeButton.setImage(UIImage(systemName: "eye"), for: .selected)
|
||
eyeButton.tintColor = EPLoginConfig.Colors.icon
|
||
eyeButton.addTarget(self, action: #selector(handleEyeTap), for: .touchUpInside)
|
||
stackView.addArrangedSubview(eyeButton)
|
||
|
||
eyeButton.snp.makeConstraints { make in
|
||
make.width.equalTo(30)
|
||
}
|
||
}
|
||
|
||
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
|
||
if let iconName = config.icon {
|
||
iconImageView.image = UIImage(systemName: iconName)
|
||
iconImageView.isHidden = false
|
||
} else {
|
||
iconImageView.isHidden = true
|
||
}
|
||
|
||
// Placeholder
|
||
inputTextField.placeholder = config.placeholder
|
||
|
||
// 密码模式
|
||
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 = ""
|
||
}
|
||
|
||
// 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)
|
||
}
|
||
}
|
||
|