How to make progress HUD in Swift

Issue #566

Code

Create a container that has blur effect

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
public class HUDContainer: UIVisualEffectView, AnimationAware {
private let innerContentView: UIView & AnimationAware
public let label = UILabel()
public var text: String? {
didSet {
label.text = text
label.sizeToFit()
label.isHidden = text == nil
}
}

public init(contentView: UIView & AnimationAware) {
self.innerContentView = contentView
super.init(effect: UIBlurEffect(style: .light))
self.contentView.addSubview(innerContentView)
self.contentView.addSubview(label)

innerContentView.pinEdgesToSuperview()
configure()
}

public required init?(coder aDecoder: NSCoder) {
fatalError()
}

public func configure() {
layer.cornerRadius = 8
layer.masksToBounds = true

label.isHidden = false
label.font = UIFont.preferredFont(forTextStyle: .body)
label.textColor = UIColor.black
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
label.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 10),
label.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -10),
])
}

public func startAnimation() {
innerContentView.startAnimation()
}

public func endAnimation() {
innerContentView.stopAnimation()
}
}

Make error view with 2 cross lines

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
import UIKit

public class ErrorView: UIView, AnimationAware {
public let line1 = CAShapeLayer()
public let line2 = CAShapeLayer()

public let animation1 = CASpringAnimation(keyPath: #keyPath(CALayer.transform))
public let animation2 = CASpringAnimation(keyPath: #keyPath(CALayer.transform))

public var lineColor: UIColor = UIColor.darkGray
public var duration: TimeInterval = 0.75

public override init(frame: CGRect) {
super.init(frame: frame)
layer.addSublayer(line1)
layer.addSublayer(line2)
configure()
}

public required init?(coder aDecoder: NSCoder) {
fatalError()
}

public override func layoutSubviews() {
super.layoutSubviews()

configureSize()
}

public func configure() {
[line1, line2].forEach {
$0.backgroundColor = lineColor.cgColor
}

[animation1, animation2].forEach {
$0.fromValue = 0
$0.damping = 0.33
$0.initialVelocity = 0.01
$0.mass = 0.2
$0.duration = duration
$0.valueFunction = CAValueFunction(name: CAValueFunctionName.rotateZ)
$0.timingFunction = CAMediaTimingFunction(name: .easeIn)
}

animation1.toValue = CGFloat.pi / 4
animation2.toValue = -CGFloat.pi / 4
}

private func configureSize() {
guard line1.frame.width <= 0 else {
return
}

[line1, line2].forEach {
$0.cornerRadius = 3
$0.frame.size = CGSize(width: bounds.width*0.6, height: 6)
$0.position = layer.position
}
}

public override func didMoveToWindow() {
super.didMoveToWindow()

line1.transform = CATransform3DIdentity
line2.transform = CATransform3DIdentity
}

public func startAnimation() {
line1.transform = CATransform3DMakeRotation(CGFloat.pi/4, 0, 0, 1.0)
line2.transform = CATransform3DMakeRotation(-CGFloat.pi/4, 0, 0, 1.0)

line1.add(animation1, forKey: nil)
line2.add(animation2, forKey: nil)
}

public func stopAnimation() {
line1.removeAllAnimations()
line2.removeAllAnimations()
}
}

Make loading progress using replicator layers

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
import UIKit

public class ProgressView: UIView, AnimationAware {
public let replicatorLayer = CAReplicatorLayer()
public let animation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
public let line = CALayer()

public var lineCount: Int = 12
public var duration: TimeInterval = 1.0
public var lineSize: CGSize = CGSize(width: 20, height: 6)
public var lineColor: UIColor = UIColor.darkGray

public override init(frame: CGRect) {
super.init(frame: .zero)

configure()
}

public required init?(coder aDecoder: NSCoder) {
fatalError()
}

public func configure() {
let angle = CGFloat.pi * 2 / CGFloat(lineCount)
let rotation = CATransform3DMakeRotation(angle, 0, 0, 1.0)

replicatorLayer.instanceTransform = rotation
replicatorLayer.instanceCount = lineCount
replicatorLayer.instanceDelay = duration / TimeInterval(lineCount)

line.backgroundColor = lineColor.cgColor
line.frame.size = lineSize
line.cornerRadius = lineSize.height / 2

animation.fromValue = 1.0
animation.toValue = 0.0
animation.repeatCount = Float.greatestFiniteMagnitude
animation.timingFunction = CAMediaTimingFunction(name: .linear)
animation.duration = duration

replicatorLayer.addSublayer(line)
layer.addSublayer(replicatorLayer)

// x: the larger, the closer to center
// y: half the height, changing affects rotation of lines
line.position = CGPoint(x: 48, y: 75)
}

public override func layoutSubviews() {
super.layoutSubviews()

replicatorLayer.frame = bounds
}

public func startAnimation() {
line.add(animation, forKey: nil)
}

public func stopAnimation() {
line.removeAllAnimations()
}
}

Make success view with check mark animation

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
import UIKit

public class SuccessView: UIView, AnimationAware {
public let shapeLayer = CAShapeLayer()
public let animation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.strokeEnd))

public var lineColor: UIColor = UIColor.darkGray
public var duration: TimeInterval = 0.25

public override init(frame: CGRect) {
super.init(frame: frame)
layer.addSublayer(shapeLayer)
configure()
}

public required init?(coder aDecoder: NSCoder) {
fatalError()
}

public override func layoutSubviews() {
super.layoutSubviews()

configurePath()
}

public func configure() {
shapeLayer.lineCap = .round
shapeLayer.lineJoin = .round
shapeLayer.fillColor = nil
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = 6

animation.timingFunction = CAMediaTimingFunction(name: .easeIn)
animation.fromValue = 0.0
animation.toValue = 1.0
animation.duration = duration
}

private func configurePath() {
let size = CGSize(width: 80, height: 60)
shapeLayer.frame = CGRect(origin: .zero, size: size)
shapeLayer.position = layer.position

let path = UIBezierPath()
path.move(to: CGPoint(x: size.width * 0, y: size.height * 0.48))
path.addLine(to: CGPoint(x: size.width * 0.38, y: size.height))
path.addLine(to: CGPoint(x: size.width, y: size.height * 0.01))

shapeLayer.path = path.cgPath
}

public override func didMoveToWindow() {
super.didMoveToWindow()

shapeLayer.strokeEnd = 0.0
}

public func startAnimation() {
shapeLayer.strokeEnd = 1.0
shapeLayer.add(animation, forKey: nil)
}

public func stopAnimation() {
shapeLayer.removeAllAnimations()
}
}

Comments