From e4b5289ad29e84783dc58a7beec3e8ddf9f5d27a Mon Sep 17 00:00:00 2001 From: iamStephenFang Date: Wed, 15 Jun 2022 21:07:06 +0800 Subject: [PATCH 1/6] fix: UINavigationBarAppearance support for iOS15 --- PasscodeKit/Sources/PasscodeKit.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/PasscodeKit/Sources/PasscodeKit.swift b/PasscodeKit/Sources/PasscodeKit.swift index 4d60343..b307cd0 100755 --- a/PasscodeKit/Sources/PasscodeKit.swift +++ b/PasscodeKit/Sources/PasscodeKit.swift @@ -268,8 +268,16 @@ class PasscodeKitNavController: UINavigationController { self.isModalInPresentation = true self.modalPresentationStyle = .fullScreen } - - navigationBar.isTranslucent = false + + if #available(iOS 15.0, *) { + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + UINavigationBar.appearance().standardAppearance = appearance + UINavigationBar.appearance().scrollEdgeAppearance = appearance + UINavigationBar.appearance().compactAppearance = appearance + } else { + navigationBar.isTranslucent = false + } } //------------------------------------------------------------------------------------------------------------------------------------------- From ce5dcbd0b1359a6a4c10714c2058cf8c4620eba3 Mon Sep 17 00:00:00 2001 From: iamStephenFang Date: Sun, 17 Jul 2022 00:24:00 +0800 Subject: [PATCH 2/6] feat: add 'Require Passcode' support --- PasscodeKit/Sources/PasscodeInterval.swift | 28 ++++++ PasscodeKit/Sources/PasscodeKit.swift | 95 +++++++++++++++------ PasscodeKit/Sources/PasscodeKitVerify.swift | 2 +- PasscodeKit/app.xcodeproj/project.pbxproj | 8 ++ PasscodeKit/app/IntervalView.swift | 66 ++++++++++++++ PasscodeKit/app/PasscodeView.swift | 63 +++++++------- PasscodeKit/app/PasscodeView.xib | 38 ++++++++- 7 files changed, 238 insertions(+), 62 deletions(-) create mode 100644 PasscodeKit/Sources/PasscodeInterval.swift create mode 100644 PasscodeKit/app/IntervalView.swift mode change 100644 => 100755 PasscodeKit/app/PasscodeView.swift mode change 100644 => 100755 PasscodeKit/app/PasscodeView.xib diff --git a/PasscodeKit/Sources/PasscodeInterval.swift b/PasscodeKit/Sources/PasscodeInterval.swift new file mode 100644 index 0000000..8da1a6c --- /dev/null +++ b/PasscodeKit/Sources/PasscodeInterval.swift @@ -0,0 +1,28 @@ +// +// PasscodeInterval.swift +// app +// +// Created by StephenFang on 2022/7/16. +// Copyright © 2022 KZ. All rights reserved. +// + +import Foundation + +enum PasscodeInterval: Double, CaseIterable { + case immediately = 0.0 + case oneMinute = 1.0 + case fiveMinutes = 5.0 + case tenMinutes = 10.0 + case halfAnHour = 30.0 + case anHour = 60.0 + + var localizedDescription: String { + if self == .immediately { + return PasscodeKit.vefifyPasscodeImmediately + } else if self == .anHour { + return PasscodeKit.vefifyPasscodeAfterOneHour + } else { + return String(format: PasscodeKit.vefifyPasscodeAfterMinutes, rawValue) + } + } +} diff --git a/PasscodeKit/Sources/PasscodeKit.swift b/PasscodeKit/Sources/PasscodeKit.swift index b307cd0..6eab60c 100755 --- a/PasscodeKit/Sources/PasscodeKit.swift +++ b/PasscodeKit/Sources/PasscodeKit.swift @@ -32,29 +32,34 @@ public class PasscodeKit: NSObject { return instance }() - public static var passcodeLength = 4 - public static var allowedFailedAttempts = 3 - - public static var textColor = UIColor.darkText - public static var backgroundColor = UIColor.lightGray - - public static var failedTextColor = UIColor.white - public static var failedBackgroundColor = UIColor.systemRed - - public static var titleEnterPasscode = "Enter Passcode" - public static var titleCreatePasscode = "Create Passcode" - public static var titleChangePasscode = "Change Passcode" - public static var titleRemovePasscode = "Remove Passcode" - - public static var textEnterPasscode = "Enter your passcode" - public static var textVerifyPasscode = "Verify your passcode" - public static var textEnterOldPasscode = "Enter your old passcode" - public static var textEnterNewPasscode = "Enter your new passcode" - public static var textVerifyNewPasscode = "Verify your new passcode" - public static var textFailedPasscode = "%d Failed Passcode Attempts" - public static var textPasscodeMismatch = "Passcodes did not match. Try again." - public static var textTouchIDAccessReason = "Please use Touch ID to unlock the app" - + public static var passcodeLength = 4 + public static var allowedFailedAttempts = 3 + + public static var textColor = UIColor.darkText + public static var backgroundColor = UIColor.lightGray + + public static var failedTextColor = UIColor.white + public static var failedBackgroundColor = UIColor.systemRed + + public static var titleEnterPasscode = "Enter Passcode" + public static var titleCreatePasscode = "Create Passcode" + public static var titleChangePasscode = "Change Passcode" + public static var titleRemovePasscode = "Remove Passcode" + + public static var textEnterPasscode = "Enter your passcode" + public static var textVerifyPasscode = "Verify your passcode" + public static var textEnterOldPasscode = "Enter your old passcode" + public static var textEnterNewPasscode = "Enter your new passcode" + public static var textVerifyNewPasscode = "Verify your new passcode" + public static var textFailedPasscode = "%d Failed Passcode Attempts" + public static var textPasscodeMismatch = "Passcodes did not match. Try again." + public static var textBiometricAccessReason = "Please use Face ID (or Touch ID) to unlock the app" + public static var textBiometricAccessTip = "Allow to use Face ID (or Touch ID) to unlock the app." + + public static var vefifyPasscodeImmediately = "Immediately" + public static var vefifyPasscodeAfterMinutes = "After %.f minutes" + public static var vefifyPasscodeAfterOneHour = "After an hour" + public static var delegate: PasscodeKitDelegate? //------------------------------------------------------------------------------------------------------------------------------------------- @@ -99,15 +104,20 @@ extension PasscodeKit { let didFinishLaunching = UIApplication.didFinishLaunchingNotification let willEnterForeground = UIApplication.willEnterForegroundNotification + let willResignActive = UIApplication.willResignActiveNotification NotificationCenter.default.addObserver(self, selector: #selector(verifyPasscode), name: didFinishLaunching, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(verifyPasscode), name: willEnterForeground, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(verifyInterval), name: willResignActive, object: nil) } //------------------------------------------------------------------------------------------------------------------------------------------- @objc private func verifyPasscode() { if (PasscodeKit.enabled()) { + if (!PasscodeKit.verifiedTimeExpired()) { + return + } if let viewController = topViewController() { if (noPasscodePresented(viewController)) { presentPasscodeVerify(viewController) @@ -117,6 +127,13 @@ extension PasscodeKit { PasscodeKit.delegate?.passcodeCheckedButDisabled?() } } + + //------------------------------------------------------------------------------------------------------------------------------------------- + @objc private func verifyInterval() { + if (PasscodeKit.enabled()) { + PasscodeKit.verifiedTimeInterval(Date()) + } + } //------------------------------------------------------------------------------------------------------------------------------------------- private func presentPasscodeVerify(_ viewController: UIViewController) { @@ -229,6 +246,8 @@ extension PasscodeKit { UserDefaults.standard.removeObject(forKey: "PasscodeValue") UserDefaults.standard.removeObject(forKey: "PasscodeBiometric") + UserDefaults.standard.removeObject(forKey: "PasscodeInterval") + UserDefaults.standard.removeObject(forKey: "PasscodeVerifiedInterval") } //------------------------------------------------------------------------------------------------------------------------------------------- @@ -253,6 +272,34 @@ extension PasscodeKit { } return text } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func passcodeInterval() -> Double { + return UserDefaults.standard.double(forKey: "PasscodeInterval") + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func passcodeInterval(_ interval: Double) { + UserDefaults.standard.set(interval, forKey: "PasscodeInterval") + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func verifiedTimeInterval() -> TimeInterval { + return UserDefaults.standard.double(forKey: "PasscodeVerifiedInterval") + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func verifiedTimeInterval(_ date: Date) { + UserDefaults.standard.set(date.timeIntervalSince1970, forKey: "PasscodeVerifiedInterval") + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private class func verifiedTimeExpired() -> Bool { + let now = Date().timeIntervalSince1970 + let prev = verifiedTimeInterval() + let seconds: Double = abs(now - prev) + return seconds >= passcodeInterval() + } } // MARK: - PasscodeKitNavController @@ -268,7 +315,7 @@ class PasscodeKitNavController: UINavigationController { self.isModalInPresentation = true self.modalPresentationStyle = .fullScreen } - + if #available(iOS 15.0, *) { let appearance = UINavigationBarAppearance() appearance.configureWithOpaqueBackground() diff --git a/PasscodeKit/Sources/PasscodeKitVerify.swift b/PasscodeKit/Sources/PasscodeKitVerify.swift index f4b33d5..9fbe4d5 100755 --- a/PasscodeKit/Sources/PasscodeKitVerify.swift +++ b/PasscodeKit/Sources/PasscodeKitVerify.swift @@ -51,7 +51,7 @@ extension PasscodeKitVerify { let context = LAContext() if (PasscodeKit.biometric()) && (context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)) { - let reason = PasscodeKit.textTouchIDAccessReason + let reason = PasscodeKit.textBiometricAccessReason context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, error in DispatchQueue.main.async { self.actionBiometric(success) diff --git a/PasscodeKit/app.xcodeproj/project.pbxproj b/PasscodeKit/app.xcodeproj/project.pbxproj index 2979f35..5065ad9 100644 --- a/PasscodeKit/app.xcodeproj/project.pbxproj +++ b/PasscodeKit/app.xcodeproj/project.pbxproj @@ -20,6 +20,8 @@ 29D9780C261DB37700C80DBD /* PasscodeKitRemove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D97806261DB37700C80DBD /* PasscodeKitRemove.swift */; }; 29D97810261DB39C00C80DBD /* PasscodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D9780E261DB39C00C80DBD /* PasscodeView.swift */; }; 29D97811261DB39C00C80DBD /* PasscodeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29D9780F261DB39C00C80DBD /* PasscodeView.xib */; }; + 4D200CAE28831CE700C8DF4D /* IntervalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D200CAD28831CE700C8DF4D /* IntervalView.swift */; }; + 4D200CB028831D0200C8DF4D /* PasscodeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D200CAF28831D0200C8DF4D /* PasscodeInterval.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -38,6 +40,8 @@ 29D97806261DB37700C80DBD /* PasscodeKitRemove.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeKitRemove.swift; sourceTree = ""; }; 29D9780E261DB39C00C80DBD /* PasscodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeView.swift; sourceTree = ""; }; 29D9780F261DB39C00C80DBD /* PasscodeView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PasscodeView.xib; sourceTree = ""; }; + 4D200CAD28831CE700C8DF4D /* IntervalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntervalView.swift; sourceTree = ""; }; + 4D200CAF28831D0200C8DF4D /* PasscodeInterval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeInterval.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -60,6 +64,7 @@ 29D97806261DB37700C80DBD /* PasscodeKitRemove.swift */, 29D97801261DB37700C80DBD /* PasscodeKitText.swift */, 29D97803261DB37700C80DBD /* PasscodeKitVerify.swift */, + 4D200CAF28831D0200C8DF4D /* PasscodeInterval.swift */, ); path = Sources; sourceTree = ""; @@ -92,6 +97,7 @@ 29D9780F261DB39C00C80DBD /* PasscodeView.xib */, 295B8481248C1C5B003E8AE6 /* ViewController.swift */, 295B8480248C1C5B003E8AE6 /* ViewController.xib */, + 4D200CAD28831CE700C8DF4D /* IntervalView.swift */, ); path = app; sourceTree = ""; @@ -181,10 +187,12 @@ buildActionMask = 2147483647; files = ( 29D97809261DB37700C80DBD /* PasscodeKitVerify.swift in Sources */, + 4D200CAE28831CE700C8DF4D /* IntervalView.swift in Sources */, 295B8483248C1C5B003E8AE6 /* ViewController.swift in Sources */, 29D9780C261DB37700C80DBD /* PasscodeKitRemove.swift in Sources */, 29D97807261DB37700C80DBD /* PasscodeKitText.swift in Sources */, 29D9780B261DB37700C80DBD /* PasscodeKitChange.swift in Sources */, + 4D200CB028831D0200C8DF4D /* PasscodeInterval.swift in Sources */, 29D97808261DB37700C80DBD /* PasscodeKit.swift in Sources */, 29D97810261DB39C00C80DBD /* PasscodeView.swift in Sources */, 29D9780A261DB37700C80DBD /* PasscodeKitCreate.swift in Sources */, diff --git a/PasscodeKit/app/IntervalView.swift b/PasscodeKit/app/IntervalView.swift new file mode 100644 index 0000000..036a171 --- /dev/null +++ b/PasscodeKit/app/IntervalView.swift @@ -0,0 +1,66 @@ +// +// IntervalViewController.swift +// Example +// +// Created by StephenFang on 2022/7/8. +// Copyright © 2022 KZ. All rights reserved. +// + +import UIKit + +class IntervalViewController: UIViewController { + + fileprivate let tableView = UITableView(frame: .zero, style: .insetGrouped) + fileprivate var selectedRow = 0 + + override func viewDidLoad() { + super.viewDidLoad() + + title = "Require Password" + + navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil) + + view.addSubview(tableView) + tableView.frame = view.bounds + tableView.delegate = self + tableView.dataSource = self + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let selectedRow = PasscodeInterval.allCases.firstIndex(where: { $0.rawValue == PasscodeKit.passcodeInterval() + }) { + self.selectedRow = selectedRow + } + tableView.selectRow(at: IndexPath(item: selectedRow, section: 0), animated: false, scrollPosition: .top) + } +} + +extension IntervalViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return PasscodeInterval.allCases.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell") + if (cell == nil) { cell = UITableViewCell(style: .default, reuseIdentifier: "cell") } + + cell.selectionStyle = .none + cell.textLabel?.text = PasscodeInterval.allCases[indexPath.item].localizedDescription + cell.accessoryType = indexPath.row == selectedRow ? .checkmark : .none + + return cell + } +} + +extension IntervalViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + PasscodeKit.passcodeInterval(PasscodeInterval.allCases[indexPath.item].rawValue) + + selectedRow = indexPath.row + tableView.reloadData() + } +} diff --git a/PasscodeKit/app/PasscodeView.swift b/PasscodeKit/app/PasscodeView.swift old mode 100644 new mode 100755 index 7849e14..d27c064 --- a/PasscodeKit/app/PasscodeView.swift +++ b/PasscodeKit/app/PasscodeView.swift @@ -19,10 +19,10 @@ class PasscodeView: UIViewController { @IBOutlet private var cellTurnPasscode: UITableViewCell! @IBOutlet private var cellChangePasscode: UITableViewCell! @IBOutlet private var cellBiometric: UITableViewCell! + @IBOutlet private var cellInterval: UITableViewCell! + @IBOutlet private var switchBiometric: UISwitch! - @IBOutlet private var switchBiometric: UISwitch! - //------------------------------------------------------------------------------------------------------------------------------------------- override func viewDidLoad() { super.viewDidLoad() @@ -31,7 +31,7 @@ class PasscodeView: UIViewController { switchBiometric.addTarget(self, action: #selector(actionBiometric), for: .valueChanged) } - //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -40,7 +40,7 @@ class PasscodeView: UIViewController { } // MARK: - User actions - //------------------------------------------------------------------------------------------------------------------------------------------- + func actionTurnPasscode() { if (PasscodeKit.enabled()) { @@ -49,33 +49,43 @@ class PasscodeView: UIViewController { PasscodeKit.createPasscode(self) } } - - //------------------------------------------------------------------------------------------------------------------------------------------- + func actionChangePasscode() { if (PasscodeKit.enabled()) { PasscodeKit.changePasscode(self) } } + + + func actionChangeInterval() { - //------------------------------------------------------------------------------------------------------------------------------------------- - @objc func actionBiometric() { + if (PasscodeKit.enabled()) { + + let intervalViewController = IntervalViewController() + navigationController?.pushViewController(intervalViewController, animated: true) + } + } + @objc func actionBiometric() { PasscodeKit.biometric(switchBiometric.isOn) } // MARK: - Helper methods - //------------------------------------------------------------------------------------------------------------------------------------------- - func updateViewDetails() { + func updateViewDetails() { if (PasscodeKit.enabled()) { cellTurnPasscode.textLabel?.text = "Turn Passcode Off" cellChangePasscode.textLabel?.textColor = UIColor.systemBlue + cellInterval.textLabel?.textColor = UIColor.systemBlue } else { cellTurnPasscode.textLabel?.text = "Turn Passcode On" cellChangePasscode.textLabel?.textColor = UIColor.lightGray + cellInterval.textLabel?.textColor = UIColor.lightGray } - + + cellInterval.detailTextLabel?.text = PasscodeInterval.allCases.first(where: { $0.rawValue == PasscodeKit.passcodeInterval()})?.localizedDescription + switchBiometric.isOn = PasscodeKit.biometric() tableView.reloadData() @@ -83,53 +93,40 @@ class PasscodeView: UIViewController { } // MARK: - UITableViewDataSource -//----------------------------------------------------------------------------------------------------------------------------------------------- -extension PasscodeView: UITableViewDataSource { - //------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeView: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - - return PasscodeKit.enabled() ? 2 : 1 + return PasscodeKit.enabled() ? 3 : 1 } - //------------------------------------------------------------------------------------------------------------------------------------------- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if (section == 0) { return 2 } - if (section == 1) { return 1 } - + if (section == 1) { return 1 } + if (section == 2) { return 1 } return 0 } - //------------------------------------------------------------------------------------------------------------------------------------------- func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - - if (section == 1) { return "Allow to use Face ID (or Touch ID) to unlock the app." } - + if (section == 2) { return PasscodeKit.textBiometricAccessTip } return nil } - //------------------------------------------------------------------------------------------------------------------------------------------- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if (indexPath.section == 0) && (indexPath.row == 0) { return cellTurnPasscode } if (indexPath.section == 0) && (indexPath.row == 1) { return cellChangePasscode } - if (indexPath.section == 1) && (indexPath.row == 0) { return cellBiometric } - + if (indexPath.section == 1) && (indexPath.row == 0) { return cellInterval } + if (indexPath.section == 2) && (indexPath.row == 0) { return cellBiometric } return UITableViewCell() } } // MARK: - UITableViewDelegate -//----------------------------------------------------------------------------------------------------------------------------------------------- -extension PasscodeView: UITableViewDelegate { - //------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeView: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - if (indexPath.section == 0) && (indexPath.row == 0) { actionTurnPasscode() } if (indexPath.section == 0) && (indexPath.row == 1) { actionChangePasscode() } + if (indexPath.section == 1) && (indexPath.row == 0) { actionChangeInterval() } } } diff --git a/PasscodeKit/app/PasscodeView.xib b/PasscodeKit/app/PasscodeView.xib old mode 100644 new mode 100755 index fbf9424..522111d --- a/PasscodeKit/app/PasscodeView.xib +++ b/PasscodeKit/app/PasscodeView.xib @@ -1,10 +1,11 @@ - + - + + @@ -12,6 +13,7 @@ + @@ -57,7 +59,7 @@ - + @@ -92,7 +94,7 @@ - + @@ -106,10 +108,38 @@ + + + + + + + + + + + + + + + + From 53d2b1a28ada095bdd447b6401ff346c409c35bb Mon Sep 17 00:00:00 2001 From: iamStephenFang Date: Sun, 17 Jul 2022 00:45:18 +0800 Subject: [PATCH 3/6] feat: built-in 'Require Passcode' --- PasscodeKit/Sources/PasscodeInterval.swift | 57 +++++++++++++++++++ PasscodeKit/Sources/PasscodeKit.swift | 18 ++++-- .../PasscodeKitInterval.swift} | 37 +++++++++--- PasscodeKit/app.xcodeproj/project.pbxproj | 12 ++-- PasscodeKit/app/PasscodeView.swift | 4 +- 5 files changed, 106 insertions(+), 22 deletions(-) rename PasscodeKit/{app/IntervalView.swift => Sources/PasscodeKitInterval.swift} (67%) diff --git a/PasscodeKit/Sources/PasscodeInterval.swift b/PasscodeKit/Sources/PasscodeInterval.swift index 8da1a6c..f55d677 100644 --- a/PasscodeKit/Sources/PasscodeInterval.swift +++ b/PasscodeKit/Sources/PasscodeInterval.swift @@ -26,3 +26,60 @@ enum PasscodeInterval: Double, CaseIterable { } } } + +class PasscodeKitInterval: UIViewController { + + fileprivate let tableView = UITableView(frame: .zero, style: .insetGrouped) + fileprivate var selectedRow = 0 + + override func viewDidLoad() { + super.viewDidLoad() + + title = "Require Password" + + navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil) + + view.addSubview(tableView) + tableView.frame = view.bounds + tableView.delegate = self + tableView.dataSource = self + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let selectedRow = PasscodeInterval.allCases.firstIndex(where: { $0.rawValue == PasscodeKit.passcodeInterval() + }) { + self.selectedRow = selectedRow + } + tableView.selectRow(at: IndexPath(item: selectedRow, section: 0), animated: false, scrollPosition: .top) + } +} + +extension IntervalViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return PasscodeInterval.allCases.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell") + if (cell == nil) { cell = UITableViewCell(style: .default, reuseIdentifier: "cell") } + + cell.selectionStyle = .none + cell.textLabel?.text = PasscodeInterval.allCases[indexPath.item].localizedDescription + cell.accessoryType = indexPath.row == selectedRow ? .checkmark : .none + + return cell + } +} + +extension IntervalViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + PasscodeKit.passcodeInterval(PasscodeInterval.allCases[indexPath.item].rawValue) + + selectedRow = indexPath.row + tableView.reloadData() + } +} diff --git a/PasscodeKit/Sources/PasscodeKit.swift b/PasscodeKit/Sources/PasscodeKit.swift index 6eab60c..f4863e9 100755 --- a/PasscodeKit/Sources/PasscodeKit.swift +++ b/PasscodeKit/Sources/PasscodeKit.swift @@ -22,6 +22,8 @@ import CryptoKit @objc optional func passcodeCheckedButDisabled() @objc optional func passcodeEnteredSuccessfully() @objc optional func passcodeMaximumFailedAttempts() + + @objc optional func passcodeIntervalChanged() } //----------------------------------------------------------------------------------------------------------------------------------------------- @@ -152,10 +154,10 @@ extension PasscodeKit { 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 } + 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 @@ -209,6 +211,14 @@ extension PasscodeKit { let navController = PasscodeKitNavController(rootViewController: passcodeKitRemove) viewController.present(navController, animated: true) } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func changeInterval(_ viewController: UINavigationController) { + + let passcodeKitInterval = PasscodeKitInterval() + passcodeKitInterval.delegate = viewController as? PasscodeKitDelegate + viewController.pushViewController(passcodeKitInterval, animated: true) + } } // MARK: - Passcode methods diff --git a/PasscodeKit/app/IntervalView.swift b/PasscodeKit/Sources/PasscodeKitInterval.swift similarity index 67% rename from PasscodeKit/app/IntervalView.swift rename to PasscodeKit/Sources/PasscodeKitInterval.swift index 036a171..bff27be 100644 --- a/PasscodeKit/app/IntervalView.swift +++ b/PasscodeKit/Sources/PasscodeKitInterval.swift @@ -1,17 +1,39 @@ // -// IntervalViewController.swift -// Example +// PasscodeInterval.swift +// app // -// Created by StephenFang on 2022/7/8. +// Created by StephenFang on 2022/7/16. // Copyright © 2022 KZ. All rights reserved. // import UIKit -class IntervalViewController: UIViewController { +enum PasscodeInterval: Double, CaseIterable { + case immediately = 0.0 + case oneMinute = 1.0 + case fiveMinutes = 5.0 + case tenMinutes = 10.0 + case halfAnHour = 30.0 + case anHour = 60.0 + + var localizedDescription: String { + if self == .immediately { + return PasscodeKit.vefifyPasscodeImmediately + } else if self == .anHour { + return PasscodeKit.vefifyPasscodeAfterOneHour + } else { + return String(format: PasscodeKit.vefifyPasscodeAfterMinutes, rawValue) + } + } +} + +class PasscodeKitInterval: UIViewController { fileprivate let tableView = UITableView(frame: .zero, style: .insetGrouped) - fileprivate var selectedRow = 0 + + private var selectedRow = 0 + + var delegate: PasscodeKitDelegate? override func viewDidLoad() { super.viewDidLoad() @@ -37,7 +59,7 @@ class IntervalViewController: UIViewController { } } -extension IntervalViewController: UITableViewDataSource { +extension PasscodeKitInterval: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return PasscodeInterval.allCases.count } @@ -55,10 +77,11 @@ extension IntervalViewController: UITableViewDataSource { } } -extension IntervalViewController: UITableViewDelegate { +extension PasscodeKitInterval: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { PasscodeKit.passcodeInterval(PasscodeInterval.allCases[indexPath.item].rawValue) + delegate?.passcodeIntervalChanged?() selectedRow = indexPath.row tableView.reloadData() diff --git a/PasscodeKit/app.xcodeproj/project.pbxproj b/PasscodeKit/app.xcodeproj/project.pbxproj index 5065ad9..97bf242 100644 --- a/PasscodeKit/app.xcodeproj/project.pbxproj +++ b/PasscodeKit/app.xcodeproj/project.pbxproj @@ -20,8 +20,7 @@ 29D9780C261DB37700C80DBD /* PasscodeKitRemove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D97806261DB37700C80DBD /* PasscodeKitRemove.swift */; }; 29D97810261DB39C00C80DBD /* PasscodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D9780E261DB39C00C80DBD /* PasscodeView.swift */; }; 29D97811261DB39C00C80DBD /* PasscodeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29D9780F261DB39C00C80DBD /* PasscodeView.xib */; }; - 4D200CAE28831CE700C8DF4D /* IntervalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D200CAD28831CE700C8DF4D /* IntervalView.swift */; }; - 4D200CB028831D0200C8DF4D /* PasscodeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D200CAF28831D0200C8DF4D /* PasscodeInterval.swift */; }; + 4D200CB028831D0200C8DF4D /* PasscodeKitInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D200CAF28831D0200C8DF4D /* PasscodeKitInterval.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -40,8 +39,7 @@ 29D97806261DB37700C80DBD /* PasscodeKitRemove.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeKitRemove.swift; sourceTree = ""; }; 29D9780E261DB39C00C80DBD /* PasscodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeView.swift; sourceTree = ""; }; 29D9780F261DB39C00C80DBD /* PasscodeView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PasscodeView.xib; sourceTree = ""; }; - 4D200CAD28831CE700C8DF4D /* IntervalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntervalView.swift; sourceTree = ""; }; - 4D200CAF28831D0200C8DF4D /* PasscodeInterval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeInterval.swift; sourceTree = ""; }; + 4D200CAF28831D0200C8DF4D /* PasscodeKitInterval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeKitInterval.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -64,7 +62,7 @@ 29D97806261DB37700C80DBD /* PasscodeKitRemove.swift */, 29D97801261DB37700C80DBD /* PasscodeKitText.swift */, 29D97803261DB37700C80DBD /* PasscodeKitVerify.swift */, - 4D200CAF28831D0200C8DF4D /* PasscodeInterval.swift */, + 4D200CAF28831D0200C8DF4D /* PasscodeKitInterval.swift */, ); path = Sources; sourceTree = ""; @@ -97,7 +95,6 @@ 29D9780F261DB39C00C80DBD /* PasscodeView.xib */, 295B8481248C1C5B003E8AE6 /* ViewController.swift */, 295B8480248C1C5B003E8AE6 /* ViewController.xib */, - 4D200CAD28831CE700C8DF4D /* IntervalView.swift */, ); path = app; sourceTree = ""; @@ -187,12 +184,11 @@ buildActionMask = 2147483647; files = ( 29D97809261DB37700C80DBD /* PasscodeKitVerify.swift in Sources */, - 4D200CAE28831CE700C8DF4D /* IntervalView.swift in Sources */, 295B8483248C1C5B003E8AE6 /* ViewController.swift in Sources */, 29D9780C261DB37700C80DBD /* PasscodeKitRemove.swift in Sources */, 29D97807261DB37700C80DBD /* PasscodeKitText.swift in Sources */, 29D9780B261DB37700C80DBD /* PasscodeKitChange.swift in Sources */, - 4D200CB028831D0200C8DF4D /* PasscodeInterval.swift in Sources */, + 4D200CB028831D0200C8DF4D /* PasscodeKitInterval.swift in Sources */, 29D97808261DB37700C80DBD /* PasscodeKit.swift in Sources */, 29D97810261DB39C00C80DBD /* PasscodeView.swift in Sources */, 29D9780A261DB37700C80DBD /* PasscodeKitCreate.swift in Sources */, diff --git a/PasscodeKit/app/PasscodeView.swift b/PasscodeKit/app/PasscodeView.swift index d27c064..6fa13bd 100755 --- a/PasscodeKit/app/PasscodeView.swift +++ b/PasscodeKit/app/PasscodeView.swift @@ -61,9 +61,7 @@ class PasscodeView: UIViewController { func actionChangeInterval() { if (PasscodeKit.enabled()) { - - let intervalViewController = IntervalViewController() - navigationController?.pushViewController(intervalViewController, animated: true) + PasscodeKit.changeInterval(self.navigationController!) } } From f8e580607af611fb26fa2c7873e9464f8b4949ea Mon Sep 17 00:00:00 2001 From: iamStephenFang Date: Sun, 17 Jul 2022 00:52:06 +0800 Subject: [PATCH 4/6] feat: add localized interval string --- PasscodeKit/Sources/PasscodeKit.swift | 8 +++++++- PasscodeKit/app/PasscodeView.swift | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/PasscodeKit/Sources/PasscodeKit.swift b/PasscodeKit/Sources/PasscodeKit.swift index f4863e9..1b1a281 100755 --- a/PasscodeKit/Sources/PasscodeKit.swift +++ b/PasscodeKit/Sources/PasscodeKit.swift @@ -294,10 +294,16 @@ extension PasscodeKit { } //------------------------------------------------------------------------------------------------------------------------------------------- - public class func verifiedTimeInterval() -> TimeInterval { + public class func passcodeLocalizedInterval() -> String { + return PasscodeInterval.allCases.first(where: { $0.rawValue == PasscodeKit.passcodeInterval()})?.localizedDescription ?? PasscodeKit.vefifyPasscodeImmediately + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func verifiedTimeInterval() -> Double { return UserDefaults.standard.double(forKey: "PasscodeVerifiedInterval") } + //------------------------------------------------------------------------------------------------------------------------------------------- public class func verifiedTimeInterval(_ date: Date) { UserDefaults.standard.set(date.timeIntervalSince1970, forKey: "PasscodeVerifiedInterval") diff --git a/PasscodeKit/app/PasscodeView.swift b/PasscodeKit/app/PasscodeView.swift index 6fa13bd..8ac45c2 100755 --- a/PasscodeKit/app/PasscodeView.swift +++ b/PasscodeKit/app/PasscodeView.swift @@ -82,7 +82,7 @@ class PasscodeView: UIViewController { cellInterval.textLabel?.textColor = UIColor.lightGray } - cellInterval.detailTextLabel?.text = PasscodeInterval.allCases.first(where: { $0.rawValue == PasscodeKit.passcodeInterval()})?.localizedDescription + cellInterval.detailTextLabel?.text = PasscodeKit.passcodeLocalizedInterval() switchBiometric.isOn = PasscodeKit.biometric() From 47a9423a0f0d9c8522b2e1a990aec918e9faacc4 Mon Sep 17 00:00:00 2001 From: iamStephenFang Date: Sun, 17 Jul 2022 01:28:22 +0800 Subject: [PATCH 5/6] feat: modfied README & LICENSE --- LICENSE | 20 ++++++++++++++++++++ README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 51f3128..e26d552 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,25 @@ MIT License +Copyright (c) 2022 StephenFang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. + Copyright (c) 2021 Related Code Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index 137fa53..51a5bc0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,20 @@ +## DIFFERENCES + +1. Added support for 'Require Passcode' + + + +2. Added UINavigationBarAppearance support for iOS15 + + + + Before / After + +3. Refined localization strings + +- Changed 'textTouchIDAccessReason' to 'textBiometricAccessReason' +- Added 'textBiometricAccessTip' + ## OVERVIEW PasscodeKit is a lightweight and easy-to-use, in-app Passcode implementation for iOS. @@ -11,7 +28,7 @@ PasscodeKit is a lightweight and easy-to-use, in-app Passcode implementation for To use PasscodeKit with [CocoaPods](https://cocoapods.org), specify in your Podfile: ```ruby -pod 'PasscodeKit' +pod 'PasscodeKit', :git => 'https://github.com/iamStephenFang/PasscodeKit.git', :branch => 'master' ``` ### Manually @@ -24,7 +41,7 @@ If you prefer not to use any of the dependency managers, you can integrate `Pass ## QUICKSTART -To activate the PasscodeKit in your codebase, you need to start it right after the app is launched. The best practice to do it in the AppDelegate `didFinishLaunchingWithOptions` method. +To activate the PasscodeKit in your codebase, you need to start it right after the app is launched. The best practice to do it in the AppDelegate `didFinishLaunchingWithOptions` method. ```swift PasscodeKit.start() @@ -52,6 +69,12 @@ PasscodeKit.removePasscode(self) +The following `PasscodeKitDelegate` methods can be used to receive notifications when the password interval is changed. + +```swift +func passcodeIntervalChanged() +``` + ## CUSTOMIZATION The following settings are available for customizing the passcode-related user experience. @@ -83,7 +106,14 @@ PasscodeKit.textEnterNewPasscode = "Enter your new passcode" PasscodeKit.textVerifyNewPasscode = "Verify your new passcode" PasscodeKit.textFailedPasscode = "%d Failed Passcode Attempts" PasscodeKit.textPasscodeMismatch = "Passcodes did not match. Try again." -PasscodeKit.textTouchIDAccessReason = "Please use Touch ID to unlock the app" +PasscodeKit.textBiometricAccessReason = "Please use Touch ID to unlock the app" +PasscodeKit.textBiometricAccessTip = "Allow to use Face ID (or Touch ID) to unlock the app" +``` + +```swift +PasscodeKit.vefifyPasscodeImmediately = "Immediately" +PasscodeKit.vefifyPasscodeAfterMinutes = "After %.f minutes" +PasscodeKit.vefifyPasscodeAfterOneHour = "After an hour" ``` ## CONFIGURATION @@ -96,6 +126,26 @@ PasscodeKit supports both TouchID and FaceID. If you're using FaceID, be sure to MIT License +Copyright (c) 2022 StephenFang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. + Copyright (c) 2021 Related Code Permission is hereby granted, free of charge, to any person obtaining a copy From bc4902c89593425f9ab32689071e2692db9b24fa Mon Sep 17 00:00:00 2001 From: StephenFang Date: Sun, 17 Jul 2022 01:33:54 +0800 Subject: [PATCH 6/6] Update README.md fix README format --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 51a5bc0..119b0cb 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ ## DIFFERENCES 1. Added support for 'Require Passcode' - - + + + 2. Added UINavigationBarAppearance support for iOS15 - - - Before / After + (Before & After) + + + + + Also see [PR](https://github.com/relatedcode/PasscodeKit/pull/2) here. 3. Refined localization strings