Skip to content

Commit

Permalink
1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
relatedcode committed Apr 7, 2021
1 parent 5b06bfa commit c1d216e
Show file tree
Hide file tree
Showing 27 changed files with 2,108 additions and 0 deletions.
18 changes: 18 additions & 0 deletions PasscodeKit.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Pod::Spec.new do |s|
s.name = 'PasscodeKit'
s.version = '1.0.0'
s.license = 'MIT'

s.summary = 'A lightweight and easy-to-use Passcode Kit for iOS.'
s.homepage = 'https://relatedcode.com'
s.author = { 'Related Code' => 'info@relatedcode.com' }

s.source = { :git => 'https://github.com/relatedcode/PasscodeKit.git', :tag => s.version }
s.source_files = 'PasscodeKit/Sources/*.swift'

s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.0' }

s.swift_version = '5.0'
s.platform = :ios, '12.0'
s.requires_arc = true
end
292 changes: 292 additions & 0 deletions PasscodeKit/Sources/PasscodeKit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
//
// Copyright (c) 2021 Related Code - https://relatedcode.com
//
// 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
import CryptoKit

//-----------------------------------------------------------------------------------------------------------------------------------------------
@objc public protocol PasscodeKitDelegate {

@objc optional func passcodeCreated(_ passcode: String)
@objc optional func passcodeChanged(_ passcode: String)
@objc optional func passcodeRemoved()

@objc optional func passcodeCheckedButDisabled()
@objc optional func passcodeEnteredSuccessfully()
@objc optional func passcodeMaximumFailedAttempts()
}

//-----------------------------------------------------------------------------------------------------------------------------------------------
public class PasscodeKit: NSObject {

static let shared: PasscodeKit = {
let instance = PasscodeKit()
return instance
}()

static var passcodeLength = 4
static var allowedFailedAttempts = 3

static var textColor = UIColor.darkText
static var backgroundColor = UIColor.lightGray

static var failedTextColor = UIColor.white
static var failedBackgroundColor = UIColor.systemRed

static var titleEnterPasscode = "Enter Passcode"
static var titleCreatePasscode = "Create Passcode"
static var titleChangePasscode = "Change Passcode"
static var titleRemovePasscode = "Remove Passcode"

static var textEnterPasscode = "Enter your passcode"
static var textVerifyPasscode = "Verify your passcode"
static var textEnterOldPasscode = "Enter your old passcode"
static var textEnterNewPasscode = "Enter your new passcode"
static var textVerifyNewPasscode = "Verify your new passcode"
static var textFailedPasscode = "%d Failed Passcode Attempts"
static var textPasscodeMismatch = "Passcodes did not match. Try again."
static var textTouchIDAccessReason = "Please use Touch ID to unlock the app"

public static var delegate: PasscodeKitDelegate?

//-------------------------------------------------------------------------------------------------------------------------------------------
public override init() {

super.init()

if #available(iOS 13.0, *) {
PasscodeKit.textColor = UIColor.label
PasscodeKit.backgroundColor = UIColor.systemGroupedBackground
} else {
PasscodeKit.backgroundColor = UIColor.groupTableViewBackground
}
}

//-------------------------------------------------------------------------------------------------------------------------------------------
public class func start() {

shared.start()
}

//-------------------------------------------------------------------------------------------------------------------------------------------
public class func dismiss() {

if (PasscodeKit.enabled()) {
if let navigationController = shared.topViewController() as? UINavigationController {
if let presentedView = navigationController.viewControllers.first {
if (presentedView is PasscodeKitVerify) {
presentedView.dismiss(animated: true)
}
}
}
}
}
}

//-----------------------------------------------------------------------------------------------------------------------------------------------
extension PasscodeKit {

//-------------------------------------------------------------------------------------------------------------------------------------------
private func start() {

let didFinishLaunching = UIApplication.didFinishLaunchingNotification
let willEnterForeground = UIApplication.willEnterForegroundNotification

NotificationCenter.default.addObserver(self, selector: #selector(verifyPasscode), name: didFinishLaunching, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(verifyPasscode), name: willEnterForeground, object: nil)
}

//-------------------------------------------------------------------------------------------------------------------------------------------
@objc private func verifyPasscode() {

if (PasscodeKit.enabled()) {
if let viewController = topViewController() {
if (noPasscodePresented(viewController)) {
presentPasscodeVerify(viewController)
}
}
} else {
PasscodeKit.delegate?.passcodeCheckedButDisabled?()
}
}

//-------------------------------------------------------------------------------------------------------------------------------------------
private func presentPasscodeVerify(_ viewController: UIViewController) {

DispatchQueue.main.async {
let passcodeKitVerify = PasscodeKitVerify()
passcodeKitVerify.delegate = PasscodeKit.delegate
let navController = PasscodeKitNavController(rootViewController: passcodeKitVerify)
viewController.present(navController, animated: false)
}
}

//-------------------------------------------------------------------------------------------------------------------------------------------
private func noPasscodePresented(_ viewController: UIViewController) -> Bool {

var result = true
if let navigationController = viewController as? UINavigationController {
if let presentedView = navigationController.viewControllers.first {
if (presentedView is PasscodeKitCreate) { result = false }
if (presentedView is PasscodeKitChange) { result = false }
if (presentedView is PasscodeKitRemove) { result = false }
if (presentedView is PasscodeKitVerify) { result = false }
}
}
return result
}

//-------------------------------------------------------------------------------------------------------------------------------------------
private func topViewController() -> UIViewController? {

var keyWindow: UIWindow?

if #available(iOS 13.0, *) {
keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
} else {
keyWindow = UIApplication.shared.keyWindow
}

var viewController = keyWindow?.rootViewController
while (viewController?.presentedViewController != nil) {
viewController = viewController?.presentedViewController
}
return viewController
}
}

//-----------------------------------------------------------------------------------------------------------------------------------------------
extension PasscodeKit {

//-------------------------------------------------------------------------------------------------------------------------------------------
public class func createPasscode(_ viewController: UIViewController) {

let passcodeKitCreate = PasscodeKitCreate()
passcodeKitCreate.delegate = viewController as? PasscodeKitDelegate
let navController = PasscodeKitNavController(rootViewController: passcodeKitCreate)
viewController.present(navController, animated: true)
}

//-------------------------------------------------------------------------------------------------------------------------------------------
public class func changePasscode(_ viewController: UIViewController) {

let passcodeKitChange = PasscodeKitChange()
passcodeKitChange.delegate = viewController as? PasscodeKitDelegate
let navController = PasscodeKitNavController(rootViewController: passcodeKitChange)
viewController.present(navController, animated: true)
}

//-------------------------------------------------------------------------------------------------------------------------------------------
public class func removePasscode(_ viewController: UIViewController) {

let passcodeKitRemove = PasscodeKitRemove()
passcodeKitRemove.delegate = viewController as? PasscodeKitDelegate
let navController = PasscodeKitNavController(rootViewController: passcodeKitRemove)
viewController.present(navController, animated: true)
}
}

// MARK: - Passcode methods
//-----------------------------------------------------------------------------------------------------------------------------------------------
extension PasscodeKit {

//-------------------------------------------------------------------------------------------------------------------------------------------
public class func enabled() -> Bool {

return (UserDefaults.standard.string(forKey: "PasscodeValue") != nil)
}

//-------------------------------------------------------------------------------------------------------------------------------------------
public class func verify(_ passcode: String) -> Bool {

if (passcode != "") {
return (UserDefaults.standard.string(forKey: "PasscodeValue") == sha256(passcode))
}
return (UserDefaults.standard.string(forKey: "PasscodeValue") == nil)
}

//-------------------------------------------------------------------------------------------------------------------------------------------
public class func update(_ passcode: String) {

if (passcode != "") {
UserDefaults.standard.set(sha256(passcode), forKey: "PasscodeValue")
} else {
UserDefaults.standard.removeObject(forKey: "PasscodeValue")
UserDefaults.standard.removeObject(forKey: "PasscodeBiometric")
}
}

//-------------------------------------------------------------------------------------------------------------------------------------------
public class func remove() {

UserDefaults.standard.removeObject(forKey: "PasscodeValue")
UserDefaults.standard.removeObject(forKey: "PasscodeBiometric")
}

//-------------------------------------------------------------------------------------------------------------------------------------------
public class func biometric() -> Bool {

return UserDefaults.standard.bool(forKey: "PasscodeBiometric")
}

//-------------------------------------------------------------------------------------------------------------------------------------------
public class func biometric(_ value: Bool) {

UserDefaults.standard.set(value, forKey: "PasscodeBiometric")
}

//-------------------------------------------------------------------------------------------------------------------------------------------
private class func sha256(_ text: String) -> String {

if #available(iOS 13.0, *) {
let data = Data(text.utf8)
let hash = SHA256.hash(data: data)
return hash.compactMap { String(format: "%02x", $0) }.joined()
}
return text
}
}

// MARK: - PasscodeKitNavController
//-----------------------------------------------------------------------------------------------------------------------------------------------
class PasscodeKitNavController: UINavigationController {

//-------------------------------------------------------------------------------------------------------------------------------------------
override func viewDidLoad() {

super.viewDidLoad()

if #available(iOS 13.0, *) {
self.isModalInPresentation = true
self.modalPresentationStyle = .fullScreen
}

navigationBar.isTranslucent = false
}

//-------------------------------------------------------------------------------------------------------------------------------------------
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {

return .portrait
}

//-------------------------------------------------------------------------------------------------------------------------------------------
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {

return .portrait
}

//-------------------------------------------------------------------------------------------------------------------------------------------
override var shouldAutorotate: Bool {

return false
}
}

0 comments on commit c1d216e

Please sign in to comment.