Swift实现OTP重发按钮60秒倒计时功能技术求助
解决OTP重发倒计时的实现问题
我帮你调整了代码,实现60秒倒计时功能,同时优化了Timer管理和按钮状态控制,确保体验流畅:
import UIKit class RegistrationViewController: UIViewController, UITextFieldDelegate { //MARK:- Outlets @IBOutlet weak var nameTextField: UITextField! @IBOutlet weak var phoneNumTextField: UITextField! @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var conformPasswordTextField: UITextField! @IBOutlet weak var otpTextField: UITextField! @IBOutlet weak var registerButton: UIButton! @IBOutlet weak var sendOtpButton: UIButton! @IBOutlet weak var otpcountLabel: UILabel! @IBOutlet weak var resendButn: UIButton! var otpField: Int? var otpTimer: Timer? // 改成可选类型,方便管理 var totalTime = 60 // 改为60秒倒计时 override func viewDidLoad() { super.viewDidLoad() self.phoneNumTextField.keyboardType = .phonePad otpTextField.isHidden = true resendButn.isHidden = true resendButn.setTitle("重发OTP", for: .normal) // 初始化按钮标题 } @IBAction func registerButton(_ sender: Any) { guard let name = nameTextField.text, !name.isEmpty, let phone = phoneNumTextField.text, !phone.isEmpty, let password = passwordTextField.text, !password.isEmpty, let confirmPwd = conformPasswordTextField.text, !confirmPwd.isEmpty else { AlertFun.ShowAlert(title: "提示", message: "请填写所有必填字段", in: self) return } // 验证密码一致性 if password != confirmPwd { AlertFun.ShowAlert(title: "提示", message: "两次输入的密码不一致", in: self) return } registerButton.isHidden = true sendOtpButton.isHidden = false otpTextField.isHidden = false resendButn.isHidden = false // 启动倒计时 startOtpCountdown() // 调用注册接口,后端返回OTP并发送到手机 registerService() } @IBAction func sendOTPButton(_ sender: Any) { otpService() } @IBAction func resendOtpButn(_ sender: Any) { print("重新发送OTP") // 重新启动倒计时 startOtpCountdown() // 这里应该调用单独的发送OTP接口,而不是注册接口! sendOtpOnlyService() } // 封装倒计时启动逻辑,避免重复代码 private func startOtpCountdown() { // 先停止之前的计时器,防止多个计时器同时运行 otpTimer?.invalidate() totalTime = 60 // 重置倒计时时长 updateResendButtonState(isCounting: true) // 启动新的计时器 otpTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCountdown), userInfo: nil, repeats: true) } @objc private func updateCountdown() { if totalTime > 0 { totalTime -= 1 resendButn.setTitle("\(totalTime)秒后重发", for: .normal) } else { otpTimer?.invalidate() updateResendButtonState(isCounting: false) } } // 统一更新重发按钮的状态 private func updateResendButtonState(isCounting: Bool) { resendButn.isEnabled = !isCounting if isCounting { resendButn.setTitle("\(totalTime)秒后重发", for: .normal) } else { resendButn.setTitle("重发OTP", for: .normal) } } //MARK:- Service part @objc func registerService(){ print("执行注册请求") let parameters: [String: Any] = [ "mobile_number": Int(phoneNumTextField.text ?? "") as Any, "email": emailTextField.text as Any, "password": passwordTextField.text as Any, "name": nameTextField.text as Any ] guard let url = URL(string: "https://dev.com/webservices/register") else { return } var req = URLRequest(url: url) req.httpMethod = "POST" req.addValue("application/json", forHTTPHeaderField: "Content-Type") // 修复拼写错误:Contet-Type -> Content-Type req.addValue("application/json", forHTTPHeaderField: "Accept") guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else { return } req.httpBody = httpBody let session = URLSession.shared session.dataTask(with: req) { [weak self] data, response, error in guard let self = self else { return } if let error = error { print("注册请求错误: \(error)") return } guard let data = data else { print("未获取到注册响应数据") return } do { let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] print("注册响应: \(json)") self.otpField = json["otp"] as? Int // 可选:自动填充OTP用于测试(正式环境建议移除) DispatchQueue.main.async { self.otpTextField.text = self.otpField.map(String.init) } } catch { print("解析注册响应错误: \(error)") } }.resume() } @objc func otpService(){ guard let otpText = otpTextField.text, !otpText.isEmpty, let phone = phoneNumTextField.text, !phone.isEmpty else { DispatchQueue.main.async { AlertFun.ShowAlert(title: "", message: "请输入OTP", in: self) } return } let parameters: [String: Any] = [ "mobile_number": phone, "otp": otpText ] guard let url = URL(string: "https://dev.com/webservices/otpverify") else { return } // 修复多余的// var req = URLRequest(url: url) req.httpMethod = "POST" req.addValue("application/json", forHTTPHeaderField: "Content-Type") // 修复拼写错误 req.addValue("application/json", forHTTPHeaderField: "Accept") guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else { return } req.httpBody = httpBody let session = URLSession.shared session.dataTask(with: req) { [weak self] data, response, error in guard let self = self else { return } if let error = error { print("OTP验证请求错误: \(error)") return } guard let data = data else { print("未获取到OTP验证响应数据") return } do { let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] print("OTP验证响应: \(json)") DispatchQueue.main.async { guard let expectedOtp = self.otpField else { AlertFun.ShowAlert(title: "", message: "OTP获取失败,请重试", in: self) return } if otpText == String(expectedOtp) { print("注册成功") let loginVC = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController self.present(loginVC, animated: true) } else { AlertFun.ShowAlert(title: "", message: "无效的OTP", in: self) } } } catch { print("解析OTP验证响应错误: \(error)") } }.resume() } // 新增:单独的发送OTP接口(如果后端支持),否则需要调整registerService逻辑 @objc func sendOtpOnlyService(){ guard let phone = phoneNumTextField.text, !phone.isEmpty else { return } let parameters: [String: Any] = [ "mobile_number": phone ] guard let url = URL(string: "https://dev.com/webservices/sendotp") else { return } // 假设后端有单独的发送OTP接口 var req = URLRequest(url: url) req.httpMethod = "POST" req.addValue("application/json", forHTTPHeaderField: "Content-Type") req.addValue("application/json", forHTTPHeaderField: "Accept") guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else { return } req.httpBody = httpBody let session = URLSession.shared session.dataTask(with: req) { [weak self] data, response, error in guard let self = self else { return } if let error = error { print("发送OTP请求错误: \(error)") return } guard let data = data else { print("未获取到发送OTP响应数据") return } do { let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] print("发送OTP响应: \(json)") self.otpField = json["otp"] as? Int DispatchQueue.main.async { self.otpTextField.text = self.otpField.map(String.init) } } catch { print("解析发送OTP响应错误: \(error)") } }.resume() } }
关键修改说明:
- 倒计时时长调整:把
totalTime从10改为60,符合需求的60秒倒计时 - Timer管理优化:
- 将
otpTimer改为可选类型,启动新计时器前先调用invalidate()停止旧计时器,避免多个计时器同时运行 - 封装
startOtpCountdown()方法,统一处理倒计时启动逻辑,减少重复代码
- 将
- 按钮状态控制:
- 新增
updateResendButtonState()方法,统一管理重发按钮的可点击状态和标题显示 - 倒计时期间按钮不可点击,显示"X秒后重发";倒计时结束后恢复可点击,标题改为"重发OTP"
- 新增
- 接口逻辑修复:
- 修复了请求头
Contet-Type的拼写错误,改为正确的Content-Type - 重发OTP时应该调用单独的发送OTP接口(新增
sendOtpOnlyService()),而不是重复调用注册接口,避免重复创建用户 - 增加了错误处理,比如网络请求失败、数据解析失败的情况
- 使用
[weak self]避免循环引用
- 修复了请求头
- UI体验优化:
- 注册前增加密码一致性验证
- 所有UI更新操作都放在
DispatchQueue.main.async中,确保线程安全
注意:如果你的后端没有单独的发送OTP接口,需要和后端沟通调整,或者修改registerService的逻辑,让它支持仅发送OTP而不重复注册用户。
内容的提问来源于stack exchange,提问作者Swift
相关产品推荐
相关产品推荐

