diff --git a/Sources/VKPinCodeView.swift b/Sources/VKPinCodeView.swift index c15a6d0..7c53c15 100644 --- a/Sources/VKPinCodeView.swift +++ b/Sources/VKPinCodeView.swift @@ -12,9 +12,16 @@ import UIKit public typealias PinCodeValidator = (_ code: String) -> Bool -private enum InterfaceLayoutDirection { +public enum InterfaceLayoutDirection { - case ltr, rtl + /// Current user interface layout direction + case `default` + + /// Force left-to-right layout + case ltr + + /// Force right-to-left layout + case rtl } @@ -26,18 +33,36 @@ public final class VKPinCodeView: UIView { private lazy var _textField = UITextField(frame: bounds) - private var _code = "" { + private var _inputAccessoryView: UIView? + + private var _activeIndex: Int { - didSet { onCodeDidChange?(_code) } + return code.count == 0 ? 0 : code.count - 1 } - private var _activeIndex: Int { + /// Current input value + public private(set) var code = "" { - return _code.count == 0 ? 0 : _code.count - 1 + didSet { onCodeDidChange?(code) } + } + + /// The custom accessory view to display when the view becomes the first responder + public override var inputAccessoryView: UIView? { + get { + _inputAccessoryView + } + set { + _inputAccessoryView = newValue + } + } + + /// View layout direction. Default value is **default**. + public var layoutDirection: InterfaceLayoutDirection = .default { + + didSet { + updateSemanticContentAttribute() + } } - - private var _layoutDirection: InterfaceLayoutDirection = .ltr - /// Enable or disable the error mode. Default value is false. public var isError = false { @@ -139,7 +164,7 @@ public final class VKPinCodeView: UIView { /// Use this method to reset the code public func resetCode() { - _code = "" + code = "" _textField.text = nil _stack.arrangedSubviews.forEach({ ($0 as! VKLabel).text = nil }) isError = false @@ -151,12 +176,6 @@ public final class VKPinCodeView: UIView { setupTextField() setupStackView() - - if UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft { - - _layoutDirection = .rtl - } - createLabels() } @@ -183,28 +202,45 @@ public final class VKPinCodeView: UIView { addSubview(_textField) } + private func updateSemanticContentAttribute() { + + let newSemanticContentAttribute: UISemanticContentAttribute + + switch layoutDirection { + case .default: + newSemanticContentAttribute = .unspecified + case .ltr: + newSemanticContentAttribute = .forceLeftToRight + case .rtl: + newSemanticContentAttribute = .forceRightToLeft + } + + semanticContentAttribute = newSemanticContentAttribute + _stack.semanticContentAttribute = newSemanticContentAttribute + } + @objc private func onTextChanged(_ sender: UITextField) { let text = sender.text! - if _code.count > text.count { + if code.count > text.count { deleteChar(text) - var index = _code.count - 1 + var index = code.count - 1 if index < 0 { index = 0 } highlightActiveLabel(index) } else { appendChar(text) - let index = _code.count - 1 + let index = code.count - 1 highlightActiveLabel(index) } - if _code.count == length { + if code.count == length { _textField.resignFirstResponder() - onComplete?(_code, self) + onComplete?(code, self) } } @@ -213,7 +249,7 @@ public final class VKPinCodeView: UIView { let index = text.count let previous = _stack.arrangedSubviews[index] as! UILabel previous.text = "" - _code = text + code = text } private func appendChar(_ text: String) { @@ -224,7 +260,7 @@ public final class VKPinCodeView: UIView { let activeLabel = _stack.arrangedSubviews[index] as! UILabel let charIndex = text.index(text.startIndex, offsetBy: index) activeLabel.text = String(text[charIndex]) - _code += activeLabel.text! + code += activeLabel.text! } private func highlightActiveLabel(_ activeIndex: Int) { @@ -276,8 +312,19 @@ public final class VKPinCodeView: UIView { } private func normalizeIndex(index: Int) -> Int { - - return _layoutDirection == .ltr ? index : length - 1 - index + + let isRTL: Bool + + switch layoutDirection { + case .default: + isRTL = UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft + case .ltr: + isRTL = false + case .rtl: + isRTL = true + } + + return isRTL ? length - 1 - index : index } } @@ -295,7 +342,7 @@ extension VKPinCodeView: UITextFieldDelegate { replacementString string: String) -> Bool { if string.isEmpty { return true } - return (validator?(string) ?? true) && _code.count < length + return (validator?(string) ?? true) && code.count < length } public func textFieldDidEndEditing(_ textField: UITextField) { diff --git a/VKPinCodeView/VKPinCodeViewExample/ViewController.swift b/VKPinCodeView/VKPinCodeViewExample/ViewController.swift index 64d1cdb..cde9bb4 100644 --- a/VKPinCodeView/VKPinCodeViewExample/ViewController.swift +++ b/VKPinCodeView/VKPinCodeViewExample/ViewController.swift @@ -35,6 +35,8 @@ class ViewController: UIViewController { private func setupPinViews() { + firstPinView.layoutDirection = .rtl + firstPinView.inputAccessoryView = getInputAccessoryView() firstPinView.onSettingStyle = { UnderlineStyle(textColor: .white, lineColor: .white, lineWidth: 2) @@ -63,5 +65,21 @@ class ViewController: UIViewController { return !code.trimmingCharacters(in: CharacterSet.decimalDigits.inverted).isEmpty } + + private func getInputAccessoryView() -> UIView { + let size = CGSize(width: view.frame.width, height: .greatestFiniteMagnitude) + let frame = CGRect(origin: .zero, size: size) + let toolbar = UIToolbar(frame: frame) + let toolbarDoneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(doneButtonDidTap)) + toolbar.setItems([toolbarDoneButton], animated: true) + toolbar.sizeToFit() + return toolbar + } + + @objc private func doneButtonDidTap() { + print(firstPinView.code) + firstPinView.endEditing(true) + } + }