Repository: Root-vb/OTPFieldView Branch: master Commit: ed198372acc9 Files: 14 Total size: 43.0 KB Directory structure: gitextract_1c6mpg_h/ ├── LICENSE ├── OTPFieldView/ │ ├── Info.plist │ ├── OTPFieldView.h │ ├── OTPFieldView.swift │ └── OTPTextField.swift ├── OTPFieldView.podspec ├── OTPFieldView.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata/ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata/ │ │ └── vaibhavbhasin.xcuserdatad/ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata/ │ └── vaibhavbhasin.xcuserdatad/ │ └── xcschemes/ │ └── xcschememanagement.plist ├── OTPFieldViewTests/ │ ├── Info.plist │ └── OTPFieldViewTests.swift └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Vaibhav Bhasin 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. ================================================ FILE: OTPFieldView/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString 1.0.1 CFBundleVersion $(CURRENT_PROJECT_VERSION) ================================================ FILE: OTPFieldView/OTPFieldView.h ================================================ // // OTPFieldView.h // OTPFieldView // // Created by Vaibhav Bhasin on 10/09/19. // Copyright © 2019 Vaibhav Bhasin. All rights reserved. // #import //! Project version number for OTPFieldView. FOUNDATION_EXPORT double OTPFieldViewVersionNumber; //! Project version string for OTPFieldView. FOUNDATION_EXPORT const unsigned char OTPFieldViewVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import ================================================ FILE: OTPFieldView/OTPFieldView.swift ================================================ // // OTPFieldView.swift // OTPFieldView // // Created by Vaibhav Bhasin on 10/09/19. // Copyright © 2019 Vaibhav Bhasin. All rights reserved. // // MIT License // // Copyright (c) 2019 Vaibhav Bhasin // // 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 @objc public protocol OTPFieldViewDelegate: class { func shouldBecomeFirstResponderForOTP(otpTextFieldIndex index: Int) -> Bool func enteredOTP(otp: String) func hasEnteredAllOTP(hasEnteredAll: Bool) -> Bool } @objc public enum DisplayType: Int { case circular case roundedCorner case square case diamond case underlinedBottom } /// Different input type for OTP fields. @objc public enum KeyboardType: Int { case numeric case alphabet case alphaNumeric } @objc public class OTPFieldView: UIView { /// Different display type for text fields. public var displayType: DisplayType = .circular public var fieldsCount: Int = 4 public var otpInputType: KeyboardType = .numeric public var fieldFont: UIFont = UIFont.systemFont(ofSize: 20) public var secureEntry: Bool = false public var hideEnteredText: Bool = false public var requireCursor: Bool = true public var cursorColor: UIColor = UIColor.blue public var fieldSize: CGFloat = 60 public var separatorSpace: CGFloat = 16 public var fieldBorderWidth: CGFloat = 1 public var shouldAllowIntermediateEditing: Bool = true public var defaultBackgroundColor: UIColor = UIColor.clear public var filledBackgroundColor: UIColor = UIColor.clear public var defaultBorderColor: UIColor = UIColor.gray public var filledBorderColor: UIColor = UIColor.clear public var errorBorderColor: UIColor? public weak var delegate: OTPFieldViewDelegate? fileprivate var secureEntryData = [String]() override public func awakeFromNib() { super.awakeFromNib() } public func initializeUI() { layer.masksToBounds = true layoutIfNeeded() initializeOTPFields() layoutIfNeeded() // Forcefully try to make first otp field as first responder (viewWithTag(1) as? OTPTextField)?.becomeFirstResponder() } fileprivate func initializeOTPFields() { secureEntryData.removeAll() for index in stride(from: 0, to: fieldsCount, by: 1) { let oldOtpField = viewWithTag(index + 1) as? OTPTextField oldOtpField?.removeFromSuperview() let otpField = getOTPField(forIndex: index) addSubview(otpField) secureEntryData.append("") } } fileprivate func getOTPField(forIndex index: Int) -> OTPTextField { let hasOddNumberOfFields = (fieldsCount % 2 == 1) var fieldFrame = CGRect(x: 0, y: 0, width: fieldSize, height: fieldSize) if hasOddNumberOfFields { // Calculate from middle each fields x and y values so as to align the entire view in center fieldFrame.origin.x = bounds.size.width / 2 - (CGFloat(fieldsCount / 2 - index) * (fieldSize + separatorSpace) + fieldSize / 2) } else { // Calculate from middle each fields x and y values so as to align the entire view in center fieldFrame.origin.x = bounds.size.width / 2 - (CGFloat(fieldsCount / 2 - index) * fieldSize + CGFloat(fieldsCount / 2 - index - 1) * separatorSpace + separatorSpace / 2) } fieldFrame.origin.y = (bounds.size.height - fieldSize) / 2 let otpField = OTPTextField(frame: fieldFrame) otpField.delegate = self otpField.tag = index + 1 otpField.font = fieldFont // Set input type for OTP fields switch otpInputType { case .numeric: otpField.keyboardType = .numberPad case .alphabet: otpField.keyboardType = .alphabet case .alphaNumeric: otpField.keyboardType = .namePhonePad } // Set the border values if needed otpField.otpBorderColor = defaultBorderColor otpField.otpBorderWidth = fieldBorderWidth if requireCursor { otpField.tintColor = cursorColor } else { otpField.tintColor = UIColor.clear } // Set the default background color when text not set otpField.backgroundColor = defaultBackgroundColor // Finally create the fields otpField.initalizeUI(forFieldType: displayType) return otpField } fileprivate func isPreviousFieldsEntered(forTextField textField: UITextField) -> Bool { var isTextFilled = true var nextOTPField: UITextField? // If intermediate editing is not allowed, then check for last filled field in forward direction. if !shouldAllowIntermediateEditing { for index in stride(from: 1, to: fieldsCount + 1, by: 1) { let tempNextOTPField = viewWithTag(index) as? UITextField if let tempNextOTPFieldText = tempNextOTPField?.text, tempNextOTPFieldText.isEmpty { nextOTPField = tempNextOTPField break } } if let nextOTPField = nextOTPField { isTextFilled = (nextOTPField == textField || (textField.tag) == (nextOTPField.tag - 1)) } } return isTextFilled } // Helper function to get the OTP String entered fileprivate func calculateEnteredOTPSTring(isDeleted: Bool) { if isDeleted { _ = delegate?.hasEnteredAllOTP(hasEnteredAll: false) // Set the default enteres state for otp entry for index in stride(from: 0, to: fieldsCount, by: 1) { var otpField = viewWithTag(index + 1) as? OTPTextField if otpField == nil { otpField = getOTPField(forIndex: index) } let fieldBackgroundColor = (otpField?.text ?? "").isEmpty ? defaultBackgroundColor : filledBackgroundColor let fieldBorderColor = (otpField?.text ?? "").isEmpty ? defaultBorderColor : filledBorderColor if displayType == .diamond || displayType == .underlinedBottom { otpField?.shapeLayer.fillColor = fieldBackgroundColor.cgColor otpField?.shapeLayer.strokeColor = fieldBorderColor.cgColor } else { otpField?.backgroundColor = fieldBackgroundColor otpField?.layer.borderColor = fieldBorderColor.cgColor } } } else { var enteredOTPString = "" // Check for entered OTP for index in stride(from: 0, to: secureEntryData.count, by: 1) { if !secureEntryData[index].isEmpty { enteredOTPString.append(secureEntryData[index]) } } if enteredOTPString.count == fieldsCount { delegate?.enteredOTP(otp: enteredOTPString) // Check if all OTP fields have been filled or not. Based on that call the 2 delegate methods. let isValid = delegate?.hasEnteredAllOTP(hasEnteredAll: (enteredOTPString.count == fieldsCount)) ?? false // Set the error state for invalid otp entry for index in stride(from: 0, to: fieldsCount, by: 1) { var otpField = viewWithTag(index + 1) as? OTPTextField if otpField == nil { otpField = getOTPField(forIndex: index) } if !isValid { // Set error border color if set, if not, set default border color otpField?.layer.borderColor = (errorBorderColor ?? filledBorderColor).cgColor } else { otpField?.layer.borderColor = filledBorderColor.cgColor } } } } } } extension OTPFieldView: UITextFieldDelegate { public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { let shouldBeginEditing = delegate?.shouldBecomeFirstResponderForOTP(otpTextFieldIndex: (textField.tag - 1)) ?? true if shouldBeginEditing { return isPreviousFieldsEntered(forTextField: textField) } return shouldBeginEditing } public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let replacedText = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) ?? "" // Check since only alphabet keyboard is not available in iOS if !replacedText.isEmpty && otpInputType == .alphabet && replacedText.rangeOfCharacter(from: .letters) == nil { return false } if replacedText.count >= 1 { // If field has a text already, then replace the text and move to next field if present secureEntryData[textField.tag - 1] = string if hideEnteredText { textField.text = " " } else { if secureEntry { textField.text = "•" } else { textField.text = string } } if displayType == .diamond || displayType == .underlinedBottom { (textField as! OTPTextField).shapeLayer.fillColor = filledBackgroundColor.cgColor (textField as! OTPTextField).shapeLayer.strokeColor = filledBorderColor.cgColor } else { textField.backgroundColor = filledBackgroundColor textField.layer.borderColor = filledBorderColor.cgColor } let nextOTPField = viewWithTag(textField.tag + 1) if let nextOTPField = nextOTPField { nextOTPField.becomeFirstResponder() } else { textField.resignFirstResponder() } // Get the entered string calculateEnteredOTPSTring(isDeleted: false) } else { let currentText = textField.text ?? "" if textField.tag > 1 && currentText.isEmpty { if let prevOTPField = viewWithTag(textField.tag - 1) as? UITextField { deleteText(in: prevOTPField) } } else { deleteText(in: textField) if textField.tag > 1 { if let prevOTPField = viewWithTag(textField.tag - 1) as? UITextField { prevOTPField.becomeFirstResponder() } } } } return false } private func deleteText(in textField: UITextField) { // If deleting the text, then move to previous text field if present secureEntryData[textField.tag - 1] = "" textField.text = "" if displayType == .diamond || displayType == .underlinedBottom { (textField as! OTPTextField).shapeLayer.fillColor = defaultBackgroundColor.cgColor (textField as! OTPTextField).shapeLayer.strokeColor = defaultBorderColor.cgColor } else { textField.backgroundColor = defaultBackgroundColor textField.layer.borderColor = defaultBorderColor.cgColor } textField.becomeFirstResponder() // Get the entered string calculateEnteredOTPSTring(isDeleted: true) } } ================================================ FILE: OTPFieldView/OTPTextField.swift ================================================ // // OTPTextField.swift // OTPFieldView // // Created by Vaibhav Bhasin on 10/09/19. // Copyright © 2019 Vaibhav Bhasin. All rights reserved. // // MIT License // // Copyright (c) 2019 Vaibhav Bhasin // // 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 @objc class OTPTextField: UITextField { /// Border color info for field public var otpBorderColor: UIColor = UIColor.black /// Border width info for field public var otpBorderWidth: CGFloat = 2 public var shapeLayer: CAShapeLayer! override func awakeFromNib() { super.awakeFromNib() } override init(frame: CGRect) { super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } public func initalizeUI(forFieldType type: DisplayType) { switch type { case .circular: layer.cornerRadius = bounds.size.width / 2 break case .roundedCorner: layer.cornerRadius = 4 break case .square: layer.cornerRadius = 0 break case .diamond: addDiamondMask() break case .underlinedBottom: addBottomView() break } // Basic UI setup if type != .diamond && type != .underlinedBottom { layer.borderColor = otpBorderColor.cgColor layer.borderWidth = otpBorderWidth } autocorrectionType = .no textAlignment = .center if #available(iOS 12.0, *) { textContentType = .oneTimeCode } } override func deleteBackward() { super.deleteBackward() _ = delegate?.textField?(self, shouldChangeCharactersIn: NSMakeRange(0, 0), replacementString: "") } // Helper function to create diamond view fileprivate func addDiamondMask() { let path = UIBezierPath() path.move(to: CGPoint(x: bounds.size.width / 2.0, y: 0)) path.addLine(to: CGPoint(x: bounds.size.width, y: bounds.size.height / 2.0)) path.addLine(to: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height)) path.addLine(to: CGPoint(x: 0, y: bounds.size.height / 2.0)) path.close() let maskLayer = CAShapeLayer() maskLayer.path = path.cgPath layer.mask = maskLayer shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath shapeLayer.lineWidth = otpBorderWidth shapeLayer.fillColor = backgroundColor?.cgColor shapeLayer.strokeColor = otpBorderColor.cgColor layer.addSublayer(shapeLayer) } // Helper function to create a underlined bottom view fileprivate func addBottomView() { let path = UIBezierPath() path.move(to: CGPoint(x: 0, y: bounds.size.height)) path.addLine(to: CGPoint(x: bounds.size.width, y: bounds.size.height)) path.close() shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath shapeLayer.lineWidth = otpBorderWidth shapeLayer.fillColor = backgroundColor?.cgColor shapeLayer.strokeColor = otpBorderColor.cgColor layer.addSublayer(shapeLayer) } } ================================================ FILE: OTPFieldView.podspec ================================================ Pod::Spec.new do |spec| spec.name = "OTPFieldView" spec.version = "1.0.1" spec.summary = "A CocoaPods library for One Time Password View written in Swift" spec.description = <<-DESC This library helps you create One-Time-Password view for iOS Applications DESC spec.homepage = "https://github.com/Root-vb/OTPFieldView" spec.license = { :type => "MIT", :file => "LICENSE" } spec.author = { "Vaibhav Bhasin" => "vaibhavbhasin15@gmail.com" } spec.ios.deployment_target = "10.3" spec.swift_version = "5.0" spec.source = { :git => "https://github.com/Root-vb/OTPFieldView.git", :tag => "#{spec.version}" } spec.source_files = "OTPFieldView/**/*.{h,m,swift}" end ================================================ FILE: OTPFieldView.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 13EDE57623278D8700B45FF6 /* OTPFieldView.h in Headers */ = {isa = PBXBuildFile; fileRef = 13EDE57423278D8700B45FF6 /* OTPFieldView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13EDE57D23278DC400B45FF6 /* OTPFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EDE57C23278DC400B45FF6 /* OTPFieldView.swift */; }; 13EDE57F23278E9A00B45FF6 /* OTPTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EDE57E23278E9A00B45FF6 /* OTPTextField.swift */; }; 13EDE5872327A8C400B45FF6 /* OTPFieldViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EDE5862327A8C400B45FF6 /* OTPFieldViewTests.swift */; }; 13EDE5892327A8C400B45FF6 /* OTPFieldView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13EDE57123278D8700B45FF6 /* OTPFieldView.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 13EDE58A2327A8C400B45FF6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 13EDE56823278D8700B45FF6 /* Project object */; proxyType = 1; remoteGlobalIDString = 13EDE57023278D8700B45FF6; remoteInfo = OTPFieldView; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 13EDE57123278D8700B45FF6 /* OTPFieldView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OTPFieldView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 13EDE57423278D8700B45FF6 /* OTPFieldView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTPFieldView.h; sourceTree = ""; }; 13EDE57523278D8700B45FF6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 13EDE57C23278DC400B45FF6 /* OTPFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPFieldView.swift; sourceTree = ""; }; 13EDE57E23278E9A00B45FF6 /* OTPTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPTextField.swift; sourceTree = ""; }; 13EDE5842327A8C400B45FF6 /* OTPFieldViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OTPFieldViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 13EDE5862327A8C400B45FF6 /* OTPFieldViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPFieldViewTests.swift; sourceTree = ""; }; 13EDE5882327A8C400B45FF6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 13EDE56E23278D8700B45FF6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 13EDE5812327A8C400B45FF6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 13EDE5892327A8C400B45FF6 /* OTPFieldView.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 13EDE56723278D8700B45FF6 = { isa = PBXGroup; children = ( 13EDE57323278D8700B45FF6 /* OTPFieldView */, 13EDE5852327A8C400B45FF6 /* OTPFieldViewTests */, 13EDE57223278D8700B45FF6 /* Products */, ); sourceTree = ""; }; 13EDE57223278D8700B45FF6 /* Products */ = { isa = PBXGroup; children = ( 13EDE57123278D8700B45FF6 /* OTPFieldView.framework */, 13EDE5842327A8C400B45FF6 /* OTPFieldViewTests.xctest */, ); name = Products; sourceTree = ""; }; 13EDE57323278D8700B45FF6 /* OTPFieldView */ = { isa = PBXGroup; children = ( 13EDE57423278D8700B45FF6 /* OTPFieldView.h */, 13EDE57523278D8700B45FF6 /* Info.plist */, 13EDE57C23278DC400B45FF6 /* OTPFieldView.swift */, 13EDE57E23278E9A00B45FF6 /* OTPTextField.swift */, ); path = OTPFieldView; sourceTree = ""; }; 13EDE5852327A8C400B45FF6 /* OTPFieldViewTests */ = { isa = PBXGroup; children = ( 13EDE5862327A8C400B45FF6 /* OTPFieldViewTests.swift */, 13EDE5882327A8C400B45FF6 /* Info.plist */, ); path = OTPFieldViewTests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 13EDE56C23278D8700B45FF6 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 13EDE57623278D8700B45FF6 /* OTPFieldView.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 13EDE57023278D8700B45FF6 /* OTPFieldView */ = { isa = PBXNativeTarget; buildConfigurationList = 13EDE57923278D8700B45FF6 /* Build configuration list for PBXNativeTarget "OTPFieldView" */; buildPhases = ( 13EDE56C23278D8700B45FF6 /* Headers */, 13EDE56D23278D8700B45FF6 /* Sources */, 13EDE56E23278D8700B45FF6 /* Frameworks */, 13EDE56F23278D8700B45FF6 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = OTPFieldView; productName = OTPFieldView; productReference = 13EDE57123278D8700B45FF6 /* OTPFieldView.framework */; productType = "com.apple.product-type.framework"; }; 13EDE5832327A8C400B45FF6 /* OTPFieldViewTests */ = { isa = PBXNativeTarget; buildConfigurationList = 13EDE58C2327A8C400B45FF6 /* Build configuration list for PBXNativeTarget "OTPFieldViewTests" */; buildPhases = ( 13EDE5802327A8C400B45FF6 /* Sources */, 13EDE5812327A8C400B45FF6 /* Frameworks */, 13EDE5822327A8C400B45FF6 /* Resources */, ); buildRules = ( ); dependencies = ( 13EDE58B2327A8C400B45FF6 /* PBXTargetDependency */, ); name = OTPFieldViewTests; productName = OTPFieldViewTests; productReference = 13EDE5842327A8C400B45FF6 /* OTPFieldViewTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 13EDE56823278D8700B45FF6 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1030; LastUpgradeCheck = 1030; ORGANIZATIONNAME = "Vaibhav Bhasin"; TargetAttributes = { 13EDE57023278D8700B45FF6 = { CreatedOnToolsVersion = 10.3; LastSwiftMigration = 1030; }; 13EDE5832327A8C400B45FF6 = { CreatedOnToolsVersion = 10.3; }; }; }; buildConfigurationList = 13EDE56B23278D8700B45FF6 /* Build configuration list for PBXProject "OTPFieldView" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 13EDE56723278D8700B45FF6; productRefGroup = 13EDE57223278D8700B45FF6 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 13EDE57023278D8700B45FF6 /* OTPFieldView */, 13EDE5832327A8C400B45FF6 /* OTPFieldViewTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 13EDE56F23278D8700B45FF6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 13EDE5822327A8C400B45FF6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 13EDE56D23278D8700B45FF6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 13EDE57D23278DC400B45FF6 /* OTPFieldView.swift in Sources */, 13EDE57F23278E9A00B45FF6 /* OTPTextField.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 13EDE5802327A8C400B45FF6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 13EDE5872327A8C400B45FF6 /* OTPFieldViewTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 13EDE58B2327A8C400B45FF6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 13EDE57023278D8700B45FF6 /* OTPFieldView */; targetProxy = 13EDE58A2327A8C400B45FF6 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 13EDE57723278D8700B45FF6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 13EDE57823278D8700B45FF6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 13EDE57A23278D8700B45FF6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 7X5CB8QV9R; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = OTPFieldView/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.Vaibhav.OTPFieldView; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 13EDE57B23278D8700B45FF6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 7X5CB8QV9R; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = OTPFieldView/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.Vaibhav.OTPFieldView; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; 13EDE58D2327A8C400B45FF6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 7X5CB8QV9R; INFOPLIST_FILE = OTPFieldViewTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.Vaibhav.OTPFieldViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 13EDE58E2327A8C400B45FF6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 7X5CB8QV9R; INFOPLIST_FILE = OTPFieldViewTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.Vaibhav.OTPFieldViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 13EDE56B23278D8700B45FF6 /* Build configuration list for PBXProject "OTPFieldView" */ = { isa = XCConfigurationList; buildConfigurations = ( 13EDE57723278D8700B45FF6 /* Debug */, 13EDE57823278D8700B45FF6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 13EDE57923278D8700B45FF6 /* Build configuration list for PBXNativeTarget "OTPFieldView" */ = { isa = XCConfigurationList; buildConfigurations = ( 13EDE57A23278D8700B45FF6 /* Debug */, 13EDE57B23278D8700B45FF6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 13EDE58C2327A8C400B45FF6 /* Build configuration list for PBXNativeTarget "OTPFieldViewTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 13EDE58D2327A8C400B45FF6 /* Debug */, 13EDE58E2327A8C400B45FF6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 13EDE56823278D8700B45FF6 /* Project object */; } ================================================ FILE: OTPFieldView.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: OTPFieldView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: OTPFieldView.xcodeproj/xcuserdata/vaibhavbhasin.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState OTPFieldView.xcscheme_^#shared#^_ orderHint 0 OTPFieldViewTests.xcscheme_^#shared#^_ orderHint 1 ================================================ FILE: OTPFieldViewTests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: OTPFieldViewTests/OTPFieldViewTests.swift ================================================ // // OTPFieldViewTests.swift // OTPFieldViewTests // // Created by Vaibhav Bhasin on 10/09/19. // Copyright © 2019 Vaibhav Bhasin. All rights reserved. // import XCTest class OTPFieldViewTests: XCTestCase { override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. self.measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: README.md ================================================ # OTPFieldView One Time Password View for iOS. Built in Swift 5

Swift 5 compatible Platform iOS License: MIT

### Preview ![demo](Screenshots/IMG_0198.PNG) ![demo](Screenshots/IMG_0199.PNG) ![demo](Screenshots/IMG_0200.PNG) ![demo](Screenshots/IMG_0201.PNG) ![demo](Screenshots/IMG_0202.PNG) ## Installation ### CocoaPods Add the following line to your Podfile: ```ruby pod 'OTPFieldView' ``` Then run the following in the same directory as your Podfile: ```ruby pod install ``` ## Usage ### Code ```swift @IBOutlet var otpTextFieldView: OTPFieldView! func setupOtpView(){ self.otpTextFieldView.fieldsCount = 5 self.otpTextFieldView.fieldBorderWidth = 2 self.otpTextFieldView.defaultBorderColor = UIColor.black self.otpTextFieldView.filledBorderColor = UIColor.green self.otpTextFieldView.cursorColor = UIColor.red self.otpTextFieldView.displayType = .underlinedBottom self.otpTextFieldView.fieldSize = 40 self.otpTextFieldView.separatorSpace = 8 self.otpTextFieldView.shouldAllowIntermediateEditing = false self.otpTextFieldView.delegate = self self.otpTextFieldView.initializeUI() } ``` The `becomeFirstResponderAtIndex` property sets the pinField at the specified index as the first responder. The `isContentTypeOneTimeCode` property sets the contentType of the first pinField to `.oneTimeCode` to leverage the iOS 12 feature where the passcode is directly fetched from the messages. #### Styles ```swift enum DisplayType: Int { case circular case roundedCorner case square case diamond case underlinedBottom } ``` ### Delegate Methods ```swift extension OtpViewController: OTPFieldViewDelegate { func hasEnteredAllOTP(hasEnteredAll hasEntered: Bool) -> Bool { print("Has entered all OTP? \(hasEntered)") return false } func shouldBecomeFirstResponderForOTP(otpTextFieldIndex index: Int) -> Bool { return true } func enteredOTP(otp otpString: String) { print("OTPString: \(otpString)") } } ``` - **hasEnteredAllOTP()**: Returns true when all text Fields are full. - **shouldBecomeFirstResponderForOTP()**: Show keyboard automatically. - **enteredOTP()**: Get entered pin. ### Properties - **.displayType**: Display type for Text Field. - **.fieldsCount**: Length of OTP. - **.otpInputType**: Input type for Text Field : numeric, alphabet, alphaNumeric. - **.fieldFont**: Font for Text Field. - **.secureEntry**: Shows • instead of text. - **.hideEnteredText**: Hides the text. - **.requireCursor**: Shows/Hides cursor. - **.cursorColor**: Color for Cursor. - **.fieldSize**: Size of Text Field. - **.separatorSpace**: Space between Text Fields. - **.fieldBorderWidth**: Border width for Text Fields. - **.shouldAllowIntermediateEditing**: Allow to edit from middle. - **.defaultBackgroundColor**: Empty text field background color. - **.filledBackgroundColor**: Filled text field background color. - **.defaultBorderColor**: Empty text field border color. - **.filledBorderColor**: Filled text field border color. - **.errorBorderColor**: Error text field border color. - **.delegate**: delegate. ## License OTPFieldView is available under the MIT license. See LICENSE for details.