balochcodes/fandeckotp
A premium, interactive OTP & PIN input component for SwiftUI featuring a 2-stage playing card fan-and-stack entrance animation, revolving neon border glow, tactile haptics, and spring-driven error shake physics.
β¨ Features
- π 2-Stage Playing Card Fan Entrance: Digit boxes start fanned out like a poker hand and smoothly snap into a clean horizontal alignment using spring physics.
- β‘ Revolving Neon Border Loader: Dual-layer animated gradient sweep (soft ambient bloom + crisp neon line) rotating around the active digit box.
- π³ Tactile Haptics: Authentic keystroke click feedback (
UIImpactFeedbackGenerator) and failure alerts (UINotificationFeedbackGenerator). - π³ Interactive Error Shake: Fluid multi-oscillation horizontal shake with red warning glow when an invalid code is entered.
- π PIN / Obscure Mode: Seamlessly toggle between plain numeric codes and obscured bullet points (
β’). - π² SMS Auto-Fill & Paste Ready: Built on native text responder with
.oneTimeCodesupport for automatic 1-tap SMS OTP suggestions. - ποΈ ObservableObject Controller: Programmatically observe text, trigger error shakes, clear digits, or pre-fill codes via
FanDeckOTPController. - π¨ Deep Theming: Configure box dimensions, corner radiuses, border colors, fonts, neon gradient stops, and fan angles via
FanDeckOTPTheme. - β‘ Zero External Dependencies: 100% native SwiftUI and Combine.
π¦ Installation
In Xcode (Recommended)
- In Xcode, open your project and go to File β Add Package Dependencies...
- In the search bar, paste the repository URL:
``text https://github.com/ShahzainBaloch/FanDeckOTP.git ``
- Select Up to Next Major Version (
1.0.0) and click Add Package.
In Package.swift
If configuring a Swift package, add it to your dependencies:
dependencies: [
.package(url: "https://github.com/ShahzainBaloch/FanDeckOTP.git", from: "1.0.0")
]Import the framework in your SwiftUI files:
import SwiftUI
import FanDeckOTPπ Usage Examples
1. Standard 6-Digit OTP with Completion Callback
import SwiftUI
import FanDeckOTP
struct VerificationView: View {
@State private var otpCode = ""
var body: some View {
VStack(spacing: 24) {
Text("Enter Verification Code")
.font(.headline)
FanDeckOTPField(
text: $otpCode,
length: 6,
autoFocus: true,
onChanged: { code in
print("Current code: \(code)")
},
onCompleted: { code in
print("Verification complete: \(code)")
}
)
Button("Verify") {
// Submit code
}
.disabled(otpCode.count < 6)
}
.padding()
}
}2. Sleek Dark Neon Theme with 4-Digit PIN Mode
FanDeckOTPField(
text: $pinCode,
length: 4,
theme: FanDeckOTPTheme(
boxWidth: 56,
boxHeight: 66,
boxCornerRadius: 16,
boxSpacing: 14,
textColor: .white,
backgroundColor: Color.white.opacity(0.06),
activeBackgroundColor: Color.white.opacity(0.12),
filledBackgroundColor: Color.white.opacity(0.08),
neonColors: [
Color(red: 0.54, green: 0.36, blue: 0.96), // Violet
Color(red: 0.0, green: 0.94, blue: 1.0), // Cyan
Color(red: 0.54, green: 0.36, blue: 0.96)
]
),
obscureText: true,
obscuringCharacter: "β’"
)3. Full Authentication Screen with Async Verification & Error Shake
import SwiftUI
import FanDeckOTP
struct SecureLoginScreen: View {
@StateObject private var otpController = FanDeckOTPController(length: 6)
@State private var isVerifying = false
var body: some View {
VStack(spacing: 32) {
VStack(spacing: 8) {
Image(systemName: "lock.shield.fill")
.font(.system(size: 48))
.foregroundColor(.blue)
Text("Two-Factor Authentication")
.font(.title2.bold())
Text("Enter the 6-digit code sent to your phone")
.font(.subheadline)
.foregroundColor(.secondary)
}
FanDeckOTPField(
controller: otpController,
isLoading: isVerifying,
autoFocus: true,
onCompleted: { code in
verifyCode(code)
}
)
if isVerifying {
ProgressView("Verifying credentials...")
}
}
.padding(32)
}
private func verifyCode(_ code: String) {
isVerifying = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
isVerifying = false
if code == "123456" {
print("Login successful!")
} else {
// Invalid code: trigger error shake & reset
otpController.triggerError()
otpController.clear()
}
}
}
}π οΈ API Reference
FanDeckOTPField
| Property | Type | Default | Description | | :--- | :--- | :--- | :--- | | text | Binding<String> | Required | Two-way binding for entered digits. | | length | Int | 6 | Total count of digit boxes (minimum 2). | | theme | FanDeckOTPTheme | FanDeckOTPTheme() | Visual appearance & animation timing. | | obscureText | Bool | false | When true, obscures digits for PIN privacy. | | obscuringCharacter | String | "β’" | Replacement character displayed when obscured. | | isLoading | Bool | false | When true, displays rotating neon loader across boxes. | | errorTrigger | Int | 0 | Increments trigger horizontal error shake animation. | | autoFocus | Bool | false | Automatically brings up the keyboard on appear. | | onChanged | ((String) -> Void)? | nil | Callback triggered whenever input text updates. | | onCompleted | ((String) -> Void)? | nil | Callback triggered once all digits are entered. |
FanDeckOTPTheme
| Property | Type | Default | Description | | :--- | :--- | :--- | :--- | | boxWidth | CGFloat | 48 | Width of each digit box. | | boxHeight | CGFloat | 58 | Height of each digit box. | | boxCornerRadius | CGFloat | 12 | Corner radius of the boxes. | | boxSpacing | CGFloat | 10 | Horizontal spacing between boxes. | | font | Font | .rounded.bold (24) | Typography font of the numbers. | | neonColors | [Color] | [#2D7BD9, #00F0FF, #2D7BD9] | Gradient colors for the rotating neon border. | | enableRotatingBorder | Bool | true | Whether the active box has rotating neon glow. | | enableHaptics | Bool | true | Enables tactile vibration on typing and errors. | | fanSpreadAngle | Double | 16.0 | Maximum degrees spread during entrance fan. | | fanDuration | Double | 0.75 | Duration of the card fan entrance animation. |
FanDeckOTPController
| Method / Property | Type | Description | | :--- | :--- | :--- | | text | String | Published string representing entered OTP. | | isComplete | Bool | Returns true if all digits are entered. | | clear() | Void | Resets entered text to empty string. | | triggerError() | Void | Triggers error shake animation and red glow. | | setCode(String) | Void | Programmatically populates digits. |
π License
This project is licensed under the MIT License - see the LICENSE file for details.
Author
Created by Shahzain Baloch.
Package Metadata
Repository: balochcodes/fandeckotp
Default branch: main
README: README.md