101 lines
2.9 KiB
Swift
101 lines
2.9 KiB
Swift
import SwiftUI
|
|
import UIKit
|
|
|
|
struct OTPDigitField: UIViewRepresentable {
|
|
@Binding var text: String
|
|
let isFocused: Bool
|
|
let onFocus: () -> Void
|
|
let onInsert: () -> Void
|
|
let onDeleteWhenEmpty: () -> Void
|
|
let onPaste: ([String]) -> Void
|
|
|
|
func makeUIView(context: Context) -> BackspaceAwareTextField {
|
|
let textField = BackspaceAwareTextField()
|
|
textField.delegate = context.coordinator
|
|
textField.keyboardType = .numberPad
|
|
textField.textAlignment = .center
|
|
textField.font = UIFont.systemFont(ofSize: 24, weight: .bold)
|
|
textField.textContentType = .oneTimeCode
|
|
textField.onDeleteWhenEmpty = {
|
|
onDeleteWhenEmpty()
|
|
}
|
|
textField.addTarget(context.coordinator, action: #selector(Coordinator.editingChanged(_:)), for: .editingChanged)
|
|
return textField
|
|
}
|
|
|
|
func updateUIView(_ uiView: BackspaceAwareTextField, context: Context) {
|
|
if uiView.text != text {
|
|
uiView.text = text
|
|
}
|
|
|
|
if isFocused && !uiView.isFirstResponder {
|
|
DispatchQueue.main.async {
|
|
uiView.becomeFirstResponder()
|
|
}
|
|
}
|
|
}
|
|
|
|
func makeCoordinator() -> Coordinator {
|
|
Coordinator(parent: self)
|
|
}
|
|
|
|
final class Coordinator: NSObject, UITextFieldDelegate {
|
|
var parent: OTPDigitField
|
|
|
|
init(parent: OTPDigitField) {
|
|
self.parent = parent
|
|
}
|
|
|
|
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
|
|
parent.onFocus()
|
|
return true
|
|
}
|
|
|
|
func textFieldDidBeginEditing(_ textField: UITextField) {
|
|
parent.onFocus()
|
|
}
|
|
|
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
|
if string.isEmpty {
|
|
return true
|
|
}
|
|
|
|
let digits = string.filter { $0.isNumber }
|
|
guard !digits.isEmpty else {
|
|
return false
|
|
}
|
|
|
|
if digits.count > 1 {
|
|
parent.onPaste(digits.map(String.init))
|
|
return false
|
|
}
|
|
|
|
parent.text = String(digits.prefix(1))
|
|
parent.onInsert()
|
|
return false
|
|
}
|
|
|
|
@objc
|
|
func editingChanged(_ textField: UITextField) {
|
|
let digitsOnly = (textField.text ?? "").filter { $0.isNumber }
|
|
let single = String(digitsOnly.prefix(1))
|
|
if textField.text != single {
|
|
textField.text = single
|
|
}
|
|
parent.text = single
|
|
}
|
|
}
|
|
}
|
|
|
|
final class BackspaceAwareTextField: UITextField {
|
|
var onDeleteWhenEmpty: (() -> Void)?
|
|
|
|
override func deleteBackward() {
|
|
let wasEmpty = (text ?? "").isEmpty
|
|
super.deleteBackward()
|
|
if wasEmpty {
|
|
onDeleteWhenEmpty?()
|
|
}
|
|
}
|
|
}
|