Merge pull request #33 from TouchInstinct/fix/pincode

Update for Swift 4. Refactor
This commit is contained in:
Igor Kislyuk 2018-03-26 16:50:01 +03:00 committed by GitHub
commit 7bc61d062c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 206 additions and 175 deletions

View File

@ -1,5 +1,8 @@
# Changelog
## 0.1.4
- **Update**: Refactor PassCode
## 0.1.3
- **Update**: Typical api response keys naming

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "LeadKitAdditions"
s.version = "0.1.3"
s.version = "0.1.4"
s.summary = "iOS framework with a bunch of tools for rapid development"
s.homepage = "https://github.com/TouchInstinct/LeadKitAdditions"
s.license = "Apache License, Version 2.0"

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
@ -24,26 +24,22 @@
public struct PassCodeConfiguration {
/// Pass code length
public var passCodeCharactersNumber: UInt = 4
public let passCodeLength: Int
/// Incorrect pass code attempts count
public var maxAttemptsLoginNumber: UInt = 5
public let maxAttemptsNumber: Int
/// Clear input progress when application goes to background
public var shouldResetWhenGoBackground: Bool = true
public let shouldResetWhenGoBackground: Bool
private init() {}
init?(passCodeCharactersNumber: UInt) {
guard passCodeCharactersNumber > 0 else {
assertionFailure("passCodeCharactersNumber must be greater then 0")
return nil
}
self.passCodeCharactersNumber = passCodeCharactersNumber
public init(passCodeLength: Int = 4, maxAttemptsNumber: Int = 5, shouldResetWhenGoBackground: Bool = true) {
self.passCodeLength = passCodeLength
self.maxAttemptsNumber = maxAttemptsNumber
self.shouldResetWhenGoBackground = shouldResetWhenGoBackground
}
/// Returns configuration with default values
public static var defaultConfiguration: PassCodeConfiguration {
return PassCodeConfiguration()
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
@ -21,8 +21,22 @@
//
/// Describes error, which may occur during pass code entering
/// - codesNotMatch: Different codes
/// - wrongCode: Value is remaining attemps
/// - tooManyAttempts: Attempts limit reached
public enum PassCodeError: Error {
case codesNotMatch
case wrongCode
case wrongCode(Int)
case tooManyAttempts
}
public extension PassCodeError {
var isTooManyAttempts: Bool {
switch self {
case .tooManyAttempts:
return true
default:
return false
}
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
@ -81,7 +81,7 @@ public class PassCodeHolderCreate: PassCodeHolderProtocol {
if let passCode = passCode {
return .valid(passCode)
} else {
return .inValid(.codesNotMatch)
return .invalid(.codesNotMatch)
}
}
@ -112,7 +112,7 @@ public class PassCodeHolderEnter: PassCodeHolderProtocol {
if let passCode = passCode {
return .valid(passCode)
} else {
return .inValid(nil)
return .invalid(nil)
}
}
@ -178,7 +178,7 @@ public class PassCodeHolderChange: PassCodeHolderProtocol {
if let passCode = passCode {
return .valid(passCode)
} else {
return .inValid(enterStep == .newEnter ? nil : .codesNotMatch)
return .invalid(enterStep == .newEnter ? nil : .codesNotMatch)
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
@ -24,33 +24,35 @@
public enum PassCodeValidationResult {
case valid(String)
case inValid(PassCodeError?)
case invalid(PassCodeError?)
public var isValid: Bool {
}
public extension PassCodeValidationResult {
var isValid: Bool {
switch self {
case .valid:
return true
default:
case .invalid:
return false
}
}
public var passCode: String? {
var passCode: String? {
switch self {
case let .valid(passCode):
return passCode
default:
case .invalid:
return nil
}
}
public var error: PassCodeError? {
var error: PassCodeError? {
switch self {
case let .inValid(error):
case let .invalid(error):
return error
default:
case .valid:
return nil
}
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
@ -47,7 +47,7 @@ public enum PassCodeControllerState {
}
/// Base view controller that operates with pass code
open class BasePassCodeViewController: UIViewController {
open class BasePassCodeViewController: UIViewController, ConfigurableController {
public var viewModel: BasePassCodeViewModel!
@ -59,7 +59,7 @@ open class BasePassCodeViewController: UIViewController {
public let disposeBag = DisposeBag()
fileprivate lazy var fakeTextField: UITextField = {
private lazy var fakeTextField: UITextField = {
let fakeTextField = UITextField()
fakeTextField.isSecureTextEntry = true
fakeTextField.keyboardType = .numberPad
@ -76,9 +76,20 @@ open class BasePassCodeViewController: UIViewController {
initialLoadView()
initialDotNumberConfiguration()
enebleKeyboard()
configureBackgroundNotifications()
showTouchIdIfNeeded(with: touchIdHint)
showBiometricsRequestIfNeeded(with: biometricsAuthorizationHint)
}
override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fakeTextField.becomeFirstResponder()
}
override open func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
fakeTextField.resignFirstResponder()
}
// MARK: - Private functions
@ -95,14 +106,10 @@ open class BasePassCodeViewController: UIViewController {
.disposed(by: disposeBag)
}
private func enebleKeyboard() {
fakeTextField.becomeFirstResponder()
}
private func initialDotNumberConfiguration() {
dotStackView.arrangedSubviews.forEach { dotStackView.removeArrangedSubview($0) }
for _ in 0..<viewModel.passCodeConfiguration.passCodeCharactersNumber {
for _ in 0 ..< viewModel.passCodeConfiguration.passCodeLength {
let dotImageView = UIImageView()
dotImageView.translatesAutoresizingMaskIntoConstraints = false
dotImageView.widthAnchor.constraint(equalTo: dotImageView.heightAnchor, multiplier: 1)
@ -113,7 +120,7 @@ open class BasePassCodeViewController: UIViewController {
resetDotsUI()
}
fileprivate func resetDotsUI() {
private func resetDotsUI() {
fakeTextField.text = nil
dotStackView.arrangedSubviews
.flatMap { $0 as? UIImageView }
@ -129,10 +136,10 @@ open class BasePassCodeViewController: UIViewController {
imageView.image = imageFor(type: state)
}
fileprivate func setStates(for passCodeText: String) {
private func setStates(for passCodeText: String) {
var statesArray: [PinImageType] = []
for characterIndex in 0..<viewModel.passCodeConfiguration.passCodeCharactersNumber {
for characterIndex in 0..<viewModel.passCodeConfiguration.passCodeLength {
let state: PinImageType = Int(characterIndex) <= passCodeText.characters.count - 1 ? .entered : .clear
statesArray.append(state)
}
@ -142,19 +149,15 @@ open class BasePassCodeViewController: UIViewController {
}
}
fileprivate func showTouchIdIfNeeded(with description: String) {
guard viewModel.isTouchIdEnabled && viewModel.controllerType == .enter else {
private func showBiometricsRequestIfNeeded(with description: String) {
guard viewModel.isBiometricsEnabled && viewModel.controllerType == .enter else {
return
}
viewModel.touchIdService?.authenticateByTouchId(description: description) { [weak self] isSuccess in
if isSuccess {
self?.viewModel.authSucceed(.touchId)
}
}
viewModel.authenticateUsingBiometrics(with: description)
}
fileprivate func resetUI() {
private func resetUI() {
resetDotsUI()
viewModel.reset()
}
@ -162,7 +165,7 @@ open class BasePassCodeViewController: UIViewController {
// MARK: - HAVE TO OVERRIDE
/// Returns prompt that appears on touch id system alert
open var touchIdHint: String {
open var biometricsAuthorizationHint: String {
assertionFailure("You should override this var: touchIdHint")
return ""
}
@ -174,9 +177,9 @@ open class BasePassCodeViewController: UIViewController {
}
/// Override to change error description
open func errorDescription(for error: PassCodeError) -> String {
open func errorDescription(for error: PassCodeError) -> NSAttributedString? {
assertionFailure("You should override this method: errorDescription(for error: PassCodeError)")
return ""
return nil
}
/// Override to change action title text
@ -189,7 +192,7 @@ open class BasePassCodeViewController: UIViewController {
/// Call to show error
open func showError(for error: PassCodeError) {
errorLabel?.text = errorDescription(for: error)
errorLabel?.attributedText = errorDescription(for: error)
errorLabel?.isHidden = false
}
@ -204,11 +207,7 @@ open class BasePassCodeViewController: UIViewController {
titleLabel?.text = actionTitle(for: passCodeControllerState)
}
}
// MARK: - ConfigurableController
// We need to implement all functions of ConfigurableController protocol to give ability to override them.
extension BasePassCodeViewController: ConfigurableController {
// MARK: - ConfigurableController
open func bindViews() {
fakeTextField.rx.text.asDriver()
@ -227,13 +226,13 @@ extension BasePassCodeViewController: ConfigurableController {
if validationResult.isValid {
self?.hideError()
} else if let pasCodeError = validationResult.error {
self?.showError(for: pasCodeError)
} else if let passCodeError = validationResult.error {
self?.showError(for: passCodeError)
}
})
.disposed(by: disposeBag)
viewModel.passCodeControllerState
viewModel.passCodeControllerStateDriver
.drive(onNext: { [weak self] controllerState in
self?.configureUI(for: controllerState)
})
@ -251,6 +250,7 @@ extension BasePassCodeViewController: ConfigurableController {
}
// MARK: - UITextFieldDelegate
extension BasePassCodeViewController: UITextFieldDelegate {
public func textField(_ textField: UITextField,
@ -262,3 +262,4 @@ extension BasePassCodeViewController: UITextFieldDelegate {
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
@ -37,36 +37,31 @@ open class BasePassCodeViewModel: BaseViewModel {
public let disposeBag = DisposeBag()
/// TouchId service, which can answer if user is authorized by finger
public let touchIdService: TouchIDService?
/// Service that can answer if user is authorized by biometrics
public let biometricsService = BiometricsService()
/// Contains configuration for pass code operations
public let passCodeConfiguration: PassCodeConfiguration
fileprivate let validationResultHolder = Variable<PassCodeValidationResult?>(nil)
private let validationResultHolder = Variable<PassCodeValidationResult?>(nil)
var validationResult: Driver<PassCodeValidationResult?> {
return validationResultHolder.asDriver()
}
fileprivate let passCodeControllerStateHolder = Variable<PassCodeControllerState>(.enter)
public var passCodeControllerState: Driver<PassCodeControllerState> {
return passCodeControllerStateHolder.asDriver()
private let passCodeControllerStateVariable = Variable<PassCodeControllerState>(.enter)
public var passCodeControllerStateDriver: Driver<PassCodeControllerState> {
return passCodeControllerStateVariable.asDriver()
}
private let passCodeText = Variable<String?>(nil)
fileprivate var attemptsNumber = 0
private var attemptsNumber = 0
fileprivate lazy var passCodeHolder: PassCodeHolderProtocol = {
return PassCodeHolderBuilder.build(with: self.controllerType)
}()
public init(controllerType: PassCodeControllerType,
passCodeConfiguration: PassCodeConfiguration,
touchIdService: TouchIDService? = nil) {
private lazy var passCodeHolder: PassCodeHolderProtocol = PassCodeHolderBuilder.build(with: self.controllerType)
public init(controllerType: PassCodeControllerType, passCodeConfiguration: PassCodeConfiguration) {
self.controllerType = controllerType
self.passCodeConfiguration = passCodeConfiguration
self.touchIdService = touchIdService
bindViewModel()
}
@ -76,35 +71,14 @@ open class BasePassCodeViewModel: BaseViewModel {
.distinctUntilChanged { $0 == $1 }
.drive(onNext: { [weak self] passCode in
if let passCode = passCode,
passCode.characters.count == Int(self?.passCodeConfiguration.passCodeCharactersNumber ?? 0) {
passCode.characters.count == Int(self?.passCodeConfiguration.passCodeLength ?? 0) {
self?.set(passCode: passCode)
}
})
.disposed(by: disposeBag)
validationResultHolder.asDriver()
.drive(onNext: { [weak self] validationResult in
guard let sSelf = self else {
return
}
if sSelf.passCodeHolder.type == .change {
if validationResult?.isValid ?? false,
sSelf.passCodeHolder.enterStep == .repeatEnter,
let passCode = validationResult?.passCode {
sSelf.authSucceed(.passCode(passCode))
} else {
sSelf.passCodeControllerStateHolder.value = sSelf.passCodeHolder.enterStep
}
} else {
if validationResult?.isValid ?? false, let passCode = validationResult?.passCode {
sSelf.authSucceed(.passCode(passCode))
} else {
sSelf.passCodeControllerStateHolder.value = sSelf.passCodeHolder.enterStep
}
}
})
validationResultHolder.asObservable()
.bind(to: validationResultBinder)
.disposed(by: disposeBag)
}
@ -121,11 +95,21 @@ open class BasePassCodeViewModel: BaseViewModel {
public func reset() {
passCodeText.value = nil
validationResultHolder.value = nil
passCodeControllerStateHolder.value = controllerType == .change ? .oldEnter : .enter
passCodeControllerStateVariable.value = controllerType == .change ? .oldEnter : .enter
attemptsNumber = 0
passCodeHolder.reset()
}
public func authenticateUsingBiometrics(with description: String) {
biometricsService.authenticateWithBiometrics(with: description) { [weak self] success, error in
if success {
self?.authSucceed(.touchId)
} else {
self?.authFailed(with: error)
}
}
}
// MARK: - HAVE TO OVERRIDE
/// Override to check if entered pass code is equal to stored
@ -134,40 +118,68 @@ open class BasePassCodeViewModel: BaseViewModel {
return false
}
/// Handler called after successful authentication
/// Method is called after successful authentication
open func authSucceed(_ type: PassCodeAuthType) {
assertionFailure("You should override this method: authSucceed(_ type: PassCodeAuthType)")
}
// MARK: - Functions that can you can override to use TouchId
/// Called when authentication failed
open func authFailed(with: Error?) {
assertionFailure("You should override this method: authFailed(with: Error)")
}
/// Override to be able use touchId during authentication
open var isTouchIdEnabled: Bool {
// MARK: - Biometrics
/// Posibility to use biometrics for authentication
open var isBiometricsEnabled: Bool {
return false
}
/// You should save user choice about authenticate by touchId
open func activateTouchIdForUser() {
assertionFailure("You should override this method: activateTouchIdForUser()")
/// Notify about activation for biometrics. Remember to save user choice
open func activateBiometricsForUser() {
assertionFailure("You should override this method: activateBiometricsForUser()")
}
}
private extension BasePassCodeViewModel {
var validationResultBinder: Binder<PassCodeValidationResult?> {
return Binder(self) { model, validationResult in
let isValid = validationResult?.isValid ?? false
let passCode = validationResult?.passCode
if model.passCodeHolder.type == .change {
if isValid, model.passCodeHolder.enterStep == .repeatEnter, let passCode = passCode {
model.authSucceed(.passCode(passCode))
} else {
model.passCodeControllerStateVariable.value = model.passCodeHolder.enterStep
}
} else {
if isValid, let passCode = passCode {
model.authSucceed(.passCode(passCode))
} else {
model.passCodeControllerStateVariable.value = model.passCodeHolder.enterStep
}
}
}
}
}
extension BasePassCodeViewModel {
fileprivate func set(passCode: String) {
private func set(passCode: String) {
passCodeHolder.add(passCode: passCode)
validateIfNeeded()
if shouldUpdateControllerState {
passCodeControllerStateHolder.value = passCodeHolder.enterStep
passCodeControllerStateVariable.value = passCodeHolder.enterStep
}
}
private var shouldUpdateControllerState: Bool {
return !passCodeHolder.shouldValidate ||
!(validationResultHolder.value?.isValid ?? true) ||
validationResultHolder.value?.error == .tooManyAttempts
validationResultHolder.value?.error?.isTooManyAttempts ?? false
}
private func validateIfNeeded() {
@ -177,16 +189,18 @@ extension BasePassCodeViewModel {
var validationResult = passCodeHolder.validate()
if passCodeHolder.type == .enter || (passCodeHolder.type == .change && passCodeHolder.enterStep == .newEnter) {
let passCodeValidationForPassCodeChange = passCodeHolder.type == .change && passCodeHolder.enterStep == .newEnter
if passCodeHolder.type == .enter || passCodeValidationForPassCodeChange {
attemptsNumber += 1
if let passCode = validationResult.passCode, !isEnteredPassCodeValid(passCode) {
validationResult = .inValid(.wrongCode)
validationResult = .invalid(.wrongCode(passCodeConfiguration.maxAttemptsNumber - attemptsNumber))
}
if (!validationResult.isValid && attemptsNumber == Int(passCodeConfiguration.maxAttemptsLoginNumber)) ||
attemptsNumber > Int(passCodeConfiguration.maxAttemptsLoginNumber) {
validationResult = .inValid(.tooManyAttempts)
if (!validationResult.isValid && attemptsNumber == Int(passCodeConfiguration.maxAttemptsNumber)) ||
attemptsNumber > Int(passCodeConfiguration.maxAttemptsNumber) {
validationResult = .invalid(.tooManyAttempts)
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
@ -24,11 +24,22 @@ import KeychainAccess
import CocoaLumberjack
import IDZSwiftCommonCrypto
private enum Keys {
static let passCodeHash = "passCodeHashKey"
static let isBiometricsEnabled = "isBiometricsEnabledKey"
static let isInitialLoad = "isInitialLoadKey"
}
private enum Values {
static let biometricsEnabled = "biometricsEnabled"
static let initialLoad = "initialLoad"
}
/// Represents base pass code service which encapsulates pass code storing
open class BasePassCodeService {
/// Override to set specific keychain service name
open class var keychainService: String {
open class var keychainServiceString: String {
return Bundle.main.bundleIdentifier ?? ""
}
@ -42,47 +53,33 @@ open class BasePassCodeService {
// MARK: - Private stuff
fileprivate lazy var keychain: Keychain = {
return Keychain(service: BasePassCodeService.keychainService)
.synchronizable(false)
}()
private lazy var keychain = Keychain(service: BasePassCodeService.keychainServiceString).synchronizable(false)
fileprivate var passCodeHash: String? {
private var passCodeHash: String? {
return keychain[Keys.passCodeHash]
}
fileprivate enum Keys {
static let passCodeHash = "passCodeHash"
static let isTouchIdEnabled = "isTouchIdEnabled"
static let isInitialLoad = "isInitialLoad"
}
fileprivate enum Values {
static let touchIdEnabled = "touchIdEnabled"
static let initialLoad = "initialLoad"
}
}
extension BasePassCodeService {
public extension BasePassCodeService {
/// Indicates is pass code already saved on this device
public var isPassCodeSaved: Bool {
var isPassCodeSaved: Bool {
return keychain[Keys.passCodeHash] != nil
}
/// Indicates is it possible to authenticate on this device via touch id
public var isTouchIdEnabled: Bool {
/// Possibility to authenticate via biometrics. TouchID or FaceID
var isBiometricsAuthorizationEnabled: Bool {
get {
return keychain[Keys.isTouchIdEnabled] == Values.touchIdEnabled
return keychain[Keys.isBiometricsEnabled] == Values.biometricsEnabled
}
set {
keychain[Keys.isTouchIdEnabled] = newValue ? Values.touchIdEnabled : nil
keychain[Keys.isBiometricsEnabled] = newValue ? Values.biometricsEnabled : nil
}
}
/// Saves new pass code
public func save(passCode: String?) {
func save(passCode: String?) {
if let passCode = passCode {
keychain[Keys.passCodeHash] = sha256(passCode)
} else {
@ -91,14 +88,14 @@ extension BasePassCodeService {
}
/// Check if pass code is correct
public func check(passCode: String) -> Bool {
func check(passCode: String) -> Bool {
return sha256(passCode) == passCodeHash
}
/// Reset pass code settings
public func reset() {
func reset() {
save(passCode: nil)
isTouchIdEnabled = false
isBiometricsAuthorizationEnabled = false
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
@ -22,34 +22,38 @@
import LocalAuthentication
public typealias TouchIDServiceAuthHandler = (Bool) -> Void
public typealias BiometricsAuthHandler = (Bool, Error?) -> Void
/// Represents service that provides access to authentication via touch id
public class TouchIDService {
/// Service that provide access to authentication via biometric
public final class BiometricsService {
private lazy var laContext: LAContext = {
return LAContext()
}()
public init() {}
private lazy var laContext = LAContext()
/// Indicates is it possible to authenticate on this device via touch id
public var canAuthenticateByTouchId: Bool {
public var canAuthenticateWithBiometrics: Bool {
return laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
/**
Initiates system touch id authentication process
Initiates system biometrics authentication process
- parameters:
- description: prompt on the system alert that describes what for user should attach finger to device
- authHandler: callback, with parameter, indicates if user authenticate successfuly
*/
public func authenticateByTouchId(description: String, authHandler: @escaping TouchIDServiceAuthHandler) {
laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
localizedReason: description) { success, _ in
public func authenticateWithBiometrics(with description: String,
fallback fallbackTitle: String? = nil,
cancel cancelTitle: String? = nil,
authHandler: @escaping BiometricsAuthHandler) {
if #available(iOS 10.0, *), let cancel = cancelTitle {
laContext.localizedCancelTitle = cancelTitle
}
if let fallback = fallbackTitle {
laContext.localizedFallbackTitle = fallbackTitle
}
authHandler(success)
laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: description) { success, error in
authHandler(success, error)
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Touch Instinct
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal