Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 73 additions & 26 deletions Sources/VKPinCodeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}


Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -151,12 +176,6 @@ public final class VKPinCodeView: UIView {

setupTextField()
setupStackView()

if UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft {

_layoutDirection = .rtl
}

createLabels()
}

Expand All @@ -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)
}
}

Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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
}
}

Expand All @@ -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) {
Expand Down
18 changes: 18 additions & 0 deletions VKPinCodeView/VKPinCodeViewExample/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}

}