Skip to content

Commit ef1b560

Browse files
committed
Finish implementing basic views
1 parent 0c26f4b commit ef1b560

File tree

5 files changed

+226
-21
lines changed

5 files changed

+226
-21
lines changed

CodeInputView.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
CE901F20205AF28000C688DD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE901F1F205AF28000C688DD /* Assets.xcassets */; };
1414
CE901F23205AF28000C688DD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE901F21205AF28000C688DD /* LaunchScreen.storyboard */; };
1515
CE901F2B205AF32600C688DD /* CodeInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE901F2A205AF32600C688DD /* CodeInputView.swift */; };
16+
CEBF4F4B2066881500D31600 /* CodeInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEBF4F4A2066881500D31600 /* CodeInputField.swift */; };
17+
CEBF4F4D2066A2AD00D31600 /* DotInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEBF4F4C2066A2AD00D31600 /* DotInputField.swift */; };
1618
/* End PBXBuildFile section */
1719

1820
/* Begin PBXFileReference section */
@@ -24,6 +26,8 @@
2426
CE901F22205AF28000C688DD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
2527
CE901F24205AF28000C688DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2628
CE901F2A205AF32600C688DD /* CodeInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeInputView.swift; sourceTree = "<group>"; };
29+
CEBF4F4A2066881500D31600 /* CodeInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeInputField.swift; sourceTree = "<group>"; };
30+
CEBF4F4C2066A2AD00D31600 /* DotInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DotInputField.swift; sourceTree = "<group>"; };
2731
/* End PBXFileReference section */
2832

2933
/* Begin PBXFrameworksBuildPhase section */
@@ -63,6 +67,8 @@
6367
CE901F21205AF28000C688DD /* LaunchScreen.storyboard */,
6468
CE901F24205AF28000C688DD /* Info.plist */,
6569
CE901F2A205AF32600C688DD /* CodeInputView.swift */,
70+
CEBF4F4A2066881500D31600 /* CodeInputField.swift */,
71+
CEBF4F4C2066A2AD00D31600 /* DotInputField.swift */,
6672
);
6773
path = CodeInputView;
6874
sourceTree = "<group>";
@@ -140,7 +146,9 @@
140146
buildActionMask = 2147483647;
141147
files = (
142148
CE901F2B205AF32600C688DD /* CodeInputView.swift in Sources */,
149+
CEBF4F4D2066A2AD00D31600 /* DotInputField.swift in Sources */,
143150
CE901F1B205AF28000C688DD /* ViewController.swift in Sources */,
151+
CEBF4F4B2066881500D31600 /* CodeInputField.swift in Sources */,
144152
CE901F19205AF28000C688DD /* AppDelegate.swift in Sources */,
145153
);
146154
runOnlyForDeploymentPostprocessing = 0;

CodeInputView/CodeInputField.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// CodeInputField.swift
3+
// CodeInputView
4+
//
5+
// Created by Isa Aliev on 24.03.18.
6+
// Copyright © 2018 IA. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import UIKit
11+
12+
class CodeInputField: UILabel, InputableField {
13+
private var value: String?
14+
15+
override init(frame: CGRect) {
16+
super.init(frame: frame)
17+
18+
}
19+
20+
required init?(coder aDecoder: NSCoder) {
21+
super.init(coder: aDecoder)
22+
}
23+
24+
override func layoutSubviews() {
25+
super.layoutSubviews()
26+
27+
}
28+
29+
func deleteValue() {
30+
value = ""
31+
text = nil
32+
}
33+
34+
func setValue(_ newValue: String) {
35+
value = newValue
36+
text = newValue
37+
}
38+
39+
func getValue() -> String {
40+
return value ?? ""
41+
}
42+
43+
func isEmpty() -> Bool {
44+
return value == nil
45+
}
46+
}

CodeInputView/CodeInputView.swift

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,46 @@ protocol InputableField {
1616
func isEmpty() -> Bool
1717
}
1818

19-
class CodeInputField: UIView, InputableField {
20-
private var value: String?
19+
class CodeInputView<T: InputableField&UIView>: UIView, UIKeyInput {
20+
private var fields = [T]()
21+
private var currentIndex = 0
22+
var numberOfFields = 4 {
23+
didSet {
24+
configure()
25+
layoutSubviews()
26+
}
27+
}
2128

22-
func deleteValue() {
23-
value = ""
29+
var code = ""
30+
var horizontalMargins: CGFloat = 8.0 {
31+
didSet {
32+
layoutSubviews()
33+
}
2434
}
2535

26-
func setValue(_ newValue: String) {
27-
value = newValue
36+
var interFieldSpacing: CGFloat = 8.0 {
37+
didSet {
38+
layoutSubviews()
39+
}
2840
}
2941

30-
func getValue() -> String {
31-
return value ?? ""
42+
var keyboardType: UIKeyboardType = .numberPad
43+
44+
private var sizeOfField: CGFloat {
45+
let frameWidth = frame.width
46+
let minBySides = min(frameWidth - horizontalMargins * 2, frame.height * 0.8)
47+
let fieldsCount = fields.count
48+
49+
return min(minBySides, (frameWidth - interFieldSpacing * CGFloat(fieldsCount - 1) - horizontalMargins * 2.0)/CGFloat(fieldsCount))
3250
}
3351

34-
func isEmpty() -> Bool {
35-
return value == nil
52+
override var canBecomeFirstResponder: Bool {
53+
return true
54+
}
55+
56+
var hasText: Bool {
57+
return fields.filter({ !$0.isEmpty() }).count > 0
3658
}
37-
}
38-
39-
class CodeInputView<T: InputableField&UIView>: UIView {
40-
var fields = [T]()
41-
var numberOfFields = 4
4259

4360
override func awakeFromNib() {
4461
super.awakeFromNib()
@@ -67,25 +84,58 @@ class CodeInputView<T: InputableField&UIView>: UIView {
6784
private func configure() {
6885
guard fields.count != numberOfFields else { return }
6986

70-
for _ in 0..<numberOfFields {
71-
fields.append(T(frame: .zero))
87+
if numberOfFields > fields.count {
88+
(0..<(numberOfFields - fields.count)).forEach({ _ in
89+
insertNewField()
90+
})
91+
} else {
92+
fields[numberOfFields..<fields.count].forEach({ $0.removeFromSuperview() })
93+
fields.removeSubrange(numberOfFields..<fields.count)
7294
}
7395
}
7496

75-
private func layoutFields() {
76-
97+
private func insertNewField() {
98+
let field = T(frame: fields.last?.frame ?? .zero)
99+
addSubview(field)
100+
fields.append(field)
77101
}
78102

79-
var hasText: Bool {
80-
return false
103+
private func layoutFields() {
104+
let numberOfFields = fields.count
105+
let widthOfFields = CGFloat(numberOfFields) / 2.0 * sizeOfField
106+
let widthOfInterFieldSpacing = (CGFloat(numberOfFields) / 2.0 - 0.5) * interFieldSpacing
107+
var x = bounds.midX - (widthOfFields + widthOfInterFieldSpacing)
108+
let y = (bounds.height - sizeOfField) * 0.5
109+
110+
fields.forEach({
111+
let frame = CGRect(x: x, y: y, width: sizeOfField, height: sizeOfField)
112+
$0.frame = frame
113+
x += sizeOfField + interFieldSpacing
114+
})
81115
}
82116

83117
func insertText(_ text: String) {
118+
guard currentIndex < fields.count else {
119+
return
120+
}
84121

122+
fields[currentIndex].setValue(text)
123+
currentIndex += 1
124+
updateCode()
85125
}
86126

87127
func deleteBackward() {
128+
guard currentIndex > 0 else {
129+
return
130+
}
88131

132+
currentIndex -= 1
133+
fields[currentIndex].deleteValue()
134+
updateCode()
135+
}
136+
137+
private func updateCode() {
138+
code = fields.reduce("", { $0 + $1.getValue() })
89139
}
90140
}
91141

CodeInputView/DotInputField.swift

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// DotInputField.swift
3+
// CodeInputView
4+
//
5+
// Created by Isa Aliev on 24.03.18.
6+
// Copyright © 2018 IA. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import UIKit
11+
12+
class DotInputField: UIView, InputableField {
13+
struct Configuration {
14+
var dotSize: CGFloat = 15.0
15+
var fillColor = UIColor.black
16+
var strokeColor = UIColor.black
17+
var emptyColor = UIColor.clear
18+
}
19+
20+
private var value: String?
21+
static var configuration: Configuration = Configuration()
22+
23+
var dotRect: CGRect {
24+
let size = DotInputField.configuration.dotSize
25+
return CGRect(x: (bounds.width - size) * 0.5,
26+
y: (bounds.height - size) * 0.5,
27+
width: size, height: size)
28+
}
29+
30+
var circlePath: UIBezierPath {
31+
return UIBezierPath(roundedRect: dotRect,
32+
byRoundingCorners: UIRectCorner.allCorners,
33+
cornerRadii: CGSize(width: dotRect.height / 2.0, height: dotRect.height / 2.0))
34+
}
35+
36+
lazy var strokeLayer: CAShapeLayer = {
37+
let layer = CAShapeLayer()
38+
39+
layer.fillColor = DotInputField.configuration.emptyColor.cgColor
40+
layer.strokeColor = DotInputField.configuration.strokeColor.cgColor
41+
42+
return layer
43+
}()
44+
45+
override init(frame: CGRect) {
46+
super.init(frame: frame)
47+
48+
layer.anchorPoint = CGPoint(x: 0.0, y: 0.0)
49+
layer.addSublayer(strokeLayer)
50+
}
51+
52+
required init?(coder aDecoder: NSCoder) {
53+
super.init(coder: aDecoder)
54+
}
55+
56+
override func layoutSubviews() {
57+
super.layoutSubviews()
58+
59+
strokeLayer.path = circlePath.cgPath
60+
strokeLayer.frame = layer.bounds
61+
}
62+
63+
func deleteValue() {
64+
strokeLayer.fillColor = DotInputField.configuration.emptyColor.cgColor
65+
value = ""
66+
}
67+
68+
func setValue(_ newValue: String) {
69+
strokeLayer.fillColor = DotInputField.configuration.fillColor.cgColor
70+
value = newValue
71+
}
72+
73+
func getValue() -> String {
74+
return value ?? ""
75+
}
76+
77+
func isEmpty() -> Bool {
78+
return value == nil
79+
}
80+
}

CodeInputView/ViewController.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,29 @@
99
import UIKit
1010

1111
class ViewController: UIViewController {
12+
1213
override func viewDidLoad() {
1314
super.viewDidLoad()
15+
16+
CodeInputField.configuration.emptyStateBlock = { label in
17+
label.backgroundColor = .gray
18+
label.layer.cornerRadius = label.bounds.height / 2.0
19+
label.layer.masksToBounds = true
20+
label.textAlignment = .center
21+
}
22+
23+
CodeInputField.configuration.filledStateBlock = { label in
24+
label.backgroundColor = .white
25+
}
26+
27+
28+
DotInputField.configuration.fillColor = .cyan
29+
let c = CodeInputView<CodeInputField>()
30+
c.frame = CGRect(x: 16.0, y: 200.0, width: 250.0, height: 100.0)
31+
c.backgroundColor = .green
32+
view.addSubview(c)
33+
c.becomeFirstResponder()
34+
1435
// Do any additional setup after loading the view, typically from a nib.
1536
}
1637
}

0 commit comments

Comments
 (0)