├── .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 |
4 |
5 |
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 | [](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
--------------------------------------------------------------------------------