132 lines
4.4 KiB
Swift
132 lines
4.4 KiB
Swift
//
|
|
// Copyright (c) 2020 Touch Instinct
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the Software), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
//
|
|
|
|
import UIKit
|
|
import TISwiftUtils
|
|
|
|
/// Base one symbol textfield
|
|
open class OTPTextField: UITextField {
|
|
private let maxSymbolsCount = 1
|
|
|
|
public weak var previousTextField: OTPTextField?
|
|
public weak var nextTextField: OTPTextField?
|
|
|
|
public var onTextChangedSignal: VoidClosure?
|
|
public var validationClosure: Closure<String, Bool>?
|
|
public var caretHeight: CGFloat?
|
|
|
|
public var lastNotEmpty: OTPTextField {
|
|
let isLastNotEmpty = !text.orEmpty.isEmpty && nextTextField?.text.orEmpty.isEmpty ?? true
|
|
return isLastNotEmpty ? self : nextTextField?.lastNotEmpty ?? self
|
|
}
|
|
|
|
open override var font: UIFont? {
|
|
didSet {
|
|
if caretHeight == nil, let font = font {
|
|
caretHeight = font.pointSize - font.descender
|
|
}
|
|
}
|
|
}
|
|
|
|
public override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
|
|
delegate = self
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required public init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
open override func deleteBackward() {
|
|
guard text.orEmpty.isEmpty else {
|
|
return
|
|
}
|
|
|
|
onTextChangedSignal?()
|
|
previousTextField?.text = ""
|
|
previousTextField?.becomeFirstResponder()
|
|
}
|
|
|
|
public func set(inputText: String) {
|
|
text = inputText.prefix(maxSymbolsCount).string
|
|
|
|
let nextInputText = inputText.count >= maxSymbolsCount
|
|
? inputText.suffix(inputText.count - maxSymbolsCount).string
|
|
: ""
|
|
|
|
nextTextField?.set(inputText: nextInputText)
|
|
}
|
|
|
|
open override func caretRect(for position: UITextPosition) -> CGRect {
|
|
guard let caretHeight = caretHeight else {
|
|
return super.caretRect(for: position)
|
|
}
|
|
|
|
var superRect = super.caretRect(for: position)
|
|
superRect.size.height = caretHeight
|
|
|
|
return superRect
|
|
}
|
|
|
|
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
let view = super.hitTest(point, with: event)
|
|
return view == self && isFirstResponder ? view : nil
|
|
}
|
|
}
|
|
|
|
extension OTPTextField: UITextFieldDelegate {
|
|
public func textField(_ textField: UITextField,
|
|
shouldChangeCharactersIn range: NSRange,
|
|
replacementString string: String) -> Bool {
|
|
guard let textField = textField as? OTPTextField else {
|
|
return true
|
|
}
|
|
|
|
let isInputEmpty = textField.text.orEmpty.isEmpty && string.isEmpty
|
|
|
|
guard isInputEmpty || validationClosure?(string) ?? true else {
|
|
return false
|
|
}
|
|
|
|
switch range.length {
|
|
case 0: // set text to textfield
|
|
textField.set(inputText: string)
|
|
|
|
let currentTextField = textField.lastNotEmpty.nextTextField ?? textField.lastNotEmpty
|
|
currentTextField.becomeFirstResponder()
|
|
textField.onTextChangedSignal?()
|
|
|
|
return false
|
|
|
|
case 1: // remove character from textfield
|
|
textField.text = ""
|
|
textField.onTextChangedSignal?()
|
|
return false
|
|
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
}
|