/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import Foundation import SnapKit private struct PasscodeUX { static let TitleVerticalSpacing: CGFloat = 32 static let DigitSize: CGFloat = 30 static let TopMargin: CGFloat = 80 static let PasscodeFieldSize: CGSize = CGSize(width: 160, height: 32) } @objc protocol PasscodeInputViewDelegate: class { func passcodeInputView(_ inputView: PasscodeInputView, didFinishEnteringCode code: String) } /// A custom, keyboard-able view that displays the blank/filled digits when entrering a passcode. class PasscodeInputView: UIView, UIKeyInput { weak var delegate: PasscodeInputViewDelegate? var digitFont: UIFont = UIConstants.PasscodeEntryFont let blankCharacter: Character = "-" let filledCharacter: Character = "•" fileprivate let passcodeSize: Int fileprivate var inputtedCode: String = "" fileprivate var blankDigitString: NSAttributedString { return NSAttributedString(string: "\(blankCharacter)", attributes: [NSFontAttributeName: digitFont]) } fileprivate var filledDigitString: NSAttributedString { return NSAttributedString(string: "\(filledCharacter)", attributes: [NSFontAttributeName: digitFont]) } @objc var keyboardType: UIKeyboardType = .numberPad init(frame: CGRect, passcodeSize: Int) { self.passcodeSize = passcodeSize super.init(frame: frame) isOpaque = false } convenience init(passcodeSize: Int) { self.init(frame: CGRect.zero, passcodeSize: passcodeSize) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override var canBecomeFirstResponder: Bool { return true } func resetCode() { inputtedCode = "" setNeedsDisplay() } @objc var hasText: Bool { return inputtedCode.characters.count > 0 } @objc func insertText(_ text: String) { guard inputtedCode.characters.count < passcodeSize else { return } inputtedCode += text setNeedsDisplay() if inputtedCode.characters.count == passcodeSize { delegate?.passcodeInputView(self, didFinishEnteringCode: inputtedCode) } } // Required for implementing UIKeyInput @objc func deleteBackward() { guard inputtedCode.characters.count > 0 else { return } inputtedCode.remove(at: inputtedCode.characters.index(before: inputtedCode.endIndex)) setNeedsDisplay() } override func draw(_ rect: CGRect) { let circleSize = CGSize(width: 14, height: 14) guard let context = UIGraphicsGetCurrentContext() else { return } context.setLineWidth(1) context.setStrokeColor(UIConstants.PasscodeDotColor.cgColor) context.setFillColor(UIConstants.PasscodeDotColor.cgColor) (0.. Int { return 1 } override func accessibilityElement(at index: Int) -> Any? { switch index { case 0: return titleLabel default: return nil } } init(title: String? = nil, passcodeSize: Int = 4) { codeInputView = PasscodeInputView(passcodeSize: passcodeSize) super.init(frame: CGRect.zero) backgroundColor = UIConstants.TableViewHeaderBackgroundColor titleLabel.text = title centerContainer.addSubview(titleLabel) centerContainer.addSubview(codeInputView) addSubview(centerContainer) centerContainer.snp.makeConstraints { make in make.centerX.equalTo(self) containerCenterConstraint = make.centerY.equalTo(self).constraint } titleLabel.snp.makeConstraints { make in make.centerX.equalTo(centerContainer) make.top.equalTo(centerContainer) make.bottom.equalTo(codeInputView.snp.top).offset(-PasscodeUX.TitleVerticalSpacing) } codeInputView.snp.makeConstraints { make in codeViewCenterConstraint = make.centerX.equalTo(centerContainer).constraint make.bottom.equalTo(centerContainer) make.size.equalTo(PasscodeUX.PasscodeFieldSize) } layoutIfNeeded() NotificationCenter.default.addObserver(self, selector: #selector(PasscodePane.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(PasscodePane.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil) } func shakePasscode() { UIView.animate(withDuration: 0.1, animations: { self.codeViewCenterConstraint?.update(offset: -10) self.layoutIfNeeded() }, completion: { complete in UIView.animate(withDuration: 0.1, animations: { self.codeViewCenterConstraint?.update(offset: 0) self.layoutIfNeeded() }) }) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func keyboardWillShow(_ sender: Notification) { guard let keyboardFrame = (sender.userInfo?[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue else { return } UIView.animate(withDuration: 0.1, animations: { self.containerCenterConstraint?.update(offset: -keyboardFrame.height/2) self.layoutIfNeeded() }) } func keyboardWillHide(_ sender: Notification) { UIView.animate(withDuration: 0.1, animations: { self.containerCenterConstraint?.update(offset: 0) self.layoutIfNeeded() }) } }