-
Notifications
You must be signed in to change notification settings - Fork 3k
/
Copy pathPasscodeViews.swift
206 lines (163 loc) · 6.68 KB
/
PasscodeViews.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/* 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: [NSAttributedStringKey.font: digitFont])
}
fileprivate var filledDigitString: NSAttributedString {
return NSAttributedString(string: "\(filledCharacter)", attributes: [NSAttributedStringKey.font: 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: .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.isEmpty
}
@objc func insertText(_ text: String) {
guard inputtedCode.count < passcodeSize else {
return
}
inputtedCode += text
setNeedsDisplay()
if inputtedCode.count == passcodeSize {
delegate?.passcodeInputView(self, didFinishEnteringCode: inputtedCode)
}
}
// Required for implementing UIKeyInput
@objc func deleteBackward() {
guard !inputtedCode.isEmpty else {
return
}
inputtedCode.remove(at: inputtedCode.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..<passcodeSize).forEach { index in
let offset = floor(rect.width / CGFloat(passcodeSize))
var circleRect = CGRect(size: circleSize)
circleRect.center = CGPoint(x: (offset * CGFloat(index + 1)) - offset / 2, y: rect.height / 2)
if index < inputtedCode.count {
context.fillEllipse(in: circleRect)
} else {
context.strokeEllipse(in: circleRect)
}
}
}
}
/// A pane that gets displayed inside the PasscodeViewController that displays a title and a passcode input field.
class PasscodePane: UIView {
let codeInputView: PasscodeInputView
var codeViewCenterConstraint: Constraint?
var containerCenterConstraint: Constraint?
fileprivate lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = UIConstants.DefaultChromeFont
label.isAccessibilityElement = true
return label
}()
fileprivate let centerContainer = UIView()
override func accessibilityElementCount() -> 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: .zero)
backgroundColor = SettingsUX.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(keyboardWillHide), name: .UIKeyboardWillHide, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), 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")
}
@objc 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()
})
}
@objc func keyboardWillHide(_ sender: Notification) {
UIView.animate(withDuration: 0.1, animations: {
self.containerCenterConstraint?.update(offset: 0)
self.layoutIfNeeded()
})
}
}