Add a cancel button to passcode lock of app extension
This commit is contained in:
parent
980b581295
commit
3f899a58d9
4 changed files with 97 additions and 6 deletions
|
|
@ -38,6 +38,7 @@
|
||||||
A2A61C201EEFABAD00CFE063 /* UtilsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A61C1F1EEFABAD00CFE063 /* UtilsExtension.swift */; };
|
A2A61C201EEFABAD00CFE063 /* UtilsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A61C1F1EEFABAD00CFE063 /* UtilsExtension.swift */; };
|
||||||
A2A61C2C1EEFDF3300CFE063 /* ExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */; };
|
A2A61C2C1EEFDF3300CFE063 /* ExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */; };
|
||||||
A2A7813F1E97DBD9001311F5 /* QRScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */; };
|
A2A7813F1E97DBD9001311F5 /* QRScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */; };
|
||||||
|
A2BEC1BB207D2EFE00F3051C /* UIViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2BEC1BA207D2EFE00F3051C /* UIViewExtension.swift */; };
|
||||||
A2C532BB201E5A9600DB9F53 /* PasscodeLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2C532BA201E5A9600DB9F53 /* PasscodeLock.swift */; };
|
A2C532BB201E5A9600DB9F53 /* PasscodeLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2C532BA201E5A9600DB9F53 /* PasscodeLock.swift */; };
|
||||||
A2C532BE201E5AA100DB9F53 /* PasscodeLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2C532BC201E5AA000DB9F53 /* PasscodeLockViewController.swift */; };
|
A2C532BE201E5AA100DB9F53 /* PasscodeLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2C532BC201E5AA000DB9F53 /* PasscodeLockViewController.swift */; };
|
||||||
A2C532BF201E5AA100DB9F53 /* PasscodeLockPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2C532BD201E5AA100DB9F53 /* PasscodeLockPresenter.swift */; };
|
A2C532BF201E5AA100DB9F53 /* PasscodeLockPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2C532BD201E5AA100DB9F53 /* PasscodeLockPresenter.swift */; };
|
||||||
|
|
@ -200,6 +201,7 @@
|
||||||
A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionViewController.swift; sourceTree = "<group>"; };
|
A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionViewController.swift; sourceTree = "<group>"; };
|
||||||
A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScannerController.swift; sourceTree = "<group>"; };
|
A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScannerController.swift; sourceTree = "<group>"; };
|
||||||
A2BC54C71EEE5669001FAFBD /* Objective-CBridgingHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Objective-CBridgingHeader.h"; sourceTree = "<group>"; };
|
A2BC54C71EEE5669001FAFBD /* Objective-CBridgingHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Objective-CBridgingHeader.h"; sourceTree = "<group>"; };
|
||||||
|
A2BEC1BA207D2EFE00F3051C /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIViewExtension.swift; path = Helpers/UIViewExtension.swift; sourceTree = "<group>"; };
|
||||||
A2C532BA201E5A9600DB9F53 /* PasscodeLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasscodeLock.swift; path = Models/PasscodeLock.swift; sourceTree = "<group>"; };
|
A2C532BA201E5A9600DB9F53 /* PasscodeLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasscodeLock.swift; path = Models/PasscodeLock.swift; sourceTree = "<group>"; };
|
||||||
A2C532BC201E5AA000DB9F53 /* PasscodeLockViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasscodeLockViewController.swift; path = Controllers/PasscodeLockViewController.swift; sourceTree = "<group>"; };
|
A2C532BC201E5AA000DB9F53 /* PasscodeLockViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasscodeLockViewController.swift; path = Controllers/PasscodeLockViewController.swift; sourceTree = "<group>"; };
|
||||||
A2C532BD201E5AA100DB9F53 /* PasscodeLockPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasscodeLockPresenter.swift; path = Controllers/PasscodeLockPresenter.swift; sourceTree = "<group>"; };
|
A2C532BD201E5AA100DB9F53 /* PasscodeLockPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasscodeLockPresenter.swift; path = Controllers/PasscodeLockPresenter.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -410,6 +412,7 @@
|
||||||
A2F4E21B1EED80160011986E /* NotificationNames.swift */,
|
A2F4E21B1EED80160011986E /* NotificationNames.swift */,
|
||||||
A2F4E21C1EED80160011986E /* UITextFieldExtension.swift */,
|
A2F4E21C1EED80160011986E /* UITextFieldExtension.swift */,
|
||||||
A2F4E21D1EED80160011986E /* Utils.swift */,
|
A2F4E21D1EED80160011986E /* Utils.swift */,
|
||||||
|
A2BEC1BA207D2EFE00F3051C /* UIViewExtension.swift */,
|
||||||
);
|
);
|
||||||
name = Helpers;
|
name = Helpers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1032,6 +1035,7 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
A2BEC1BB207D2EFE00F3051C /* UIViewExtension.swift in Sources */,
|
||||||
A2C532BB201E5A9600DB9F53 /* PasscodeLock.swift in Sources */,
|
A2C532BB201E5A9600DB9F53 /* PasscodeLock.swift in Sources */,
|
||||||
A2F4E2151EED800F0011986E /* Password.swift in Sources */,
|
A2F4E2151EED800F0011986E /* Password.swift in Sources */,
|
||||||
A26075AD1EEC7125005DB03E /* pass.xcdatamodeld in Sources */,
|
A26075AD1EEC7125005DB03E /* pass.xcdatamodeld in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,35 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import passKit
|
import passKit
|
||||||
|
|
||||||
|
// cancel means cancel the extension
|
||||||
|
class PasscodeLockViewControllerForExtension: PasscodeLockViewController {
|
||||||
|
var originalExtensionContest: NSExtensionContext?
|
||||||
|
public convenience init(extensionContext: NSExtensionContext?) {
|
||||||
|
self.init()
|
||||||
|
originalExtensionContest = extensionContext
|
||||||
|
}
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
cancelButton?.removeTarget(nil, action: nil, for: .allEvents)
|
||||||
|
cancelButton?.addTarget(self, action: #selector(cancelExtension), for: .touchUpInside)
|
||||||
|
}
|
||||||
|
@objc func cancelExtension() {
|
||||||
|
originalExtensionContest?.completeRequest(returningItems: [], completionHandler: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PasscodeExtensionDisplay {
|
class PasscodeExtensionDisplay {
|
||||||
private var isPasscodePresented = false
|
private var isPasscodePresented = false
|
||||||
private let passcodeLockVC: PasscodeLockViewController
|
private let passcodeLockVC: PasscodeLockViewControllerForExtension
|
||||||
|
private let extensionContext: NSExtensionContext?
|
||||||
|
|
||||||
init(extensionContext: NSExtensionContext?) {
|
init(extensionContext: NSExtensionContext?) {
|
||||||
passcodeLockVC = PasscodeLockViewController()
|
self.extensionContext = extensionContext
|
||||||
|
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
|
||||||
passcodeLockVC.dismissCompletionCallback = { [weak self] in
|
passcodeLockVC.dismissCompletionCallback = { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
}
|
}
|
||||||
|
passcodeLockVC.setCancellable(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// present the passcode lock view if passcode is set and the view controller is not presented
|
// present the passcode lock view if passcode is set and the view controller is not presented
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
weak var passcodeWrongAttemptsLabel: UILabel?
|
weak var passcodeWrongAttemptsLabel: UILabel?
|
||||||
weak var passcodeTextField: UITextField?
|
weak var passcodeTextField: UITextField?
|
||||||
weak var biometryAuthButton: UIButton?
|
weak var biometryAuthButton: UIButton?
|
||||||
|
open weak var cancelButton: UIButton?
|
||||||
|
|
||||||
var passcodeFailedAttempts = 0
|
var passcodeFailedAttempts = 0
|
||||||
|
var isCancellable: Bool = false
|
||||||
|
|
||||||
open override func loadView() {
|
open override func loadView() {
|
||||||
super.loadView()
|
super.loadView()
|
||||||
|
|
@ -80,6 +82,16 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cancelButton = UIButton(type: .custom)
|
||||||
|
cancelButton.setTitle("Cancel", for: .normal)
|
||||||
|
cancelButton.setTitleColor(Globals.blue, for: .normal)
|
||||||
|
cancelButton.addTarget(self, action: #selector(passcodeLockDidCancel), for: .touchUpInside)
|
||||||
|
cancelButton.isHidden = !self.isCancellable
|
||||||
|
cancelButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
cancelButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left
|
||||||
|
self.view.addSubview(cancelButton)
|
||||||
|
self.cancelButton = cancelButton
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
passcodeTextField.widthAnchor.constraint(equalToConstant: 300),
|
passcodeTextField.widthAnchor.constraint(equalToConstant: 300),
|
||||||
passcodeTextField.heightAnchor.constraint(equalToConstant: 40),
|
passcodeTextField.heightAnchor.constraint(equalToConstant: 40),
|
||||||
|
|
@ -99,7 +111,12 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
biometryAuthButton.widthAnchor.constraint(equalToConstant: 150),
|
biometryAuthButton.widthAnchor.constraint(equalToConstant: 150),
|
||||||
biometryAuthButton.heightAnchor.constraint(equalToConstant: 40),
|
biometryAuthButton.heightAnchor.constraint(equalToConstant: 40),
|
||||||
biometryAuthButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
biometryAuthButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
||||||
biometryAuthButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -40),
|
biometryAuthButton.bottomAnchor.constraint(equalTo: self.view.safeBottomAnchor, constant: -40),
|
||||||
|
// cancel (top-left of the screen)
|
||||||
|
cancelButton.widthAnchor.constraint(equalToConstant: 150),
|
||||||
|
cancelButton.heightAnchor.constraint(equalToConstant: 40),
|
||||||
|
cancelButton.topAnchor.constraint(equalTo: self.view.safeTopAnchor),
|
||||||
|
cancelButton.leftAnchor.constraint(equalTo: self.view.safeLeftAnchor, constant: 20)
|
||||||
])
|
])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -144,11 +161,11 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
dismissPasscodeLock(completionHandler: successCallback)
|
dismissPasscodeLock(completionHandler: successCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
open func passcodeLockDidCancel() {
|
@objc func passcodeLockDidCancel() {
|
||||||
dismissPasscodeLock(completionHandler: cancelCallback)
|
dismissPasscodeLock(completionHandler: cancelCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public func bioButtonPressedAction(_ uiButton: UIButton) {
|
@objc func bioButtonPressedAction(_ uiButton: UIButton) {
|
||||||
let myContext = LAContext()
|
let myContext = LAContext()
|
||||||
let myLocalizedReasonString = "Authentication is needed to access Pass."
|
let myLocalizedReasonString = "Authentication is needed to access Pass."
|
||||||
var authError: NSError?
|
var authError: NSError?
|
||||||
|
|
@ -182,9 +199,14 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public func passcodeTextFieldDidChange(_ textField: UITextField) {
|
@objc func passcodeTextFieldDidChange(_ textField: UITextField) {
|
||||||
if PasscodeLock.shared.check(passcode: textField.text ?? "") {
|
if PasscodeLock.shared.check(passcode: textField.text ?? "") {
|
||||||
self.passcodeLockDidSucceed()
|
self.passcodeLockDidSucceed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func setCancellable(_ isCancellable: Bool) {
|
||||||
|
self.isCancellable = isCancellable
|
||||||
|
cancelButton?.isHidden = !isCancellable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
45
passKit/Helpers/UIViewExtension.swift
Normal file
45
passKit/Helpers/UIViewExtension.swift
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// UIViewExtension.swift
|
||||||
|
// passKit
|
||||||
|
//
|
||||||
|
// Created by Yishi Lin on 2018/4/11.
|
||||||
|
// Copyright © 2018 Yishi Lin. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension UIView {
|
||||||
|
|
||||||
|
// Save anchors: https://stackoverflow.com/questions/46317061/use-safe-area-layout-programmatically
|
||||||
|
var safeTopAnchor: NSLayoutYAxisAnchor {
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
return self.safeAreaLayoutGuide.topAnchor
|
||||||
|
} else {
|
||||||
|
return self.topAnchor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var safeLeftAnchor: NSLayoutXAxisAnchor {
|
||||||
|
if #available(iOS 11.0, *){
|
||||||
|
return self.safeAreaLayoutGuide.leftAnchor
|
||||||
|
} else {
|
||||||
|
return self.leftAnchor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var safeRightAnchor: NSLayoutXAxisAnchor {
|
||||||
|
if #available(iOS 11.0, *){
|
||||||
|
return self.safeAreaLayoutGuide.rightAnchor
|
||||||
|
} else {
|
||||||
|
return self.rightAnchor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var safeBottomAnchor: NSLayoutYAxisAnchor {
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
return self.safeAreaLayoutGuide.bottomAnchor
|
||||||
|
} else {
|
||||||
|
return self.bottomAnchor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue