A secure, customizable PIN entry and setup view for iOS and iPadOS built with SwiftUI. Features encrypted PIN storage, lockout protection, and a beautiful, modern UI.
- 🔐 Secure PIN Storage: AES-256-GCM encryption with device-specific keys
- 🛡️ File Protection: Uses
NSFileProtectionCompletefor encrypted storage - 🔒 Lockout Protection: Automatic lockout after maximum failed attempts
- 🎨 Modern UI: Beautiful, customizable SwiftUI interface
- 📱 iOS & iPadOS Support: Optimized for both platforms
- ⚡ Easy Integration: Simple API with minimal setup required
- 🎯 Two Views: Separate views for PIN entry and PIN setup
- 🔄 Auto-validation: Automatic PIN validation with error handling
The main PIN entry screen for authenticating users.
The PIN setup screen with entry and confirmation fields.
Error message display when PIN is invalid.
Lockout screen when maximum attempts are reached.
Note: To add screenshots:
- Create a
Screenshotsfolder in your repository root- Add your screenshot images (PNG or JPG format recommended)
- Update the image paths above to match your screenshot filenames
- For best results, use screenshots with consistent dimensions (e.g., iPhone 14 Pro size: 1179x2556)
- iOS 15.0+ / iPadOS 15.0+
- Swift 6.2+
- Xcode 15.0+
- In Xcode, go to File → Add Package Dependencies...
- Enter the repository URL:
https://github.com/kamalkumar1/KKPinView_SwiftUI.git - Select the version rule (recommended: Up to Next Major Version)
- Click Add Package
Alternatively, add the following to your Package.swift:
dependencies: [
.package(url: "https://github.com/kamalkumar1/KKPinView_SwiftUI.git", from: "1.0.0")
]import SwiftUI
import KKPinViewUse KKPINSetUPView when the user needs to set up a new PIN:
struct SetupView: View {
var body: some View {
KKPINSetUPView(
onSetupComplete: { pin in
print("PIN setup completed!")
// Navigate to main screen or authenticate
}
)
}
}Use KKPinViews when the user needs to enter their PIN:
struct LoginView: View {
var body: some View {
KKPinViews(
onForgotPin: {
print("Forgot PIN tapped")
// Handle forgot PIN flow
},
onSubmit: { isValid in
if isValid {
print("PIN is valid - access granted")
// Navigate to authenticated screen
} else {
print("PIN is invalid")
// Error is automatically displayed
}
},
showForgotPin: true
)
}
}import SwiftUI
import KKPinView
struct ContentView: View {
@State private var isAuthenticated = false
@State private var hasPIN = false
var body: some View {
Group {
if !hasPIN {
// Show PIN setup if no PIN exists
KKPINSetUPView(
onSetupComplete: { pin in
hasPIN = true
isAuthenticated = true
}
)
} else if !isAuthenticated {
// Show PIN entry for authentication
KKPinViews(
onForgotPin: {
// Handle forgot PIN
hasPIN = false
},
onSubmit: { isValid in
if isValid {
isAuthenticated = true
}
}
)
} else {
// Authenticated - show main content
MainContentView()
}
}
.onAppear {
// Check if PIN exists
hasPIN = KKPinStorage.hasStoredPIN()
}
}
}The default PIN length is 4 digits. To change it, modify KKPinviewConstant.totalDigits:
// In your app initialization or configuration
KKPinviewConstant.totalDigits = 6 // For 6-digit PINNote: This requires modifying the package source. For production use, consider making this configurable via initialization parameters.
KKPinViews(
onForgotPin: {
// Option 1: Delete PIN and show setup screen
KKPinStorage.deletePIN()
// Navigate to setup screen
// Option 2: Show recovery options
showRecoveryOptions = true
},
onSubmit: { isValid in
// Handle authentication
}
)Main PIN entry view for authenticating users.
KKPinViews(
onForgotPin: (() -> Void)? = nil,
onSubmit: ((Bool) -> Void)? = nil,
showForgotPin: Bool = true
)onForgotPin: Optional callback when "Forgot PIN?" is tappedonSubmit: Callback with validation result (trueif PIN is valid,falseotherwise)showForgotPin: Whether to show the "Forgot PIN?" button (default:true)
- Automatically validates PIN when all digits are entered
- Displays error messages for invalid PINs
- Handles lockout automatically (disables input when locked out)
- Clears PIN fields after validation
PIN setup view for creating a new PIN with confirmation.
KKPINSetUPView(
onSetupComplete: ((String) -> Void)? = nil
)onSetupComplete: Optional callback when PIN setup is completed successfully. Receives the PIN string.
- Two-step flow: Enter PIN → Confirm PIN
- Validates that both PINs match
- Automatically saves PIN when both match
- Clears previous PIN and lockout state before saving
- Displays success/error messages with animations
High-level API for securely storing and retrieving PINs.
// Save a PIN
static func savePIN(_ pin: String) -> Bool
// Load stored PIN
static func loadPIN() -> String?
// Verify a PIN
static func verifyPIN(_ pin: String) -> Bool
// Check if PIN exists
static func hasStoredPIN() -> Bool
// Delete stored PIN
static func deletePIN()// Save PIN
if KKPinStorage.savePIN("1234") {
print("PIN saved successfully")
}
// Verify PIN
if KKPinStorage.verifyPIN("1234") {
print("PIN is correct")
}
// Check if PIN exists
if KKPinStorage.hasStoredPIN() {
// Show PIN entry screen
} else {
// Show PIN setup screen
}Manages PIN validation attempts and lockout logic.
let manager = KKPinLockoutManager(
maxAttempts: 5, // Default: 5
lockoutDurationMinutes: 5 // Default: 5 minutes
)failedAttempts: Int- Current number of failed attemptsmaxAttempts: Int- Maximum allowed attemptsisLockedOut: Bool- Whether currently locked outremainingLockoutMinutes: Int- Remaining lockout timehasReachedMaxAttempts: Bool- Whether max attempts reached
// Validate PIN (handles attempt tracking)
func validatePIN(_ pin: String) -> Bool
// Reset failed attempts
func resetFailedAttempts()
// Check lockout status
func checkLockoutStatus()
// Get error message
func getErrorMessage() -> String?let manager = KKPinLockoutManager()
if manager.isLockedOut {
print("Locked out for \(manager.remainingLockoutMinutes) minutes")
}
if manager.validatePIN("1234") {
print("PIN is valid")
} else {
if let error = manager.getErrorMessage() {
print(error)
}
}- Algorithm: AES-256-GCM (Galois/Counter Mode)
- Key Derivation: PBKDF2-HMAC-SHA256 with device-specific salt
- Key Storage: Secure keychain storage with device binding
- Protection Type:
NSFileProtectionComplete - Access: Files are only accessible when device is unlocked
- Storage Location: Application Support directory
- Default Max Attempts: 5
- Default Lockout Duration: 5 minutes
- Configurable: Can be customized via
KKPinLockoutManager
- PINs are encrypted before storage
- Encryption keys are device-specific (cannot be transferred)
- Files are protected at the OS level
- Failed attempts are tracked and enforced
- Lockout state persists across app launches
Most UI elements can be customized via KKPinviewConstant:
// Colors
KKPinviewConstant.backgroundColor
KKPinviewConstant.textColor
KKPinviewConstant.errorTextColor
KKPinviewConstant.successTextColor
// Fonts
KKPinviewConstant.titleFontSize
KKPinviewConstant.subtitleFontSize
// Dimensions
KKPinviewConstant.totalDigits // PIN length (4 or 6)
KKPinviewConstant.fieldHeight
KKPinviewConstant.fieldSpacing
// Strings
KKPinviewConstant.titleTextFormat
KKPinviewConstant.subtitleText
KKPinviewConstant.forgotPinText// In KKPinviewConstant.swift
public static let maxPinAttempts: Int = 5
public static let pinLockoutDurationMinutes: Int = 5KKPinView/
├── Views/
│ ├── KKPinViews.swift # PIN entry view
│ ├── KKPINSetUPView.swift # PIN setup view
│ ├── PinDigitField.swift # Individual digit field
│ └── NumericKeypad.swift # Custom keypad
├── Storage/
│ ├── KKPinStorage.swift # High-level storage API
│ └── KeyGenerator/
│ ├── KKEncryptionHelper.swift # AES-256-GCM encryption
│ ├── KKSecureKeyGenerator.swift # Key generation
│ └── KKSecureKey.swift # Key wrapper
├── Security/
│ └── KKPinLockoutManager.swift # Lockout management
└── Constants/
└── KKPinviewConstant.swift # Configuration constants
The package includes comprehensive test coverage:
# Run tests
cd KKPinView
swift test
# Run tests with code coverage
swift test --enable-code-coverageContributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
For issues, questions, or feature requests, please open an issue on GitHub.
Created by kamalkumar
Made with ❤️ using SwiftUI



