Skip to content

Commit 806484e

Browse files
committed
Implement message view controller
1 parent df1440d commit 806484e

File tree

8 files changed

+345
-48
lines changed

8 files changed

+345
-48
lines changed

BarcodeScanner.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
D55281BA2016770800FF3CDD /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281B92016770800FF3CDD /* SettingsViewController.swift */; };
1818
D55281BC2016782C00FF3CDD /* ScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281BB2016782C00FF3CDD /* ScannerViewController.swift */; };
1919
D55281BF20167DB400FF3CDD /* UIViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281BE20167DB400FF3CDD /* UIViewController+Extensions.swift */; };
20+
D55281C720168E7000FF3CDD /* NSLayoutConstraint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281C620168E7000FF3CDD /* NSLayoutConstraint+Extensions.swift */; };
21+
D55281C920168E8A00FF3CDD /* BlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281C820168E8A00FF3CDD /* BlurView.swift */; };
22+
D55281CB20168E9600FF3CDD /* FooterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281CA20168E9600FF3CDD /* FooterViewController.swift */; };
2023
D5C4E08E1CA0BFB9008D9269 /* InfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C4E08C1CA0BFB9008D9269 /* InfoView.swift */; };
2124
D5C4E08F1CA0BFB9008D9269 /* TorchMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C4E08D1CA0BFB9008D9269 /* TorchMode.swift */; };
2225
D5F1C1C91C9C5113001E17A6 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F1C1C51C9C5113001E17A6 /* Config.swift */; };
@@ -36,6 +39,9 @@
3639
D55281B92016770800FF3CDD /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
3740
D55281BB2016782C00FF3CDD /* ScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannerViewController.swift; sourceTree = "<group>"; };
3841
D55281BE20167DB400FF3CDD /* UIViewController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extensions.swift"; sourceTree = "<group>"; };
42+
D55281C620168E7000FF3CDD /* NSLayoutConstraint+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraint+Extensions.swift"; sourceTree = "<group>"; };
43+
D55281C820168E8A00FF3CDD /* BlurView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurView.swift; sourceTree = "<group>"; };
44+
D55281CA20168E9600FF3CDD /* FooterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FooterViewController.swift; sourceTree = "<group>"; };
3945
D5B2E89F1C3A780C00C0327D /* BarcodeScanner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BarcodeScanner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4046
D5C4E08C1CA0BFB9008D9269 /* InfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = InfoView.swift; sourceTree = "<group>"; tabWidth = 2; };
4147
D5C4E08D1CA0BFB9008D9269 /* TorchMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = TorchMode.swift; sourceTree = "<group>"; tabWidth = 2; };
@@ -70,6 +76,7 @@
7076
D55281B42016758000FF3CDD /* Controllers */ = {
7177
isa = PBXGroup;
7278
children = (
79+
D55281CA20168E9600FF3CDD /* FooterViewController.swift */,
7380
D5F1C1C61C9C5113001E17A6 /* BarcodeScannerController.swift */,
7481
D55281B52016758F00FF3CDD /* HeaderViewController.swift */,
7582
D55281B7201675D500FF3CDD /* MessageViewController.swift */,
@@ -82,6 +89,7 @@
8289
D55281BD20167D9F00FF3CDD /* Extensions */ = {
8390
isa = PBXGroup;
8491
children = (
92+
D55281C620168E7000FF3CDD /* NSLayoutConstraint+Extensions.swift */,
8593
D504555E1FD8714700E46826 /* UIView+Extensions.swift */,
8694
D55281BE20167DB400FF3CDD /* UIViewController+Extensions.swift */,
8795
);
@@ -117,6 +125,7 @@
117125
D5C629691C3A809D007F7B7C /* Sources */ = {
118126
isa = PBXGroup;
119127
children = (
128+
D55281C820168E8A00FF3CDD /* BlurView.swift */,
120129
D55281BD20167D9F00FF3CDD /* Extensions */,
121130
D55281B42016758000FF3CDD /* Controllers */,
122131
D5FC8AD61D252A12004BED88 /* State.swift */,
@@ -211,6 +220,9 @@
211220
buildActionMask = 2147483647;
212221
files = (
213222
D5C4E08F1CA0BFB9008D9269 /* TorchMode.swift in Sources */,
223+
D55281C720168E7000FF3CDD /* NSLayoutConstraint+Extensions.swift in Sources */,
224+
D55281C920168E8A00FF3CDD /* BlurView.swift in Sources */,
225+
D55281CB20168E9600FF3CDD /* FooterViewController.swift in Sources */,
214226
D5C4E08E1CA0BFB9008D9269 /* InfoView.swift in Sources */,
215227
D55281BF20167DB400FF3CDD /* UIViewController+Extensions.swift in Sources */,
216228
D55281BA2016770800FF3CDD /* SettingsViewController.swift in Sources */,

BlurView.swift

Lines changed: 0 additions & 27 deletions
This file was deleted.

Sources/BlurView.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import UIKit
2+
3+
public final class BlurView: UIView {
4+
let effectView: UIVisualEffectView = .init(effect: UIBlurEffect(style: .extraLight))
5+
6+
// MARK: - Init
7+
8+
public override init(frame: CGRect) {
9+
super.init(frame: .zero)
10+
insertSubview(effectView, at: 0)
11+
}
12+
13+
public required init?(coder aDecoder: NSCoder) {
14+
fatalError()
15+
}
16+
17+
// MARK: - Subviews
18+
19+
public override func addSubview(_ view: UIView) {
20+
effectView.contentView.addSubview(view)
21+
}
22+
23+
public override func layoutSubviews() {
24+
super.layoutSubviews()
25+
effectView.frame = bounds
26+
}
27+
}
Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,63 @@
1-
//
2-
// FooterViewController.swift
3-
// BarcodeScanner
4-
//
5-
// Created by Vadym Markov on 22/01/2018.
6-
//
7-
8-
import Foundation
1+
import UIKit
2+
3+
final class FooterViewController: UIViewController {
4+
// Blur effect view.
5+
public private(set) lazy var blurView: BlurView = .init()
6+
/// Text label.
7+
public private(set) lazy var textLabel: UILabel = .init()
8+
/// Info image view.
9+
public private(set) lazy var imageView: UIImageView = .init()
10+
11+
// MARK: - View lifecycle
12+
13+
override func viewDidLoad() {
14+
super.viewDidLoad()
15+
view.addSubview(blurView)
16+
blurView.addSubviews(textLabel, imageView)
17+
setupSubviews()
18+
}
19+
20+
// MARK: - Subviews
21+
22+
private func setupSubviews() {
23+
textLabel.textColor = .black
24+
textLabel.font = UIFont.boldSystemFont(ofSize: 14)
25+
textLabel.numberOfLines = 3
26+
27+
imageView.image = imageNamed("info").withRenderingMode(.alwaysTemplate)
28+
imageView.tintColor = .black
29+
}
30+
31+
// MARK: - Layout
32+
33+
private func setupConstraints() {
34+
let padding: CGFloat = 10
35+
36+
NSLayoutConstraint.activate(
37+
imageView.centerYAnchor.constraint(equalTo: blurView.centerYAnchor),
38+
imageView.widthAnchor.constraint(equalToConstant: 30),
39+
imageView.heightAnchor.constraint(equalToConstant: 27),
40+
41+
textLabel.topAnchor.constraint(equalTo: imageView.topAnchor),
42+
textLabel.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10)
43+
)
44+
45+
if #available(iOS 11.0, *) {
46+
NSLayoutConstraint.activate(
47+
imageView.leadingAnchor.constraint(
48+
equalTo: view.safeAreaLayoutGuide.leadingAnchor,
49+
constant: padding
50+
),
51+
textLabel.trailingAnchor.constraint(
52+
equalTo: view.safeAreaLayoutGuide.trailingAnchor,
53+
constant: -padding
54+
)
55+
)
56+
} else {
57+
NSLayoutConstraint.activate(
58+
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
59+
textLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding)
60+
)
61+
}
62+
}
63+
}
Lines changed: 208 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,212 @@
11
import UIKit
22

3-
final class MessageViewController: UIViewController {
3+
public enum MessageStyle {
4+
case initial
5+
case loading
6+
case error
7+
}
8+
9+
public final class MessageViewController: UIViewController {
10+
// Blur effect view.
11+
private lazy var blurView: BlurView = .init()
12+
/// Text label.
13+
public private(set) lazy var textLabel: UILabel = .init()
14+
/// Info image view.
15+
public private(set) lazy var imageView: UIImageView = .init()
16+
/// Border view.
17+
public private(set) lazy var borderView: UIView = .init()
18+
19+
private lazy var collapsedConstraints: [NSLayoutConstraint] = self.makeCollapsedConstraints()
20+
private lazy var expandedConstraints: [NSLayoutConstraint] = self.makeExpandedConstraints()
21+
22+
var state: State = .scanning {
23+
didSet {
24+
handleStateUpdate()
25+
}
26+
}
27+
28+
// MARK: - View lifecycle
29+
30+
public override func viewDidLoad() {
31+
super.viewDidLoad()
32+
view.addSubview(blurView)
33+
blurView.addSubviews(textLabel, imageView, borderView)
34+
setupSubviews()
35+
handleStateUpdate()
36+
}
37+
38+
public override func viewDidLayoutSubviews() {
39+
super.viewDidLayoutSubviews()
40+
blurView.frame = view.bounds
41+
}
42+
43+
public override func viewWillLayoutSubviews() {
44+
super.viewWillLayoutSubviews()
45+
}
46+
47+
// MARK: - Subviews
48+
49+
private func setupSubviews() {
50+
textLabel.translatesAutoresizingMaskIntoConstraints = false
51+
textLabel.textColor = .black
52+
textLabel.numberOfLines = 3
53+
54+
imageView.translatesAutoresizingMaskIntoConstraints = false
55+
imageView.image = imageNamed("info").withRenderingMode(.alwaysTemplate)
56+
imageView.tintColor = .black
57+
58+
borderView.translatesAutoresizingMaskIntoConstraints = false
59+
borderView.backgroundColor = .clear
60+
borderView.layer.borderWidth = 2
61+
borderView.layer.cornerRadius = 10
62+
borderView.layer.borderColor = UIColor.black.cgColor
63+
}
64+
65+
private func handleStateUpdate() {
66+
borderView.isHidden = true
67+
borderView.layer.removeAllAnimations()
68+
69+
switch state {
70+
case .scanning, .unauthorized:
71+
textLabel.text = state == .scanning ? Info.text : Info.settingsText
72+
textLabel.textColor = .black
73+
textLabel.font = UIFont.boldSystemFont(ofSize: 14)
74+
textLabel.numberOfLines = 3
75+
textLabel.textAlignment = .left
76+
imageView.tintColor = .black
77+
case .processing:
78+
textLabel.text = Info.loadingText
79+
textLabel.textColor = .black
80+
textLabel.font = UIFont.boldSystemFont(ofSize: 16)
81+
textLabel.numberOfLines = 10
82+
textLabel.textAlignment = .center
83+
borderView.isHidden = false
84+
imageView.tintColor = .black
85+
case .notFound:
86+
textLabel.text = Info.notFoundText
87+
textLabel.textColor = .black
88+
textLabel.font = UIFont.boldSystemFont(ofSize: 16)
89+
textLabel.numberOfLines = 10
90+
textLabel.textAlignment = .center
91+
imageView.tintColor = .red
92+
}
93+
94+
if state == .scanning || state == .unauthorized {
95+
expandedConstraints.forEach({ $0.isActive = false })
96+
collapsedConstraints.forEach({ $0.isActive = true })
97+
} else {
98+
collapsedConstraints.forEach({ $0.isActive = false })
99+
expandedConstraints.forEach({ $0.isActive = true })
100+
}
101+
}
102+
103+
// MARK: - Animations
104+
105+
/**
106+
Animates blur and border view.
107+
*/
108+
func animateLoading() {
109+
animate(blurStyle: .light)
110+
animate(borderViewAngle: CGFloat(Double.pi/2))
111+
}
112+
113+
/**
114+
Animates blur to make pulsating effect.
115+
116+
- Parameter style: The current blur style.
117+
*/
118+
private func animate(blurStyle: UIBlurEffectStyle) {
119+
guard state == .processing else { return }
120+
121+
UIView.animate(
122+
withDuration: 2.0,
123+
delay: 0.5,
124+
options: [.beginFromCurrentState],
125+
animations: ({ [weak self] in
126+
self?.blurView.effectView.effect = UIBlurEffect(style: blurStyle)
127+
}),
128+
completion: ({ [weak self] _ in
129+
self?.animate(blurStyle: blurStyle == .light ? .extraLight : .light)
130+
}))
131+
}
132+
133+
/**
134+
Animates border view with a given angle.
135+
136+
- Parameter angle: Rotation angle.
137+
*/
138+
private func animate(borderViewAngle: CGFloat) {
139+
guard state == .processing else {
140+
borderView.transform = .identity
141+
return
142+
}
143+
144+
UIView.animate(
145+
withDuration: 0.8,
146+
delay: 0.5,
147+
usingSpringWithDamping: 0.6,
148+
initialSpringVelocity: 1.0,
149+
options: [.beginFromCurrentState],
150+
animations: ({ [weak self] in
151+
self?.borderView.transform = CGAffineTransform(rotationAngle: borderViewAngle)
152+
}),
153+
completion: ({ [weak self] _ in
154+
self?.animate(borderViewAngle: borderViewAngle + CGFloat(Double.pi / 2))
155+
}))
156+
}
157+
}
158+
159+
extension MessageViewController {
160+
private func makeExpandedConstraints() -> [NSLayoutConstraint] {
161+
let padding: CGFloat = 10
162+
let borderSize: CGFloat = 51
163+
164+
return [
165+
imageView.centerYAnchor.constraint(equalTo: blurView.centerYAnchor, constant: -60),
166+
imageView.centerXAnchor.constraint(equalTo: blurView.centerXAnchor),
167+
imageView.widthAnchor.constraint(equalToConstant: 30),
168+
imageView.heightAnchor.constraint(equalToConstant: 27),
169+
170+
textLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 14),
171+
textLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
172+
textLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding),
173+
174+
borderView.topAnchor.constraint(equalTo: imageView.topAnchor, constant: -12),
175+
borderView.centerXAnchor.constraint(equalTo: blurView.centerXAnchor),
176+
borderView.widthAnchor.constraint(equalToConstant: borderSize),
177+
borderView.heightAnchor.constraint(equalToConstant: borderSize)
178+
]
179+
}
180+
181+
private func makeCollapsedConstraints() -> [NSLayoutConstraint] {
182+
let padding: CGFloat = 10
183+
var constraints = [
184+
imageView.centerYAnchor.constraint(equalTo: blurView.centerYAnchor),
185+
imageView.widthAnchor.constraint(equalToConstant: 30),
186+
imageView.heightAnchor.constraint(equalToConstant: 27),
187+
188+
textLabel.topAnchor.constraint(equalTo: imageView.topAnchor),
189+
textLabel.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10)
190+
]
191+
192+
if #available(iOS 11.0, *) {
193+
constraints += [
194+
imageView.leadingAnchor.constraint(
195+
equalTo: view.safeAreaLayoutGuide.leadingAnchor,
196+
constant: padding
197+
),
198+
textLabel.trailingAnchor.constraint(
199+
equalTo: view.safeAreaLayoutGuide.trailingAnchor,
200+
constant: -padding
201+
)
202+
]
203+
} else {
204+
constraints += [
205+
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
206+
textLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding)
207+
]
208+
}
4209

210+
return constraints
211+
}
5212
}

0 commit comments

Comments
 (0)