├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── OTPView │ └── OTPView.swift ├── Tests └── OTPViewTests │ └── OTPViewTests.swift └── example.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | xcuserdata/ 11 | *.xcscmblueprint 12 | *.xccheckout 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | *.hmap 25 | *.ipa 26 | *.dSYM.zip 27 | *.dSYM 28 | timeline.xctimeline 29 | playground.xcworkspace 30 | .build/ 31 | Carthage/Build/ 32 | Dependencies/ 33 | .accio/ 34 | fastlane/report.xml 35 | fastlane/Preview.html 36 | fastlane/screenshots/**/*.png 37 | fastlane/test_output 38 | iOSInjectionProject/ -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mukesh Solanki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.8 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: "OTPView", 8 | platforms: [.iOS(.v15)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, and make them visible to other packages. 11 | .library( 12 | name: "OTPView", 13 | targets: ["OTPView"]), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | // .package(url: /* package url */, from: "1.0.0"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 22 | .target( 23 | name: "OTPView", 24 | dependencies: []), 25 | .testTarget( 26 | name: "OTPViewTests", 27 | dependencies: ["OTPView"]), 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

SwiftUI OtpView/PinView

2 |

3 | Swift Package Manager 4 | Twitter Follow 5 | GitHub release (latest by date) 6 | 7 |

8 | A custom view to enter a code usually in cases of authentication. 9 |

10 | 11 |    12 | 13 | # Supporting SwiftUI OtpView/PinView 14 | 15 | SwiftUI OtpView/PinView is an independent project with ongoing development and support made possible thanks to your donations. 16 | - [Become a backer](https://www.paypal.me/mukeshsolanki) 17 | 18 | ## How to integrate into your app? 19 | Integrating the project is simple. All you need to do is follow the below steps 20 | 21 | ###Swift Package Manager 22 | 23 | The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. 24 | 25 | Once you have your Swift package set up, adding OtpView as a dependency is as easy as adding it to the dependencies value of your Package.swift. 26 | 27 | ``` 28 | dependencies: [ 29 | .package(url: "https://github.com/mukeshsolanki/otpview-swiftui.git", .upToNextMajor(from: "1.0.0")) 30 | ] 31 | ``` 32 | 33 | ## How to use the library? 34 | Okay seems like you integrated the library in your project but **how do you use it**? Well its really easy. 35 | Just use the `OtpView` where you need to display the view like. 36 | ```swift 37 | OtpView(activeIndicatorColor: Color.black, inactiveIndicatorColor: Color.gray, length: 4, doSomething: { value in 38 | // Do something with the value input 39 | }) 40 | .padding() 41 | ``` 42 | That's pretty much it and your all wrapped up. 43 | 44 | ## Author 45 | Maintained by [Mukesh Solanki](https://www.github.com/mukeshsolanki) 46 | 47 | ## Contribution 48 | [![GitHub contributors](https://img.shields.io/github/contributors/mukeshsolanki/otpview-swiftui.svg)](https://github.com/mukeshsolanki/otpview-swiftui/graphs/contributors) 49 | 50 | * Bug reports and pull requests are welcome. 51 | 52 | ## License 53 | ``` 54 | MIT License 55 | 56 | Copyright (c) 2018 Mukesh Solanki 57 | 58 | Permission is hereby granted, free of charge, to any person obtaining a copy 59 | of this software and associated documentation files (the "Software"), to deal 60 | in the Software without restriction, including without limitation the rights 61 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 62 | copies of the Software, and to permit persons to whom the Software is 63 | furnished to do so, subject to the following conditions: 64 | 65 | The above copyright notice and this permission notice shall be included in all 66 | copies or substantial portions of the Software. 67 | 68 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 69 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 70 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 71 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 72 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 73 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 74 | SOFTWARE. 75 | ``` 76 | -------------------------------------------------------------------------------- /Sources/OTPView/OTPView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @available(iOS 15.0, *) 4 | public struct OtpView:View { 5 | 6 | private var activeIndicatorColor: Color 7 | private var inactiveIndicatorColor: Color 8 | private let doSomething: (String) -> Void 9 | private let length: Int 10 | 11 | @State private var otpText = "" 12 | @FocusState private var isKeyboardShowing: Bool 13 | 14 | public init(activeIndicatorColor:Color,inactiveIndicatorColor:Color, length:Int, doSomething: @escaping (String) -> Void) { 15 | self.activeIndicatorColor = activeIndicatorColor 16 | self.inactiveIndicatorColor = inactiveIndicatorColor 17 | self.length = length 18 | self.doSomething = doSomething 19 | } 20 | public var body: some View { 21 | HStack(spacing: 0){ 22 | ForEach(0...length-1, id: \.self) { index in 23 | OTPTextBox(index) 24 | } 25 | }.background(content: { 26 | TextField("", text: $otpText.limit(4)) 27 | .keyboardType(.numberPad) 28 | .textContentType(.oneTimeCode) 29 | .frame(width: 1, height: 1) 30 | .opacity(0.001) 31 | .blendMode(.screen) 32 | .focused($isKeyboardShowing) 33 | .onChange(of: otpText) { newValue in 34 | if newValue.count == length { 35 | doSomething(newValue) 36 | } 37 | } 38 | .onAppear { 39 | DispatchQueue.main.async { 40 | isKeyboardShowing = true 41 | } 42 | } 43 | }) 44 | .contentShape(Rectangle()) 45 | .onTapGesture { 46 | isKeyboardShowing = true 47 | } 48 | } 49 | 50 | @ViewBuilder 51 | func OTPTextBox(_ index: Int) -> some View { 52 | ZStack{ 53 | if otpText.count > index { 54 | let startIndex = otpText.startIndex 55 | let charIndex = otpText.index(startIndex, offsetBy: index) 56 | let charToString = String(otpText[charIndex]) 57 | Text(charToString) 58 | } else { 59 | Text(" ") 60 | } 61 | } 62 | .frame(width: 45, height: 45) 63 | .background { 64 | let status = (isKeyboardShowing && otpText.count == index) 65 | RoundedRectangle(cornerRadius: 6, style: .continuous) 66 | .stroke(status ? activeIndicatorColor : inactiveIndicatorColor) 67 | .animation(.easeInOut(duration: 0.2), value: status) 68 | 69 | } 70 | .padding() 71 | } 72 | } 73 | 74 | @available(iOS 13.0, *) 75 | extension Binding where Value == String { 76 | func limit(_ length: Int)->Self { 77 | if self.wrappedValue.count > length { 78 | DispatchQueue.main.async { 79 | self.wrappedValue = String(self.wrappedValue.prefix(length)) 80 | } 81 | } 82 | return self 83 | } 84 | } 85 | 86 | @available(iOS 15.0, *) 87 | struct OTPView_Previews: PreviewProvider { 88 | static var previews: some View { 89 | OtpView(activeIndicatorColor: Color.black, inactiveIndicatorColor: Color.gray, length: 4, doSomething: { value in 90 | 91 | }) 92 | .padding() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Tests/OTPViewTests/OTPViewTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import OTPView 3 | 4 | final class OTPViewTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | // XCTAssertEqual(OTPView().text, "Hello, World!") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mukeshsolanki/otpview-swiftui/a4b5e210ffce6ff5b7889c212939df25a3c24dd4/example.gif --------------------------------------------------------------------------------