├── .gitignore ├── .travis.yml ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── BorderStyle.swift ├── EntryViewStyle+Extensions.swift ├── EntryViewStyle.swift ├── ResetType.swift ├── UnderlineStyle.swift ├── VKLabel.swift └── VKPinCodeView.swift ├── VKPinCodeView.podspec ├── VKPinCodeView ├── VKPinCodeView.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── VKPinCodeViewExample.xcscheme │ │ └── VKPinCodeViewLibrary.xcscheme ├── VKPinCodeViewExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── 1.imageset │ │ │ ├── 1.jpg │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── background.colorset │ │ │ └── Contents.json │ │ └── selection.colorset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── ShadowView.swift │ └── ViewController.swift └── VKPinCodeViewLibrary │ ├── Info.plist │ └── VKPinCodeViewLibrary.h └── pincode.gif /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.xcuserstate 3 | 4 | *.xcbkptlist 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode11.2 3 | before_install: 4 | - gem install xcpretty 5 | before_script: 6 | - set -o pipefail 7 | script: 8 | - xcodebuild clean -project VKPinCodeView/VKPinCodeView.xcodeproj && xcodebuild build -project VKPinCodeView/VKPinCodeView.xcodeproj -scheme VKPinCodeViewExample -configuration Debug -sdk iphonesimulator | xcpretty -c 9 | 10 | branches: 11 | only: 12 | - master 13 | notifications: 14 | email: false 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Vladimir Kokhanevich 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "VKPinCodeView", 8 | platforms: [ 9 | .iOS(.v9), 10 | ], 11 | products: [ 12 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 13 | .library( 14 | name: "VKPinCodeView", 15 | targets: ["VKPinCodeView"]), 16 | ], 17 | targets: [ 18 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 19 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 20 | .target( 21 | name: "VKPinCodeView", 22 | path: "Sources"), 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/Sunspension/VKPinCodeView.svg?branch=master)](https://travis-ci.com/Sunspension/VKPinCodeView) 2 | ![(Swift Version)](https://img.shields.io/badge/swift-5.1-orange) 3 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/VKPinCodeView.svg)](https://img.shields.io/cocoapods/v/VKPinCodeView.svg) 4 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 5 | [![Swift Package Manager](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen)](https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html) 6 | 7 | ## Features 8 | 9 | - [x] Variable PIN length 10 | - [x] Underline, border and custom styles 11 | - [x] The error status with / without shake animation 12 | - [x] Resetting the error status manually, by user interaction or automatically with a custom delay 13 | - [x] Highlighting the selected entry with / without animation 14 | - [x] Text input callbacks (begin editing, change code, complete) 15 | - [x] Text input validation 16 | - [x] LTR/RTL support 17 | 18 | 19 | ## Preview 20 | 21 | ![](pincode.gif) 22 | 23 | ## Installation 24 | 25 | ### CocoaPods 26 | 27 | [CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate `VKPinCodeView` into your Xcode project using CocoaPods, specify it in your `Podfile`: 28 | 29 | ```ruby 30 | pod 'VKPinCodeView' 31 | ``` 32 | 33 | ### Carthage 34 | 35 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To integrate `VKPinCodeView` into your Xcode project using Carthage, specify it in your `Cartfile`: 36 | 37 | ```ogdl 38 | github "Sunspension/VKPinCodeView" 39 | ``` 40 | 41 | ### Manually 42 | 43 | Just copy and paste Source folder into your project. 44 | 45 | 46 | ## Minimal Setup 47 | 48 | ```swift 49 | override func viewDidLoad() { 50 | super.viewDidLoad() 51 | 52 | let pinView = VKPinCodeView() 53 | pinView.translatesAutoresizingMaskIntoConstraints = false 54 | view.addSubview(pinView) 55 | pinView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40).isActive = true 56 | pinView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40).isActive = true 57 | pinView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 58 | pinView.heightAnchor.constraint(equalToConstant: 50).isActive = true 59 | pinView.onSettingStyle = { UnderlineStyle() } 60 | pinView.becomeFirstResponder() 61 | } 62 | ``` 63 | 64 | ## Contribute 65 | 66 | VKPinCodeView is open to contribute, see contribution notes. 67 | - If you want to contribute, submit a pull request 68 | - If you found a bug, open an issue. 69 | - If you need help with a feature or need to disscuss something else please contact me vladimir.kokhanevich@gmail.com 70 | 71 | 72 | ## Requirements 73 | 74 | - iOS 9.0+ 75 | - Xcode 10.2+ 76 | - Swift 5.0 77 | 78 | ## Author 79 | 80 | Made with :heart: by Vladimir Kokhanevich 81 | 82 | 83 | ## License 84 | 85 | VKPinCodeView is released under the MIT license. [See LICENSE](https://github.com/Sunspension/VKPinCodeView/blob/master/LICENSE) for details. 86 | -------------------------------------------------------------------------------- /Sources/BorderStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BorderStyle.swift 3 | // VKPinCodeView 4 | // 5 | // Created by Vladimir Kokhanevich on 25.11.19. 6 | // Copyright © 2019 Vladimir Kokhanevich. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public final class BorderStyle: EntryViewStyle { 12 | 13 | private var _font: UIFont 14 | 15 | private var _textColor: UIColor 16 | 17 | private var _errorTextColor: UIColor 18 | 19 | private var _cornerRadius: CGFloat 20 | 21 | private var _borderColor: UIColor 22 | 23 | private var _borderWidth: CGFloat 24 | 25 | private var _selectedBorderColor: UIColor 26 | 27 | private var _errorBorderColor: UIColor 28 | 29 | private var _backgroundColor: UIColor 30 | 31 | private var _selectedBackgroundColor: UIColor 32 | 33 | 34 | public required init( 35 | font: UIFont = UIFont.systemFont(ofSize: 22), 36 | textColor: UIColor = .black, 37 | errorTextColor: UIColor = .red, 38 | cornerRadius: CGFloat = 10, 39 | borderWidth: CGFloat = 1, 40 | borderColor: UIColor = UIColor(white: 0.9, alpha: 1), 41 | selectedBorderColor: UIColor = .lightGray, 42 | errorBorderColor: UIColor = .red, 43 | backgroundColor: UIColor = .white, 44 | selectedBackgroundColor: UIColor = .white) { 45 | 46 | _font = font 47 | _textColor = textColor 48 | _errorTextColor = errorTextColor 49 | _cornerRadius = cornerRadius 50 | _borderWidth = borderWidth 51 | _borderColor = borderColor 52 | _selectedBorderColor = selectedBorderColor 53 | _errorBorderColor = errorBorderColor 54 | _backgroundColor = backgroundColor 55 | _selectedBackgroundColor = selectedBackgroundColor 56 | } 57 | 58 | public func onSetStyle(_ label: VKLabel) { 59 | 60 | let layer = label.layer 61 | layer.cornerRadius = _cornerRadius 62 | layer.borderColor = _borderColor.cgColor 63 | layer.borderWidth = _borderWidth 64 | layer.backgroundColor = _backgroundColor.cgColor 65 | 66 | label.textAlignment = .center 67 | label.font = _font 68 | label.textColor = _textColor 69 | } 70 | 71 | public func onUpdateSelectedState(_ label: VKLabel) { 72 | 73 | let layer = label.layer 74 | 75 | if label.isSelected { 76 | 77 | layer.borderColor = _selectedBorderColor.cgColor 78 | layer.backgroundColor = _selectedBackgroundColor.cgColor 79 | 80 | if label.animateWhileSelected { 81 | 82 | let colors = [_borderColor.cgColor, 83 | _selectedBorderColor.cgColor, 84 | _selectedBorderColor.cgColor, 85 | _borderColor.cgColor] 86 | 87 | let animation = animateSelection(keyPath: #keyPath(CALayer.borderColor), values: colors) 88 | layer.add(animation, forKey: "borderColorAnimation") 89 | } 90 | } 91 | else { 92 | 93 | layer.removeAllAnimations() 94 | layer.borderColor = _borderColor.cgColor 95 | layer.backgroundColor = _backgroundColor.cgColor 96 | } 97 | } 98 | 99 | public func onUpdateErrorState(_ label: VKLabel) { 100 | 101 | if label.isError { 102 | 103 | label.layer.removeAllAnimations() 104 | label.layer.borderColor = _errorBorderColor.cgColor 105 | label.textColor = _errorTextColor 106 | } 107 | else { 108 | 109 | label.layer.borderColor = _borderColor.cgColor 110 | label.textColor = _textColor 111 | } 112 | } 113 | 114 | public func onLayoutSubviews(_ label: VKLabel) {} 115 | } 116 | -------------------------------------------------------------------------------- /Sources/EntryViewStyle+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EntryViewStyle+Extensions.swift 3 | // VKPinCodeView 4 | // 5 | // Created by Vladimir Kokhanevich on 25.11.19. 6 | // Copyright © 2019 Vladimir Kokhanevich. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Friednly helper for the selection animation. 12 | public extension EntryViewStyle { 13 | 14 | func animateSelection(keyPath: String, values: [Any]) -> CAKeyframeAnimation { 15 | 16 | let animation = CAKeyframeAnimation(keyPath: keyPath) 17 | animation.duration = 1.0 18 | animation.repeatCount = Float.greatestFiniteMagnitude 19 | animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 20 | animation.values = values 21 | return animation 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/EntryViewStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EntryViewStylable.swift 3 | // VKPinCodeView 4 | // 5 | // Created by Vladimir Kokhanevich on 25.11.19. 6 | // Copyright © 2019 Vladimir Kokhanevich. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Describes of an appearence lifecycle for a label. 12 | public protocol EntryViewStyle { 13 | 14 | func onSetStyle(_ label: VKLabel) 15 | 16 | func onUpdateSelectedState(_ label: VKLabel) 17 | 18 | func onUpdateErrorState(_ label: VKLabel) 19 | 20 | func onLayoutSubviews(_ label: VKLabel) 21 | } 22 | -------------------------------------------------------------------------------- /Sources/ResetType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResetType.swift 3 | // VKPinCodeView 4 | // 5 | // Created by Vladimir Kokhanevich on 21.11.19. 6 | // Copyright © 2019 Vladimir Kokhanevich. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum ResetType { 12 | 13 | case none, onUserInteraction, afterError(_ delay: TimeInterval) 14 | } 15 | -------------------------------------------------------------------------------- /Sources/UnderlineStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnderlineStyle.swift 3 | // VKPinCodeView 4 | // 5 | // Created by Vladimir Kokhanevich on 25.11.19. 6 | // Copyright © 2019 Vladimir Kokhanevich. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public final class UnderlineStyle: EntryViewStyle { 12 | 13 | private var _line = CAShapeLayer() 14 | 15 | private var _font: UIFont 16 | 17 | private var _textColor: UIColor 18 | 19 | private var _errorTextColor: UIColor 20 | 21 | private var _lineColor: UIColor 22 | 23 | private var _selectedLineColor: UIColor 24 | 25 | private var _lineWidth: CGFloat 26 | 27 | private var _errorLineColor: UIColor 28 | 29 | 30 | public required init( 31 | font: UIFont = UIFont.systemFont(ofSize: 22), 32 | textColor: UIColor = .black, 33 | errorTextColor: UIColor = .red, 34 | lineColor: UIColor = UIColor(white: 0.9, alpha: 1), 35 | selectedLineColor: UIColor = .lightGray, 36 | lineWidth: CGFloat = 1, 37 | errorLineColor: UIColor = .red) { 38 | 39 | _font = font 40 | _textColor = textColor 41 | _errorTextColor = errorTextColor 42 | _lineColor = lineColor 43 | _selectedLineColor = selectedLineColor 44 | _lineWidth = lineWidth 45 | _errorLineColor = errorLineColor 46 | } 47 | 48 | public func onSetStyle(_ label: VKLabel) { 49 | 50 | _line.strokeColor = _lineColor.cgColor 51 | _line.lineWidth = _lineWidth 52 | label.layer.addSublayer(_line) 53 | 54 | label.font = _font 55 | label.textColor = _textColor 56 | label.textAlignment = .center 57 | } 58 | 59 | public func onUpdateSelectedState(_ label: VKLabel) { 60 | 61 | if label.isSelected { 62 | 63 | _line.strokeColor = _selectedLineColor.cgColor 64 | 65 | if label.animateWhileSelected { 66 | 67 | let colors = [_lineColor.cgColor, 68 | _selectedLineColor.cgColor, 69 | _selectedLineColor.cgColor, 70 | _lineColor.cgColor] 71 | 72 | let animation = animateSelection(keyPath: #keyPath(CAShapeLayer.strokeColor), values: colors) 73 | _line.add(animation, forKey: "strokeColorAnimation") 74 | } 75 | } 76 | else { 77 | 78 | _line.removeAllAnimations() 79 | _line.strokeColor = _lineColor.cgColor 80 | } 81 | } 82 | 83 | public func onUpdateErrorState(_ label: VKLabel) { 84 | 85 | if label.isError { 86 | 87 | _line.removeAllAnimations() 88 | _line.strokeColor = _errorLineColor.cgColor 89 | label.textColor = _errorTextColor 90 | } 91 | else { 92 | 93 | _line.strokeColor = _lineColor.cgColor 94 | label.textColor = _textColor 95 | } 96 | } 97 | 98 | public func onLayoutSubviews(_ label: VKLabel) { 99 | 100 | let bounds = label.bounds 101 | let path = UIBezierPath() 102 | let y = bounds.maxY - _lineWidth / 2 103 | path.move(to: CGPoint(x: bounds.minX, y: y)) 104 | path.addLine(to: CGPoint(x: bounds.maxX, y: y)) 105 | _line.path = path.cgPath 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Sources/VKLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VKLabel.swift 3 | // VKPinCodeView 4 | // 5 | // Created by Vladimir Kokhanevich on 22/02/2019. 6 | // Copyright © 2019 Vladimir Kokhanevich. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Input item which is use in main container. 12 | public class VKLabel: UILabel { 13 | 14 | private var _style: EntryViewStyle? 15 | 16 | /// Enable or disable selection animation for active input item. Default value is true. 17 | public var animateWhileSelected = true 18 | 19 | /// Enable or disable selection for displaying active state. 20 | public var isSelected = false { 21 | 22 | didSet { if oldValue != isSelected { updateSelectedState() } } 23 | } 24 | 25 | /// Enable or disable selection for displaying error state. 26 | public var isError = false { 27 | 28 | didSet { updateErrorState() } 29 | } 30 | 31 | // MARK: - Initializers 32 | 33 | /// Prefered initializer if you don't use storyboards or nib files. 34 | public init(_ style: EntryViewStyle?) { 35 | 36 | super.init(frame: CGRect.zero) 37 | setStyle(style) 38 | } 39 | 40 | 41 | public required init?(coder aDecoder: NSCoder) { 42 | 43 | super.init(coder: aDecoder) 44 | } 45 | 46 | // MARK: - Overrides 47 | 48 | public override func layoutSubviews() { 49 | 50 | super.layoutSubviews() 51 | _style?.onLayoutSubviews(self) 52 | } 53 | 54 | // MARK: - Public methods 55 | 56 | /// Set appearence style. 57 | public func setStyle(_ style: EntryViewStyle?) { 58 | 59 | _style = style 60 | _style?.onSetStyle(self) 61 | } 62 | 63 | 64 | // MARK: - Private methods 65 | 66 | private func updateSelectedState() { 67 | 68 | _style?.onUpdateSelectedState(self) 69 | } 70 | 71 | private func updateErrorState() { 72 | 73 | _style?.onUpdateErrorState(self) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/VKPinCodeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VKPinCodeView.swift 3 | // VKPinCodeView 4 | // 5 | // Created by Vladimir Kokhanevich on 22/02/2019. 6 | // Copyright © 2019 Vladimir Kokhanevich. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Validation closure. Use it as soon as you need to validate input text which is different from digits. 12 | public typealias PinCodeValidator = (_ code: String) -> Bool 13 | 14 | 15 | private enum InterfaceLayoutDirection { 16 | 17 | case ltr, rtl 18 | } 19 | 20 | 21 | /// Main container with PIN input items. You can use it in storyboards, nib files or right in the code. 22 | public final class VKPinCodeView: UIView { 23 | 24 | private lazy var _stack = UIStackView(frame: bounds) 25 | 26 | private lazy var _textField = UITextField(frame: bounds) 27 | 28 | private var _code = "" { 29 | 30 | didSet { onCodeDidChange?(_code) } 31 | } 32 | 33 | private var _activeIndex: Int { 34 | 35 | return _code.count == 0 ? 0 : _code.count - 1 36 | } 37 | 38 | private var _layoutDirection: InterfaceLayoutDirection = .ltr 39 | 40 | 41 | /// Enable or disable error mode. Default value is false. 42 | public var isError = false { 43 | 44 | didSet { if oldValue != isError { updateErrorState() } } 45 | } 46 | 47 | /// Number of input items. 48 | public var length: Int = 4 { 49 | 50 | willSet { createLabels() } 51 | } 52 | 53 | /// Spacing between input items. 54 | public var spacing: CGFloat = 16 { 55 | 56 | willSet { if newValue != spacing { _stack.spacing = newValue } } 57 | } 58 | 59 | /// Setup a keyboard type. Default value is numberPad. 60 | public var keyBoardType = UIKeyboardType.numberPad { 61 | 62 | willSet { _textField.keyboardType = newValue } 63 | } 64 | 65 | /// Setup a keyboard appearence. Default value is light. 66 | public var keyBoardAppearance = UIKeyboardAppearance.light { 67 | 68 | willSet { _textField.keyboardAppearance = newValue } 69 | } 70 | 71 | /// Setup autocapitalization. Default value is none. 72 | public var autocapitalizationType = UITextAutocapitalizationType.none { 73 | 74 | willSet { _textField.autocapitalizationType = newValue } 75 | } 76 | 77 | /// Enable or disable selection animation for active input item. Default value is true. 78 | public var animateSelectedInputItem = true 79 | 80 | /// Enable or disable shake animation on error. Default value is true. 81 | public var shakeOnError = true 82 | 83 | /// Setup a preferred error reset type. Default value is none. 84 | public var resetAfterError = ResetType.none 85 | 86 | /// Fires when PIN is completely entered. Provides actual code and view for managing error state. 87 | public var onComplete: ((_ code: String, _ pinView: VKPinCodeView) -> Void)? 88 | 89 | /// Fires after each char has been entered. 90 | public var onCodeDidChange: ((_ code: String) -> Void)? 91 | 92 | /// Fires after begin editing. 93 | public var onBeginEditing: (() -> Void)? 94 | 95 | /// Text input validation. You might be need it if text input is different from digits. You don't need this by default. 96 | public var validator: PinCodeValidator? 97 | 98 | /// Fires every time when the label is ready to set the style. 99 | public var onSettingStyle: (() -> EntryViewStyle)? { 100 | 101 | didSet { createLabels() } 102 | } 103 | 104 | 105 | // MARK: - Initializers 106 | 107 | public convenience init() { 108 | 109 | self.init(frame: CGRect.zero) 110 | } 111 | 112 | override public init(frame: CGRect) { 113 | 114 | super.init(frame: frame) 115 | setup() 116 | } 117 | 118 | required init?(coder aDecoder: NSCoder) { 119 | 120 | super.init(coder: aDecoder) 121 | } 122 | 123 | 124 | // MARK: Life cycle 125 | 126 | override public func awakeFromNib() { 127 | 128 | super.awakeFromNib() 129 | setup() 130 | } 131 | 132 | 133 | // MARK: Overrides 134 | 135 | @discardableResult 136 | override public func becomeFirstResponder() -> Bool { 137 | 138 | onBecomeActive() 139 | return super.becomeFirstResponder() 140 | } 141 | 142 | override public func touchesEnded(_ touches: Set, with event: UIEvent?) { 143 | 144 | onBecomeActive() 145 | } 146 | 147 | 148 | // MARK: Public methods 149 | 150 | /// Use this method to reset the code 151 | public func resetCode() { 152 | _code = "" 153 | _textField.text = nil 154 | _stack.arrangedSubviews.forEach({ ($0 as! VKLabel).text = nil }) 155 | isError = false 156 | } 157 | 158 | // MARK: Private methods 159 | 160 | private func setup() { 161 | 162 | setupTextField() 163 | setupStackView() 164 | 165 | if UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft { 166 | 167 | _layoutDirection = .rtl 168 | } 169 | 170 | createLabels() 171 | } 172 | 173 | private func setupStackView() { 174 | 175 | _stack.autoresizingMask = [.flexibleWidth, .flexibleHeight] 176 | _stack.alignment = .fill 177 | _stack.axis = .horizontal 178 | _stack.distribution = .fillEqually 179 | _stack.spacing = spacing 180 | addSubview(_stack) 181 | } 182 | 183 | private func setupTextField() { 184 | 185 | _textField.keyboardType = keyBoardType 186 | _textField.autocapitalizationType = autocapitalizationType 187 | _textField.keyboardAppearance = keyBoardAppearance 188 | _textField.isHidden = true 189 | _textField.delegate = self 190 | _textField.autoresizingMask = [.flexibleWidth, .flexibleHeight] 191 | _textField.addTarget(self, action: #selector(self.onTextChanged(_:)), for: .editingChanged) 192 | 193 | if #available(iOS 12.0, *) { _textField.textContentType = .oneTimeCode } 194 | 195 | addSubview(_textField) 196 | } 197 | 198 | @objc private func onTextChanged(_ sender: UITextField) { 199 | 200 | let text = sender.text! 201 | 202 | if _code.count > text.count { 203 | 204 | deleteChar(text) 205 | var index = _code.count - 1 206 | if index < 0 { index = 0 } 207 | highlightActiveLabel(index) 208 | } 209 | else { 210 | 211 | appendChar(text) 212 | let index = _code.count - 1 213 | highlightActiveLabel(index) 214 | } 215 | 216 | if _code.count == length { 217 | 218 | _textField.resignFirstResponder() 219 | onComplete?(_code, self) 220 | } 221 | } 222 | 223 | private func deleteChar(_ text: String) { 224 | 225 | let index = text.count 226 | let previous = _stack.arrangedSubviews[index] as! UILabel 227 | previous.text = "" 228 | _code = text 229 | } 230 | 231 | private func appendChar(_ text: String) { 232 | 233 | if text.isEmpty { return } 234 | 235 | let index = text.count - 1 236 | let activeLabel = _stack.arrangedSubviews[index] as! UILabel 237 | let charIndex = text.index(text.startIndex, offsetBy: index) 238 | activeLabel.text = String(text[charIndex]) 239 | _code += activeLabel.text! 240 | } 241 | 242 | private func highlightActiveLabel(_ activeIndex: Int) { 243 | 244 | for i in 0 ..< _stack.arrangedSubviews.count { 245 | 246 | let label = _stack.arrangedSubviews[normalizeIndex(index: i)] as! VKLabel 247 | label.isSelected = i == normalizeIndex(index: activeIndex) 248 | } 249 | } 250 | 251 | private func turnOffSelectedLabel() { 252 | 253 | let label = _stack.arrangedSubviews[_activeIndex] as! VKLabel 254 | label.isSelected = false 255 | } 256 | 257 | private func createLabels() { 258 | 259 | _stack.arrangedSubviews.forEach { $0.removeFromSuperview() } 260 | for _ in 1 ... length { _stack.addArrangedSubview(VKLabel(onSettingStyle?())) } 261 | } 262 | 263 | private func updateErrorState() { 264 | 265 | if isError { 266 | 267 | turnOffSelectedLabel() 268 | if shakeOnError { shakeAnimation() } 269 | } 270 | 271 | _stack.arrangedSubviews.forEach({ ($0 as! VKLabel).isError = isError }) 272 | } 273 | 274 | private func shakeAnimation() { 275 | 276 | let animation = CAKeyframeAnimation(keyPath: "transform.translation.x") 277 | animation.timingFunction = CAMediaTimingFunction(name: .linear) 278 | animation.duration = 0.5 279 | animation.values = [-15.0, 15.0, -15.0, 15.0, -12.0, 12.0, -10.0, 10.0, 0.0] 280 | animation.delegate = self 281 | layer.add(animation, forKey: "shake") 282 | } 283 | 284 | private func onBecomeActive() { 285 | 286 | _textField.becomeFirstResponder() 287 | highlightActiveLabel(_activeIndex) 288 | } 289 | 290 | private func normalizeIndex(index: Int) -> Int { 291 | 292 | return _layoutDirection == .ltr ? index : length - 1 - index 293 | } 294 | } 295 | 296 | 297 | extension VKPinCodeView: UITextFieldDelegate { 298 | 299 | public func textFieldDidBeginEditing(_ textField: UITextField) { 300 | 301 | onBeginEditing?() 302 | handleErrorStateOnBeginEditing() 303 | } 304 | 305 | public func textField(_ textField: UITextField, 306 | shouldChangeCharactersIn range: NSRange, 307 | replacementString string: String) -> Bool { 308 | 309 | if string.isEmpty { return true } 310 | return (validator?(string) ?? true) && _code.count < length 311 | } 312 | 313 | public func textFieldDidEndEditing(_ textField: UITextField) { 314 | 315 | if isError { return } 316 | turnOffSelectedLabel() 317 | } 318 | 319 | private func handleErrorStateOnBeginEditing() { 320 | 321 | if isError, case ResetType.onUserInteraction = resetAfterError { 322 | 323 | return resetCode() 324 | } 325 | 326 | isError = false 327 | } 328 | } 329 | 330 | extension VKPinCodeView: CAAnimationDelegate { 331 | 332 | public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 333 | 334 | if !flag { return } 335 | 336 | switch resetAfterError { 337 | 338 | case let .afterError(delay): 339 | DispatchQueue.main.asyncAfter(deadline: .now() + delay) { self.resetCode() } 340 | default: 341 | break 342 | } 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /VKPinCodeView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | 3 | spec.name = "VKPinCodeView" 4 | spec.version = "0.4.2" 5 | spec.summary = "VKPinCodeView is a library written in Swift that provides the easy peasy way to enter code from SMS." 6 | 7 | spec.description = <<-DESC 8 | VKPinCodeView is great when you need just in seconds make your custom entry PIN view. It is simple and elegant UI component written in Swift, and works like a charm. You can easily customise appearance and get auto fill iOS 12 feature right from the box. 9 | DESC 10 | 11 | spec.homepage = "https://github.com/Sunspension/VKPinCodeView" 12 | # spec.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 13 | 14 | spec.license = "MIT" 15 | spec.author = { "Vladimir Kokhanevich" => "vladimir.kokhanevich@gmail.com" } 16 | 17 | spec.platform = :ios, "9.0" 18 | 19 | spec.source = { :git => "https://github.com/Sunspension/VKPinCodeView.git", :tag => spec.version } 20 | 21 | spec.source_files = "Source", "Sources/*.swift" 22 | spec.swift_version = "5.0" 23 | 24 | end 25 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0883B962240828320087A72A /* ResetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B95B240828310087A72A /* ResetType.swift */; }; 11 | 0883B963240828320087A72A /* EntryViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B95C240828310087A72A /* EntryViewStyle.swift */; }; 12 | 0883B964240828320087A72A /* VKPinCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B95D240828310087A72A /* VKPinCodeView.swift */; }; 13 | 0883B965240828320087A72A /* BorderStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B95E240828310087A72A /* BorderStyle.swift */; }; 14 | 0883B966240828320087A72A /* EntryViewStyle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B95F240828310087A72A /* EntryViewStyle+Extensions.swift */; }; 15 | 0883B967240828320087A72A /* UnderlineStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B960240828310087A72A /* UnderlineStyle.swift */; }; 16 | 0883B968240828320087A72A /* VKLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B961240828310087A72A /* VKLabel.swift */; }; 17 | 0883B971240828410087A72A /* ResetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B96A240828410087A72A /* ResetType.swift */; }; 18 | 0883B972240828410087A72A /* EntryViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B96B240828410087A72A /* EntryViewStyle.swift */; }; 19 | 0883B973240828410087A72A /* VKPinCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B96C240828410087A72A /* VKPinCodeView.swift */; }; 20 | 0883B974240828410087A72A /* BorderStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B96D240828410087A72A /* BorderStyle.swift */; }; 21 | 0883B975240828410087A72A /* EntryViewStyle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B96E240828410087A72A /* EntryViewStyle+Extensions.swift */; }; 22 | 0883B976240828410087A72A /* UnderlineStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B96F240828410087A72A /* UnderlineStyle.swift */; }; 23 | 0883B977240828410087A72A /* VKLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0883B970240828410087A72A /* VKLabel.swift */; }; 24 | 7709F0E02221DC1200CA659F /* ShadowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7709F0DF2221DC1200CA659F /* ShadowView.swift */; }; 25 | 7754FF3C2222A53700BF8FFF /* VKPinCodeViewLibrary.h in Headers */ = {isa = PBXBuildFile; fileRef = 7754FF3A2222A53700BF8FFF /* VKPinCodeViewLibrary.h */; settings = {ATTRIBUTES = (Public, ); }; }; 26 | 7754FF3F2222A53700BF8FFF /* VKPinCodeViewLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7754FF382222A53700BF8FFF /* VKPinCodeViewLibrary.framework */; }; 27 | 7754FF402222A53700BF8FFF /* VKPinCodeViewLibrary.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7754FF382222A53700BF8FFF /* VKPinCodeViewLibrary.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 28 | 77FD7792221F69EE00DC6541 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FD7791221F69EE00DC6541 /* AppDelegate.swift */; }; 29 | 77FD7794221F69EE00DC6541 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FD7793221F69EE00DC6541 /* ViewController.swift */; }; 30 | 77FD7797221F69EE00DC6541 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 77FD7795221F69EE00DC6541 /* Main.storyboard */; }; 31 | 77FD7799221F69EF00DC6541 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 77FD7798221F69EF00DC6541 /* Assets.xcassets */; }; 32 | 77FD779C221F69EF00DC6541 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 77FD779A221F69EF00DC6541 /* LaunchScreen.storyboard */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXContainerItemProxy section */ 36 | 7754FF3D2222A53700BF8FFF /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = 77FD7786221F69EE00DC6541 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = 7754FF372222A53700BF8FFF; 41 | remoteInfo = VKPinCodeViewLibrary; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXCopyFilesBuildPhase section */ 46 | 7754FF442222A53700BF8FFF /* Embed Frameworks */ = { 47 | isa = PBXCopyFilesBuildPhase; 48 | buildActionMask = 2147483647; 49 | dstPath = ""; 50 | dstSubfolderSpec = 10; 51 | files = ( 52 | 7754FF402222A53700BF8FFF /* VKPinCodeViewLibrary.framework in Embed Frameworks */, 53 | ); 54 | name = "Embed Frameworks"; 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXCopyFilesBuildPhase section */ 58 | 59 | /* Begin PBXFileReference section */ 60 | 0883B95B240828310087A72A /* ResetType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResetType.swift; sourceTree = ""; }; 61 | 0883B95C240828310087A72A /* EntryViewStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntryViewStyle.swift; sourceTree = ""; }; 62 | 0883B95D240828310087A72A /* VKPinCodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VKPinCodeView.swift; sourceTree = ""; }; 63 | 0883B95E240828310087A72A /* BorderStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BorderStyle.swift; sourceTree = ""; }; 64 | 0883B95F240828310087A72A /* EntryViewStyle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EntryViewStyle+Extensions.swift"; sourceTree = ""; }; 65 | 0883B960240828310087A72A /* UnderlineStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnderlineStyle.swift; sourceTree = ""; }; 66 | 0883B961240828310087A72A /* VKLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VKLabel.swift; sourceTree = ""; }; 67 | 0883B96A240828410087A72A /* ResetType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResetType.swift; sourceTree = ""; }; 68 | 0883B96B240828410087A72A /* EntryViewStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntryViewStyle.swift; sourceTree = ""; }; 69 | 0883B96C240828410087A72A /* VKPinCodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VKPinCodeView.swift; sourceTree = ""; }; 70 | 0883B96D240828410087A72A /* BorderStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BorderStyle.swift; sourceTree = ""; }; 71 | 0883B96E240828410087A72A /* EntryViewStyle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EntryViewStyle+Extensions.swift"; sourceTree = ""; }; 72 | 0883B96F240828410087A72A /* UnderlineStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnderlineStyle.swift; sourceTree = ""; }; 73 | 0883B970240828410087A72A /* VKLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VKLabel.swift; sourceTree = ""; }; 74 | 7709F0DF2221DC1200CA659F /* ShadowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowView.swift; sourceTree = ""; }; 75 | 7754FF382222A53700BF8FFF /* VKPinCodeViewLibrary.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = VKPinCodeViewLibrary.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 76 | 7754FF3A2222A53700BF8FFF /* VKPinCodeViewLibrary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VKPinCodeViewLibrary.h; sourceTree = ""; }; 77 | 7754FF3B2222A53700BF8FFF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 78 | 77FD778E221F69EE00DC6541 /* VKPinCodeView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VKPinCodeView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 79 | 77FD7791221F69EE00DC6541 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 80 | 77FD7793221F69EE00DC6541 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 81 | 77FD7796221F69EE00DC6541 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 82 | 77FD7798221F69EF00DC6541 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 83 | 77FD779B221F69EF00DC6541 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 84 | 77FD779D221F69EF00DC6541 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 85 | /* End PBXFileReference section */ 86 | 87 | /* Begin PBXFrameworksBuildPhase section */ 88 | 7754FF352222A53700BF8FFF /* Frameworks */ = { 89 | isa = PBXFrameworksBuildPhase; 90 | buildActionMask = 2147483647; 91 | files = ( 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | 77FD778B221F69EE00DC6541 /* Frameworks */ = { 96 | isa = PBXFrameworksBuildPhase; 97 | buildActionMask = 2147483647; 98 | files = ( 99 | 7754FF3F2222A53700BF8FFF /* VKPinCodeViewLibrary.framework in Frameworks */, 100 | ); 101 | runOnlyForDeploymentPostprocessing = 0; 102 | }; 103 | /* End PBXFrameworksBuildPhase section */ 104 | 105 | /* Begin PBXGroup section */ 106 | 0883B95A240828310087A72A /* Sources */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 0883B95B240828310087A72A /* ResetType.swift */, 110 | 0883B95C240828310087A72A /* EntryViewStyle.swift */, 111 | 0883B95D240828310087A72A /* VKPinCodeView.swift */, 112 | 0883B95E240828310087A72A /* BorderStyle.swift */, 113 | 0883B95F240828310087A72A /* EntryViewStyle+Extensions.swift */, 114 | 0883B960240828310087A72A /* UnderlineStyle.swift */, 115 | 0883B961240828310087A72A /* VKLabel.swift */, 116 | ); 117 | name = Sources; 118 | path = ../../Sources; 119 | sourceTree = ""; 120 | }; 121 | 0883B969240828410087A72A /* Sources */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 0883B96A240828410087A72A /* ResetType.swift */, 125 | 0883B96B240828410087A72A /* EntryViewStyle.swift */, 126 | 0883B96C240828410087A72A /* VKPinCodeView.swift */, 127 | 0883B96D240828410087A72A /* BorderStyle.swift */, 128 | 0883B96E240828410087A72A /* EntryViewStyle+Extensions.swift */, 129 | 0883B96F240828410087A72A /* UnderlineStyle.swift */, 130 | 0883B970240828410087A72A /* VKLabel.swift */, 131 | ); 132 | name = Sources; 133 | path = ../../Sources; 134 | sourceTree = ""; 135 | }; 136 | 17B70153E4F01E9A05AEDF58 /* Frameworks */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | ); 140 | name = Frameworks; 141 | sourceTree = ""; 142 | }; 143 | 7754FF392222A53700BF8FFF /* VKPinCodeViewLibrary */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 0883B969240828410087A72A /* Sources */, 147 | 7754FF3A2222A53700BF8FFF /* VKPinCodeViewLibrary.h */, 148 | 7754FF3B2222A53700BF8FFF /* Info.plist */, 149 | ); 150 | path = VKPinCodeViewLibrary; 151 | sourceTree = ""; 152 | }; 153 | 77FD7785221F69EE00DC6541 = { 154 | isa = PBXGroup; 155 | children = ( 156 | 77FD7790221F69EE00DC6541 /* VKPinCodeViewExample */, 157 | 7754FF392222A53700BF8FFF /* VKPinCodeViewLibrary */, 158 | 77FD778F221F69EE00DC6541 /* Products */, 159 | 17B70153E4F01E9A05AEDF58 /* Frameworks */, 160 | ); 161 | sourceTree = ""; 162 | }; 163 | 77FD778F221F69EE00DC6541 /* Products */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | 77FD778E221F69EE00DC6541 /* VKPinCodeView.app */, 167 | 7754FF382222A53700BF8FFF /* VKPinCodeViewLibrary.framework */, 168 | ); 169 | name = Products; 170 | sourceTree = ""; 171 | }; 172 | 77FD7790221F69EE00DC6541 /* VKPinCodeViewExample */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | 0883B95A240828310087A72A /* Sources */, 176 | 77FD7791221F69EE00DC6541 /* AppDelegate.swift */, 177 | 77FD7793221F69EE00DC6541 /* ViewController.swift */, 178 | 77FD7795221F69EE00DC6541 /* Main.storyboard */, 179 | 77FD7798221F69EF00DC6541 /* Assets.xcassets */, 180 | 77FD779A221F69EF00DC6541 /* LaunchScreen.storyboard */, 181 | 77FD779D221F69EF00DC6541 /* Info.plist */, 182 | 7709F0DF2221DC1200CA659F /* ShadowView.swift */, 183 | ); 184 | path = VKPinCodeViewExample; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXGroup section */ 188 | 189 | /* Begin PBXHeadersBuildPhase section */ 190 | 7754FF332222A53700BF8FFF /* Headers */ = { 191 | isa = PBXHeadersBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | 7754FF3C2222A53700BF8FFF /* VKPinCodeViewLibrary.h in Headers */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXHeadersBuildPhase section */ 199 | 200 | /* Begin PBXNativeTarget section */ 201 | 7754FF372222A53700BF8FFF /* VKPinCodeViewLibrary */ = { 202 | isa = PBXNativeTarget; 203 | buildConfigurationList = 7754FF432222A53700BF8FFF /* Build configuration list for PBXNativeTarget "VKPinCodeViewLibrary" */; 204 | buildPhases = ( 205 | 7754FF332222A53700BF8FFF /* Headers */, 206 | 7754FF342222A53700BF8FFF /* Sources */, 207 | 7754FF352222A53700BF8FFF /* Frameworks */, 208 | 7754FF362222A53700BF8FFF /* Resources */, 209 | ); 210 | buildRules = ( 211 | ); 212 | dependencies = ( 213 | ); 214 | name = VKPinCodeViewLibrary; 215 | productName = VKPinCodeViewLibrary; 216 | productReference = 7754FF382222A53700BF8FFF /* VKPinCodeViewLibrary.framework */; 217 | productType = "com.apple.product-type.framework"; 218 | }; 219 | 77FD778D221F69EE00DC6541 /* VKPinCodeView */ = { 220 | isa = PBXNativeTarget; 221 | buildConfigurationList = 77FD77A0221F69EF00DC6541 /* Build configuration list for PBXNativeTarget "VKPinCodeView" */; 222 | buildPhases = ( 223 | 77FD778A221F69EE00DC6541 /* Sources */, 224 | 77FD778B221F69EE00DC6541 /* Frameworks */, 225 | 77FD778C221F69EE00DC6541 /* Resources */, 226 | 7754FF442222A53700BF8FFF /* Embed Frameworks */, 227 | ); 228 | buildRules = ( 229 | ); 230 | dependencies = ( 231 | 7754FF3E2222A53700BF8FFF /* PBXTargetDependency */, 232 | ); 233 | name = VKPinCodeView; 234 | productName = VKPinCodeViewExample; 235 | productReference = 77FD778E221F69EE00DC6541 /* VKPinCodeView.app */; 236 | productType = "com.apple.product-type.application"; 237 | }; 238 | /* End PBXNativeTarget section */ 239 | 240 | /* Begin PBXProject section */ 241 | 77FD7786221F69EE00DC6541 /* Project object */ = { 242 | isa = PBXProject; 243 | attributes = { 244 | LastSwiftUpdateCheck = 1010; 245 | LastUpgradeCheck = 1010; 246 | ORGANIZATIONNAME = "Vladimir Kokhanevich"; 247 | TargetAttributes = { 248 | 7754FF372222A53700BF8FFF = { 249 | CreatedOnToolsVersion = 10.1; 250 | LastSwiftMigration = 1020; 251 | }; 252 | 77FD778D221F69EE00DC6541 = { 253 | CreatedOnToolsVersion = 10.1; 254 | }; 255 | }; 256 | }; 257 | buildConfigurationList = 77FD7789221F69EE00DC6541 /* Build configuration list for PBXProject "VKPinCodeView" */; 258 | compatibilityVersion = "Xcode 9.3"; 259 | developmentRegion = en; 260 | hasScannedForEncodings = 0; 261 | knownRegions = ( 262 | en, 263 | Base, 264 | ); 265 | mainGroup = 77FD7785221F69EE00DC6541; 266 | productRefGroup = 77FD778F221F69EE00DC6541 /* Products */; 267 | projectDirPath = ""; 268 | projectRoot = ""; 269 | targets = ( 270 | 77FD778D221F69EE00DC6541 /* VKPinCodeView */, 271 | 7754FF372222A53700BF8FFF /* VKPinCodeViewLibrary */, 272 | ); 273 | }; 274 | /* End PBXProject section */ 275 | 276 | /* Begin PBXResourcesBuildPhase section */ 277 | 7754FF362222A53700BF8FFF /* Resources */ = { 278 | isa = PBXResourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | 77FD778C221F69EE00DC6541 /* Resources */ = { 285 | isa = PBXResourcesBuildPhase; 286 | buildActionMask = 2147483647; 287 | files = ( 288 | 77FD779C221F69EF00DC6541 /* LaunchScreen.storyboard in Resources */, 289 | 77FD7799221F69EF00DC6541 /* Assets.xcassets in Resources */, 290 | 77FD7797221F69EE00DC6541 /* Main.storyboard in Resources */, 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | /* End PBXResourcesBuildPhase section */ 295 | 296 | /* Begin PBXSourcesBuildPhase section */ 297 | 7754FF342222A53700BF8FFF /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 0883B973240828410087A72A /* VKPinCodeView.swift in Sources */, 302 | 0883B971240828410087A72A /* ResetType.swift in Sources */, 303 | 0883B972240828410087A72A /* EntryViewStyle.swift in Sources */, 304 | 0883B977240828410087A72A /* VKLabel.swift in Sources */, 305 | 0883B975240828410087A72A /* EntryViewStyle+Extensions.swift in Sources */, 306 | 0883B976240828410087A72A /* UnderlineStyle.swift in Sources */, 307 | 0883B974240828410087A72A /* BorderStyle.swift in Sources */, 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | 77FD778A221F69EE00DC6541 /* Sources */ = { 312 | isa = PBXSourcesBuildPhase; 313 | buildActionMask = 2147483647; 314 | files = ( 315 | 0883B964240828320087A72A /* VKPinCodeView.swift in Sources */, 316 | 77FD7794221F69EE00DC6541 /* ViewController.swift in Sources */, 317 | 77FD7792221F69EE00DC6541 /* AppDelegate.swift in Sources */, 318 | 0883B966240828320087A72A /* EntryViewStyle+Extensions.swift in Sources */, 319 | 0883B963240828320087A72A /* EntryViewStyle.swift in Sources */, 320 | 0883B965240828320087A72A /* BorderStyle.swift in Sources */, 321 | 7709F0E02221DC1200CA659F /* ShadowView.swift in Sources */, 322 | 0883B968240828320087A72A /* VKLabel.swift in Sources */, 323 | 0883B967240828320087A72A /* UnderlineStyle.swift in Sources */, 324 | 0883B962240828320087A72A /* ResetType.swift in Sources */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | /* End PBXSourcesBuildPhase section */ 329 | 330 | /* Begin PBXTargetDependency section */ 331 | 7754FF3E2222A53700BF8FFF /* PBXTargetDependency */ = { 332 | isa = PBXTargetDependency; 333 | target = 7754FF372222A53700BF8FFF /* VKPinCodeViewLibrary */; 334 | targetProxy = 7754FF3D2222A53700BF8FFF /* PBXContainerItemProxy */; 335 | }; 336 | /* End PBXTargetDependency section */ 337 | 338 | /* Begin PBXVariantGroup section */ 339 | 77FD7795221F69EE00DC6541 /* Main.storyboard */ = { 340 | isa = PBXVariantGroup; 341 | children = ( 342 | 77FD7796221F69EE00DC6541 /* Base */, 343 | ); 344 | name = Main.storyboard; 345 | sourceTree = ""; 346 | }; 347 | 77FD779A221F69EF00DC6541 /* LaunchScreen.storyboard */ = { 348 | isa = PBXVariantGroup; 349 | children = ( 350 | 77FD779B221F69EF00DC6541 /* Base */, 351 | ); 352 | name = LaunchScreen.storyboard; 353 | sourceTree = ""; 354 | }; 355 | /* End PBXVariantGroup section */ 356 | 357 | /* Begin XCBuildConfiguration section */ 358 | 7754FF412222A53700BF8FFF /* Debug */ = { 359 | isa = XCBuildConfiguration; 360 | buildSettings = { 361 | CODE_SIGN_IDENTITY = ""; 362 | CODE_SIGN_STYLE = Automatic; 363 | CURRENT_PROJECT_VERSION = 1; 364 | DEFINES_MODULE = YES; 365 | DYLIB_COMPATIBILITY_VERSION = 1; 366 | DYLIB_CURRENT_VERSION = 1; 367 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 368 | INFOPLIST_FILE = VKPinCodeViewLibrary/Info.plist; 369 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 370 | LD_RUNPATH_SEARCH_PATHS = ( 371 | "$(inherited)", 372 | "@executable_path/Frameworks", 373 | "@loader_path/Frameworks", 374 | ); 375 | PRODUCT_BUNDLE_IDENTIFIER = com.vk.app.VKPinCodeViewLibrary; 376 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 377 | SKIP_INSTALL = YES; 378 | SWIFT_VERSION = 5.0; 379 | TARGETED_DEVICE_FAMILY = "1,2"; 380 | VERSIONING_SYSTEM = "apple-generic"; 381 | VERSION_INFO_PREFIX = ""; 382 | }; 383 | name = Debug; 384 | }; 385 | 7754FF422222A53700BF8FFF /* Release */ = { 386 | isa = XCBuildConfiguration; 387 | buildSettings = { 388 | CODE_SIGN_IDENTITY = ""; 389 | CODE_SIGN_STYLE = Automatic; 390 | CURRENT_PROJECT_VERSION = 1; 391 | DEFINES_MODULE = YES; 392 | DYLIB_COMPATIBILITY_VERSION = 1; 393 | DYLIB_CURRENT_VERSION = 1; 394 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 395 | INFOPLIST_FILE = VKPinCodeViewLibrary/Info.plist; 396 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 397 | LD_RUNPATH_SEARCH_PATHS = ( 398 | "$(inherited)", 399 | "@executable_path/Frameworks", 400 | "@loader_path/Frameworks", 401 | ); 402 | PRODUCT_BUNDLE_IDENTIFIER = com.vk.app.VKPinCodeViewLibrary; 403 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 404 | SKIP_INSTALL = YES; 405 | SWIFT_VERSION = 5.0; 406 | TARGETED_DEVICE_FAMILY = "1,2"; 407 | VERSIONING_SYSTEM = "apple-generic"; 408 | VERSION_INFO_PREFIX = ""; 409 | }; 410 | name = Release; 411 | }; 412 | 77FD779E221F69EF00DC6541 /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ALWAYS_SEARCH_USER_PATHS = NO; 416 | CLANG_ANALYZER_NONNULL = YES; 417 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 418 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 419 | CLANG_CXX_LIBRARY = "libc++"; 420 | CLANG_ENABLE_MODULES = YES; 421 | CLANG_ENABLE_OBJC_ARC = YES; 422 | CLANG_ENABLE_OBJC_WEAK = YES; 423 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 424 | CLANG_WARN_BOOL_CONVERSION = YES; 425 | CLANG_WARN_COMMA = YES; 426 | CLANG_WARN_CONSTANT_CONVERSION = YES; 427 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 428 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 429 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 430 | CLANG_WARN_EMPTY_BODY = YES; 431 | CLANG_WARN_ENUM_CONVERSION = YES; 432 | CLANG_WARN_INFINITE_RECURSION = YES; 433 | CLANG_WARN_INT_CONVERSION = YES; 434 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 435 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 436 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 437 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 438 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 439 | CLANG_WARN_STRICT_PROTOTYPES = YES; 440 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 441 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 442 | CLANG_WARN_UNREACHABLE_CODE = YES; 443 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 444 | CODE_SIGN_IDENTITY = "iPhone Developer"; 445 | COPY_PHASE_STRIP = NO; 446 | DEBUG_INFORMATION_FORMAT = dwarf; 447 | ENABLE_STRICT_OBJC_MSGSEND = YES; 448 | ENABLE_TESTABILITY = YES; 449 | GCC_C_LANGUAGE_STANDARD = gnu11; 450 | GCC_DYNAMIC_NO_PIC = NO; 451 | GCC_NO_COMMON_BLOCKS = YES; 452 | GCC_OPTIMIZATION_LEVEL = 0; 453 | GCC_PREPROCESSOR_DEFINITIONS = ( 454 | "DEBUG=1", 455 | "$(inherited)", 456 | ); 457 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 458 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 459 | GCC_WARN_UNDECLARED_SELECTOR = YES; 460 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 461 | GCC_WARN_UNUSED_FUNCTION = YES; 462 | GCC_WARN_UNUSED_VARIABLE = YES; 463 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 464 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 465 | MTL_FAST_MATH = YES; 466 | ONLY_ACTIVE_ARCH = YES; 467 | SDKROOT = iphoneos; 468 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 469 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 470 | }; 471 | name = Debug; 472 | }; 473 | 77FD779F221F69EF00DC6541 /* Release */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | ALWAYS_SEARCH_USER_PATHS = NO; 477 | CLANG_ANALYZER_NONNULL = YES; 478 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 479 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 480 | CLANG_CXX_LIBRARY = "libc++"; 481 | CLANG_ENABLE_MODULES = YES; 482 | CLANG_ENABLE_OBJC_ARC = YES; 483 | CLANG_ENABLE_OBJC_WEAK = YES; 484 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 485 | CLANG_WARN_BOOL_CONVERSION = YES; 486 | CLANG_WARN_COMMA = YES; 487 | CLANG_WARN_CONSTANT_CONVERSION = YES; 488 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 489 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 490 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 491 | CLANG_WARN_EMPTY_BODY = YES; 492 | CLANG_WARN_ENUM_CONVERSION = YES; 493 | CLANG_WARN_INFINITE_RECURSION = YES; 494 | CLANG_WARN_INT_CONVERSION = YES; 495 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 496 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 497 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 498 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 499 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 500 | CLANG_WARN_STRICT_PROTOTYPES = YES; 501 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 502 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 503 | CLANG_WARN_UNREACHABLE_CODE = YES; 504 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 505 | CODE_SIGN_IDENTITY = "iPhone Developer"; 506 | COPY_PHASE_STRIP = NO; 507 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 508 | ENABLE_NS_ASSERTIONS = NO; 509 | ENABLE_STRICT_OBJC_MSGSEND = YES; 510 | GCC_C_LANGUAGE_STANDARD = gnu11; 511 | GCC_NO_COMMON_BLOCKS = YES; 512 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 513 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 514 | GCC_WARN_UNDECLARED_SELECTOR = YES; 515 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 516 | GCC_WARN_UNUSED_FUNCTION = YES; 517 | GCC_WARN_UNUSED_VARIABLE = YES; 518 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 519 | MTL_ENABLE_DEBUG_INFO = NO; 520 | MTL_FAST_MATH = YES; 521 | SDKROOT = iphoneos; 522 | SWIFT_COMPILATION_MODE = wholemodule; 523 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 524 | VALIDATE_PRODUCT = YES; 525 | }; 526 | name = Release; 527 | }; 528 | 77FD77A1221F69EF00DC6541 /* Debug */ = { 529 | isa = XCBuildConfiguration; 530 | buildSettings = { 531 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 532 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 533 | CODE_SIGN_STYLE = Automatic; 534 | INFOPLIST_FILE = VKPinCodeViewExample/Info.plist; 535 | LD_RUNPATH_SEARCH_PATHS = ( 536 | "$(inherited)", 537 | "@executable_path/Frameworks", 538 | ); 539 | PRODUCT_BUNDLE_IDENTIFIER = com.vk.app.VKPinCodeViewExample; 540 | PRODUCT_NAME = "$(TARGET_NAME)"; 541 | SWIFT_VERSION = 5.0; 542 | TARGETED_DEVICE_FAMILY = "1,2"; 543 | }; 544 | name = Debug; 545 | }; 546 | 77FD77A2221F69EF00DC6541 /* Release */ = { 547 | isa = XCBuildConfiguration; 548 | buildSettings = { 549 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 550 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 551 | CODE_SIGN_STYLE = Automatic; 552 | INFOPLIST_FILE = VKPinCodeViewExample/Info.plist; 553 | LD_RUNPATH_SEARCH_PATHS = ( 554 | "$(inherited)", 555 | "@executable_path/Frameworks", 556 | ); 557 | PRODUCT_BUNDLE_IDENTIFIER = com.vk.app.VKPinCodeViewExample; 558 | PRODUCT_NAME = "$(TARGET_NAME)"; 559 | SWIFT_VERSION = 5.0; 560 | TARGETED_DEVICE_FAMILY = "1,2"; 561 | }; 562 | name = Release; 563 | }; 564 | /* End XCBuildConfiguration section */ 565 | 566 | /* Begin XCConfigurationList section */ 567 | 7754FF432222A53700BF8FFF /* Build configuration list for PBXNativeTarget "VKPinCodeViewLibrary" */ = { 568 | isa = XCConfigurationList; 569 | buildConfigurations = ( 570 | 7754FF412222A53700BF8FFF /* Debug */, 571 | 7754FF422222A53700BF8FFF /* Release */, 572 | ); 573 | defaultConfigurationIsVisible = 0; 574 | defaultConfigurationName = Release; 575 | }; 576 | 77FD7789221F69EE00DC6541 /* Build configuration list for PBXProject "VKPinCodeView" */ = { 577 | isa = XCConfigurationList; 578 | buildConfigurations = ( 579 | 77FD779E221F69EF00DC6541 /* Debug */, 580 | 77FD779F221F69EF00DC6541 /* Release */, 581 | ); 582 | defaultConfigurationIsVisible = 0; 583 | defaultConfigurationName = Release; 584 | }; 585 | 77FD77A0221F69EF00DC6541 /* Build configuration list for PBXNativeTarget "VKPinCodeView" */ = { 586 | isa = XCConfigurationList; 587 | buildConfigurations = ( 588 | 77FD77A1221F69EF00DC6541 /* Debug */, 589 | 77FD77A2221F69EF00DC6541 /* Release */, 590 | ); 591 | defaultConfigurationIsVisible = 0; 592 | defaultConfigurationName = Release; 593 | }; 594 | /* End XCConfigurationList section */ 595 | }; 596 | rootObject = 77FD7786221F69EE00DC6541 /* Project object */; 597 | } 598 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeView.xcodeproj/xcshareddata/xcschemes/VKPinCodeViewExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeView.xcodeproj/xcshareddata/xcschemes/VKPinCodeViewLibrary.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // VKPinCodeViewExample 4 | // 5 | // Created by Vladimir Kokhanevich on 22/02/2019. 6 | // Copyright © 2019 Vladimir Kokhanevich. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/Assets.xcassets/1.imageset/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sunspension/VKPinCodeView/9aace99b23a7812a38a46a921996c297fc82aeb4/VKPinCodeView/VKPinCodeViewExample/Assets.xcassets/1.imageset/1.jpg -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/Assets.xcassets/1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/Assets.xcassets/background.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.472", 13 | "alpha" : "1.000", 14 | "blue" : "1.000", 15 | "green" : "0.472" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/Assets.xcassets/selection.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.341", 13 | "alpha" : "1.000", 14 | "blue" : "1.000", 15 | "green" : "0.472" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 87 | 93 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/ShadowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShadowView.swift 3 | // VKPinCodeViewExample 4 | // 5 | // Created by Vladimir Kokhanevich on 23/02/2019. 6 | // Copyright © 2019 Vladimir Kokhanevich. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ShadowView: UIView { 12 | 13 | override var bounds: CGRect { 14 | 15 | didSet { 16 | 17 | layer.shadowOffset = CGSize(width: 0, height: 5) 18 | layer.shadowOpacity = 0.1 19 | layer.shadowRadius = 8.0 20 | layer.shadowPath = UIBezierPath(roundedRect: layer.bounds, cornerRadius: layer.cornerRadius).cgPath 21 | } 22 | } 23 | 24 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 25 | 26 | } 27 | 28 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 29 | 30 | } 31 | 32 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 33 | 34 | } 35 | 36 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // VKPinCodeViewExample 4 | // 5 | // Created by Vladimir Kokhanevich on 22/02/2019. 6 | // Copyright © 2019 Vladimir Kokhanevich. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet weak var firstContainer: ShadowView! 14 | 15 | @IBOutlet weak var secondContainer: ShadowView! 16 | 17 | @IBOutlet weak var firstPinView: VKPinCodeView! 18 | 19 | @IBOutlet weak var secondPinView: VKPinCodeView! 20 | 21 | 22 | override func viewDidLoad() { 23 | 24 | super.viewDidLoad() 25 | firstContainer.layer.cornerRadius = 20 26 | secondContainer.layer.cornerRadius = 20 27 | setupPinViews() 28 | } 29 | 30 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 31 | 32 | view.endEditing(true) 33 | } 34 | 35 | 36 | private func setupPinViews() { 37 | 38 | firstPinView.onSettingStyle = { 39 | 40 | UnderlineStyle(textColor: .white, lineColor: .white, lineWidth: 2) 41 | } 42 | 43 | firstPinView.validator = validator(_:) 44 | 45 | secondPinView.onSettingStyle = { 46 | 47 | BorderStyle( 48 | textColor: .white, 49 | borderWidth: 2, 50 | backgroundColor: .clear, 51 | selectedBackgroundColor: UIColor(named: "selection")!) 52 | } 53 | 54 | secondPinView.onComplete = { code, pinView in 55 | 56 | if code != "1111" { pinView.isError = true } 57 | } 58 | 59 | secondPinView.validator = validator(_:) 60 | } 61 | 62 | private func validator(_ code: String) -> Bool { 63 | 64 | return !code.trimmingCharacters(in: CharacterSet.decimalDigits.inverted).isEmpty 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewLibrary/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /VKPinCodeView/VKPinCodeViewLibrary/VKPinCodeViewLibrary.h: -------------------------------------------------------------------------------- 1 | // 2 | // VKPinCodeViewLibrary.h 3 | // VKPinCodeViewLibrary 4 | // 5 | // Created by Vladimir Kokhanevich on 24/02/2019. 6 | // Copyright © 2019 Vladimir Kokhanevich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for VKPinCodeViewLibrary. 12 | FOUNDATION_EXPORT double VKPinCodeViewLibraryVersionNumber; 13 | 14 | //! Project version string for VKPinCodeViewLibrary. 15 | FOUNDATION_EXPORT const unsigned char VKPinCodeViewLibraryVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /pincode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sunspension/VKPinCodeView/9aace99b23a7812a38a46a921996c297fc82aeb4/pincode.gif --------------------------------------------------------------------------------