LNCaptchaInputView.swift 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. //
  2. // LNCaptchaInputView.swift
  3. // Gami
  4. //
  5. // Created by OneeChan on 2026/1/16.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. protocol LNCaptchaInputViewDelegate: NSObject {
  11. func onCaptchaInputChange(view: LNCaptchaInputView)
  12. }
  13. class LNCaptchaInputView: UIView {
  14. private let captchaCount: Int = 4
  15. private let stackView = UIStackView()
  16. private(set) var inputViews: [UITextField] = []
  17. weak var delegate: LNCaptchaInputViewDelegate?
  18. var hasDone: Bool {
  19. inputViews.first { $0.text?.isEmpty != false } == nil
  20. }
  21. var curInput: String {
  22. inputViews.map { $0.text ?? "" }.joined()
  23. }
  24. override init(frame: CGRect) {
  25. super.init(frame: frame)
  26. setupViews()
  27. buildCaptchaInput()
  28. }
  29. func startInput() {
  30. inputViews.first?.becomeFirstResponder()
  31. }
  32. required init?(coder: NSCoder) {
  33. fatalError("init(coder:) has not been implemented")
  34. }
  35. }
  36. extension LNCaptchaInputView: UITextFieldDelegate, LNTextFieldDelegate {
  37. func textFieldDidBeginEditing(_ textField: UITextField) {
  38. textField.text = nil
  39. }
  40. func onDeleteBackward(_ textField: UITextField, oldText: String?) {
  41. if let oldText, !oldText.isEmpty { return }
  42. guard let index = inputViews.firstIndex(of: textField) else { return }
  43. if index == 0 { return }
  44. inputViews[index - 1].text = nil
  45. inputViews[index - 1].becomeFirstResponder()
  46. }
  47. }
  48. extension LNCaptchaInputView {
  49. private func setupViews() {
  50. stackView.axis = .horizontal
  51. stackView.distribution = .equalCentering
  52. addSubview(stackView)
  53. stackView.snp.makeConstraints { make in
  54. make.edges.equalToSuperview()
  55. }
  56. }
  57. private func buildCaptchaInput() {
  58. let allViews = stackView.arrangedSubviews
  59. allViews.forEach {
  60. stackView.removeArrangedSubview($0)
  61. $0.removeFromSuperview()
  62. }
  63. inputViews.removeAll()
  64. for _ in 0..<captchaCount {
  65. let container = UIView()
  66. container.layer.cornerRadius = 10
  67. container.backgroundColor = .fill
  68. container.snp.makeConstraints { make in
  69. make.width.equalTo(64)
  70. }
  71. let input = LNTextField()
  72. input.delegate = self
  73. input.exDelegate = self
  74. input.font = .systemFont(ofSize: 32, weight: .semibold)
  75. input.textColor = .text_5
  76. input.keyboardType = .numberPad
  77. input.textAlignment = .center
  78. input.cursorWidth = 2
  79. input.cursorHeight = 24
  80. input.addAction(UIAction(handler: { [weak self, weak input] _ in
  81. guard let self, let input else { return }
  82. guard let text = input.text, !text.isEmpty else { return }
  83. input.text = "\(Int(text.prefix(1)) ?? 0)"
  84. guard let index = inputViews.firstIndex(of: input) else { return }
  85. if index < inputViews.count - 1 {
  86. inputViews[index + 1].text = nil
  87. inputViews[index + 1].becomeFirstResponder()
  88. } else {
  89. input.resignFirstResponder()
  90. }
  91. delegate?.onCaptchaInputChange(view: self)
  92. }), for: .editingChanged)
  93. container.addSubview(input)
  94. input.snp.makeConstraints { make in
  95. make.horizontalEdges.equalToSuperview()
  96. make.centerY.equalToSuperview()
  97. }
  98. stackView.addArrangedSubview(container)
  99. inputViews.append(input)
  100. }
  101. }
  102. }