Skip to content

Commit 6174084

Browse files
NO-JIRA: Add AlertViewController
1 parent 0d8d120 commit 6174084

File tree

11 files changed

+431
-6
lines changed

11 files changed

+431
-6
lines changed

Style/Sources/Style/Assets.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public enum Asset {
6363
public static let more = ImageAsset(name: "more")
6464
public static let podium = ImageAsset(name: "podium")
6565
public static let cityLogo = ImageAsset(name: "city_logo")
66+
public static let dogWhileEating = ImageAsset(name: "dog_while_eating")
6667
public static let animealLogo = ImageAsset(name: "animeal_logo")
6768
}
6869
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "dog_while_eating.pdf",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
},
12+
"properties" : {
13+
"preserves-vector-representation" : true
14+
}
15+
}

UIComponents/Sources/UIComponents/Components/Buttons/ButtonView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public extension ButtonView {
1818
public init(
1919
identifier: String,
2020
viewType: ButtonView.Type,
21-
icon: UIImage?,
21+
icon: UIImage? = nil,
2222
title: String = String.empty
2323
) {
2424
self.identifier = identifier

UIComponents/Sources/UIComponents/Components/Buttons/ButtonViewFactory.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,44 @@ public struct ButtonViewFactory: ButtonViewGenerating, StyleEngineContainable {
7878
return ButtonView(contentView: button)
7979
}
8080

81+
public func makeAccentButton() -> ButtonView {
82+
let button = UIButton()
83+
button.layer.cornerRadius = Constants.cornerRadius
84+
button.clipsToBounds = true
85+
button.backgroundColor = designEngine.colors.accent.uiColor
86+
87+
button.setTitleColor(
88+
designEngine.colors.alwaysLight.uiColor,
89+
for: UIControl.State.normal
90+
)
91+
button.setTitleColor(
92+
designEngine.colors.error.uiColor,
93+
for: UIControl.State.highlighted
94+
)
95+
96+
return ButtonView(contentView: button)
97+
}
98+
99+
public func makeAccentInvertedButton() -> ButtonView {
100+
let button = UIButton()
101+
button.layer.cornerRadius = Constants.cornerRadius
102+
button.clipsToBounds = true
103+
button.layer.borderColor = designEngine.colors.accent.cgColor
104+
button.layer.borderWidth = 1
105+
106+
button.backgroundColor = designEngine.colors.backgroundPrimary.uiColor
107+
button.setTitleColor(
108+
designEngine.colors.accent.uiColor,
109+
for: UIControl.State.normal
110+
)
111+
button.setTitleColor(
112+
designEngine.colors.accent.uiColor.withAlphaComponent(0.5),
113+
for: UIControl.State.highlighted
114+
)
115+
116+
return ButtonView(contentView: button)
117+
}
118+
81119
public func makeMyLocationButton() -> ButtonView {
82120
let button = UIButton()
83121
button.backgroundColor = designEngine.colors.backgroundPrimary.uiColor
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import UIKit
2+
3+
class TransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
4+
var toViewController: UIViewController!
5+
var fromViewController: UIViewController!
6+
let inDuration: TimeInterval
7+
let outDuration: TimeInterval
8+
let direction: AnimationDirection
9+
10+
init(inDuration: TimeInterval, outDuration: TimeInterval, direction: AnimationDirection) {
11+
self.inDuration = inDuration
12+
self.outDuration = outDuration
13+
self.direction = direction
14+
super.init()
15+
}
16+
17+
internal func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
18+
return direction == .in ? inDuration : outDuration
19+
}
20+
21+
internal func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
22+
switch direction {
23+
case .in:
24+
guard let toViewController = transitionContext.viewController(
25+
forKey: UITransitionContextViewControllerKey.to
26+
), let fromViewController = transitionContext.viewController(
27+
forKey: UITransitionContextViewControllerKey.from
28+
) else { return }
29+
30+
self.toViewController = toViewController
31+
self.fromViewController = fromViewController
32+
33+
let container = transitionContext.containerView
34+
container.addSubview(toViewController.view)
35+
case .out:
36+
guard let toViewController = transitionContext.viewController(
37+
forKey: UITransitionContextViewControllerKey.to
38+
), let fromViewController = transitionContext.viewController(
39+
forKey: UITransitionContextViewControllerKey.from
40+
) else { return }
41+
42+
self.toViewController = toViewController
43+
self.fromViewController = fromViewController
44+
}
45+
}
46+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import UIKit
2+
3+
final class ZoomTransition: TransitionAnimator {
4+
5+
init(direction: AnimationDirection) {
6+
super.init(inDuration: 0.22, outDuration: 0.2, direction: direction)
7+
}
8+
9+
override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
10+
super.animateTransition(using: transitionContext)
11+
12+
switch direction {
13+
case .in:
14+
toViewController.view.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
15+
UIView.animate(
16+
withDuration: 0.6,
17+
delay: 0.0,
18+
usingSpringWithDamping: 0.6,
19+
initialSpringVelocity: 0,
20+
options: [.curveEaseOut],
21+
animations: { [weak self] in
22+
guard let self = self else { return }
23+
self.toViewController.view.transform = CGAffineTransform(scaleX: 1, y: 1)
24+
},
25+
completion: { _ in
26+
transitionContext.completeTransition(true)
27+
}
28+
)
29+
case .out:
30+
UIView.animate(
31+
withDuration: outDuration,
32+
delay: 0.0,
33+
options: [.curveEaseIn],
34+
animations: { [weak self] in
35+
guard let self = self else { return }
36+
self.fromViewController.view.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
37+
self.fromViewController.view.alpha = 0.0
38+
},
39+
completion: { _ in
40+
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
41+
}
42+
)
43+
}
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import UIKit
2+
3+
final class BlurryPresentationController: UIPresentationController {
4+
private lazy var overlay: UIVisualEffectView = {
5+
let blurEffectView = UIVisualEffectView()
6+
blurEffectView.backgroundColor = .clear
7+
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
8+
return blurEffectView
9+
}()
10+
private var blurAnimator: UIViewPropertyAnimator?
11+
12+
override func presentationTransitionWillBegin() {
13+
guard let containerView = containerView else { return }
14+
15+
blurAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [overlay] in
16+
overlay.effect = UIBlurEffect(style: .dark)
17+
}
18+
blurAnimator?.fractionComplete = 0.15 // set the blur intensity.
19+
20+
overlay.frame = containerView.bounds
21+
containerView.insertSubview(overlay, at: 0)
22+
23+
presentedViewController.transitionCoordinator?.animate(
24+
alongsideTransition: { [weak self] _ in
25+
self?.overlay.alpha = 1.0
26+
},
27+
completion: nil
28+
)
29+
}
30+
31+
override func dismissalTransitionWillBegin() {
32+
blurAnimator?.stopAnimation(true)
33+
presentedViewController.transitionCoordinator?.animate(
34+
alongsideTransition: { [weak self] _ in
35+
self?.overlay.alpha = 0.0
36+
},
37+
completion: nil
38+
)
39+
}
40+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import UIKit
2+
3+
final class ZoomTransitionController: NSObject, UIViewControllerTransitioningDelegate {
4+
func presentationController(
5+
forPresented presented: UIViewController,
6+
presenting: UIViewController?,
7+
source: UIViewController
8+
) -> UIPresentationController? {
9+
let presentationController = BlurryPresentationController(
10+
presentedViewController: presented,
11+
presenting: source
12+
)
13+
return presentationController
14+
}
15+
16+
func animationController(
17+
forPresented presented: UIViewController,
18+
presenting: UIViewController,
19+
source: UIViewController
20+
) -> UIViewControllerAnimatedTransitioning? {
21+
return ZoomTransition(direction: .in)
22+
}
23+
24+
func animationController(
25+
forDismissed dismissed: UIViewController
26+
) -> UIViewControllerAnimatedTransitioning? {
27+
return ZoomTransition(direction: .out)
28+
}
29+
}
30+
31+
enum AnimationDirection {
32+
case `in` // swiftlint:disable:this identifier_name
33+
case out
34+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import UIKit
2+
3+
/// AlertViewController
4+
///
5+
/// Usage example:
6+
///
7+
/// let alert = AlertViewController(
8+
/// title: "Some Title"
9+
/// )
10+
/// alert.addAction(
11+
/// AlertAction(title: "Action title", style: AlertAction.Style.inverted, handler: { })
12+
/// )
13+
///
14+
/// present(alert, animated: true)
15+
///
16+
public final class AlertViewController: UIViewController {
17+
// MARK: - Private properties
18+
private let actionsContainer = UIStackView()
19+
private let transitionController = ZoomTransitionController()
20+
21+
// MARK: - Initialization
22+
public init(title: String, image: UIImage? = nil) {
23+
super.init(nibName: nil, bundle: nil)
24+
25+
self.transitioningDelegate = transitionController
26+
self.modalPresentationStyle = .custom
27+
28+
setup(title: title, image: image)
29+
}
30+
31+
required init?(coder: NSCoder) {
32+
fatalError("init(coder:) has not been implemented")
33+
}
34+
35+
// MARK: - Public API
36+
public func addAction(_ action: AlertAction) {
37+
let factory = ButtonViewFactory()
38+
var button: ButtonView
39+
switch action.style {
40+
case .accent:
41+
button = factory.makeAccentButton()
42+
case .inverted:
43+
button = factory.makeAccentInvertedButton()
44+
}
45+
46+
button.condifure(
47+
ButtonView.Model(
48+
identifier: UUID().uuidString,
49+
viewType: ButtonView.self,
50+
title: action.title ?? .empty
51+
)
52+
)
53+
button.onTap = { _ in
54+
action.handler?()
55+
}
56+
actionsContainer.addArrangedSubview(button)
57+
}
58+
59+
// MARK: - Private API
60+
private func setup(title: String, image: UIImage?) {
61+
let dialogView = UIView()
62+
dialogView.layer.cornerRadius = 30
63+
64+
view.addSubview(dialogView.prepareForAutoLayout())
65+
dialogView.leadingAnchor ~= view.leadingAnchor + 36
66+
dialogView.trailingAnchor ~= view.trailingAnchor - 36
67+
dialogView.centerXAnchor ~= view.centerXAnchor
68+
dialogView.centerYAnchor ~= view.centerYAnchor
69+
dialogView.backgroundColor = designEngine.colors.backgroundPrimary.uiColor
70+
71+
let contentView = UIStackView()
72+
contentView.axis = .horizontal
73+
74+
dialogView.addSubview(contentView.prepareForAutoLayout())
75+
contentView.topAnchor ~= dialogView.topAnchor + 26
76+
contentView.leadingAnchor ~= dialogView.leadingAnchor + 26
77+
contentView.trailingAnchor ~= dialogView.trailingAnchor - 26
78+
contentView.axis = .vertical
79+
80+
dialogView.addSubview(actionsContainer.prepareForAutoLayout())
81+
actionsContainer.topAnchor ~= contentView.bottomAnchor + 28
82+
actionsContainer.leadingAnchor ~= dialogView.leadingAnchor + 26
83+
actionsContainer.trailingAnchor ~= dialogView.trailingAnchor - 26
84+
actionsContainer.bottomAnchor ~= dialogView.bottomAnchor - 26
85+
actionsContainer.distribution = .fillEqually
86+
actionsContainer.spacing = 12
87+
88+
let titleLabel = UILabel()
89+
titleLabel.font = designEngine.fonts.primary.bold(18).uiFont
90+
titleLabel.textColor = designEngine.colors.textPrimary.uiColor
91+
titleLabel.numberOfLines = 0
92+
titleLabel.text = title
93+
94+
contentView.addArrangedSubview(titleLabel)
95+
96+
if let image = image {
97+
let spacerView = UIView().prepareForAutoLayout()
98+
spacerView.heightAnchor ~= 26
99+
contentView.addArrangedSubview(spacerView)
100+
101+
let imageView = UIImageView()
102+
imageView.image = image
103+
imageView.layer.cornerRadius = 16
104+
105+
contentView.addArrangedSubview(imageView)
106+
}
107+
}
108+
}
109+
110+
// MARK: - AlertAction model
111+
public struct AlertAction {
112+
public let title: String?
113+
public let style: Style
114+
public let handler: (() -> Void)?
115+
116+
public enum Style {
117+
case accent
118+
case inverted
119+
}
120+
121+
public init(
122+
title: String? = nil,
123+
style: Style,
124+
handler: (() -> Void)? = nil
125+
) {
126+
self.title = title
127+
self.style = style
128+
self.handler = handler
129+
}
130+
}

0 commit comments

Comments
 (0)