Skip to content

Commit 3c53234

Browse files
committed
Adds ConfettiMode
1 parent 8195419 commit 3c53234

File tree

6 files changed

+97
-27
lines changed

6 files changed

+97
-27
lines changed

Example/Example/ViewController.swift

+42-14
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,64 @@ import UIKit
1111
class ViewController: UIViewController {
1212
private let emojiLabel: UILabel = {
1313
let this = UILabel()
14-
this.translatesAutoresizingMaskIntoConstraints = false
1514
this.font = .systemFont(ofSize: 64)
1615
this.text = "🎉"
1716
return this
1817
}()
19-
private let confettiView: ConfettiView = {
20-
let images = (1 ... 7).compactMap { UIImage(named: "confetti\($0)") }
21-
let this = ConfettiView(images: images)
18+
private var confettiView: ConfettiView?
19+
private let segmentedControl: UISegmentedControl = {
20+
let this = UISegmentedControl()
2221
this.translatesAutoresizingMaskIntoConstraints = false
22+
this.insertSegment(withTitle: "Top to Bottom", at: 0, animated: false)
23+
this.insertSegment(withTitle: "Center to Left", at: 1, animated: false)
24+
this.selectedSegmentIndex = 0
2325
return this
2426
}()
2527

2628
override func viewDidLoad() {
2729
super.viewDidLoad()
2830
view.backgroundColor = .systemBackground
2931
view.addSubview(emojiLabel)
30-
view.addSubview(confettiView)
31-
NSLayoutConstraint.activate([
32-
emojiLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
33-
emojiLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
32+
view.addSubview(segmentedControl)
33+
segmentedControl.addTarget(self, action: #selector(segmentedControlValueChanged), for: .valueChanged)
34+
setupConfettiView(with: .topToBottom)
35+
}
3436

35-
confettiView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
36-
confettiView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
37-
confettiView.topAnchor.constraint(equalTo: view.topAnchor),
38-
confettiView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
39-
])
37+
override func viewDidLayoutSubviews() {
38+
super.viewDidLayoutSubviews()
39+
let emojiLabelSize = emojiLabel.intrinsicContentSize
40+
let emojiLabelOrigin = CGPoint(x: (view.frame.width - emojiLabelSize.width) / 2, y: (view.frame.height - emojiLabelSize.height) / 2)
41+
emojiLabel.frame = CGRect(origin: emojiLabelOrigin, size: emojiLabelSize)
42+
confettiView?.frame = view.bounds
43+
let segmentedControlSize = segmentedControl.intrinsicContentSize
44+
let segmentedControlOriginX = (view.frame.width - segmentedControlSize.width) / 2
45+
let segmentedControlOriginY = view.frame.height - view.safeAreaInsets.bottom - segmentedControlSize.height - 30
46+
let segmentedControlOrigin = CGPoint(x: segmentedControlOriginX, y: segmentedControlOriginY)
47+
segmentedControl.frame = CGRect(origin: segmentedControlOrigin, size: segmentedControlSize)
4048
}
4149

4250
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
4351
super.touchesEnded(touches, with: event)
44-
confettiView.shoot()
52+
confettiView?.shoot()
53+
}
54+
}
55+
56+
private extension ViewController {
57+
private func setupConfettiView(with mode: ConfettiMode) {
58+
confettiView?.removeFromSuperview()
59+
let images = (1 ... 7).compactMap { UIImage(named: "confetti\($0)") }
60+
let confettiView = ConfettiView(mode: mode, images: images)
61+
confettiView.isUserInteractionEnabled = false
62+
view.addSubview(confettiView)
63+
self.confettiView = confettiView
64+
view.setNeedsLayout()
65+
}
66+
67+
@objc private func segmentedControlValueChanged() {
68+
if segmentedControl.selectedSegmentIndex == 0 {
69+
setupConfettiView(with: .topToBottom)
70+
} else if segmentedControl.selectedSegmentIndex == 1{
71+
setupConfettiView(with: .centerToLeft)
72+
}
4573
}
4674
}

Package.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
// swift-tools-version: 5.6
1+
// swift-tools-version: 5.7
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription
55

66
let package = Package(
77
name: "ConfettiKit",
8-
platforms: [.iOS(.v15)],
8+
platforms: [.iOS(.v15), .macCatalyst(.v15)],
99
products: [
1010
.library(name: "ConfettiKit", targets: ["ConfettiKit"]),
1111
],

Sources/ConfettiKit/ConfettiLayerView.swift

+39-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import UIKit
22

33
final class ConfettiLayerView: UIView {
4+
private let mode: ConfettiMode
45
private let emitterLayer: CAEmitterLayer = {
56
var behaviors: [NSObject] = []
67
if let behavior = EmitterBehavior.makeHorizontalWaveBehavior() {
@@ -10,7 +11,6 @@ final class ConfettiLayerView: UIView {
1011
behaviors.append(behavior)
1112
}
1213
let this = CAEmitterLayer()
13-
this.emitterShape = .line
1414
this.birthRate = 0
1515
this.lifetime = 0
1616
if !behaviors.isEmpty {
@@ -25,13 +25,15 @@ final class ConfettiLayerView: UIView {
2525
private let scaleRange: CGFloat
2626
private let speed: Float
2727

28-
init(images: [UIImage], birthRate: Float = 100, scale: CGFloat = 1, scaleRange: CGFloat = 0, speed: Float = 1) {
28+
init(mode: ConfettiMode, images: [UIImage], birthRate: Float = 100, scale: CGFloat = 1, scaleRange: CGFloat = 0, speed: Float = 1) {
29+
self.mode = mode
2930
self.images = images
3031
self.birthRate = birthRate
3132
self.scale = scale
3233
self.scaleRange = scaleRange
3334
self.speed = speed
3435
super.init(frame: .zero)
36+
emitterLayer.emitterShape = mode.emitterShape
3537
layer.addSublayer(emitterLayer)
3638
}
3739

@@ -42,7 +44,7 @@ final class ConfettiLayerView: UIView {
4244
override func layoutSubviews() {
4345
super.layoutSubviews()
4446
emitterLayer.emitterSize = CGSize(width: bounds.width, height: 0)
45-
emitterLayer.emitterPosition = CGPoint(x: bounds.midX, y: -10)
47+
emitterLayer.emitterPosition = mode.emitterPosition(in: bounds)
4648
emitterLayer.frame = bounds
4749
updateAttractorBehavior()
4850
}
@@ -139,12 +141,23 @@ private extension ConfettiLayerView {
139141
private func addGravityAnimation() {
140142
let animation = CAKeyframeAnimation()
141143
animation.duration = 6
142-
animation.keyTimes = [0.05, 0.1, 0.5, 1]
143-
animation.values = [0, 300, 750, 1000]
144+
switch mode {
145+
case .topToBottom:
146+
animation.keyTimes = [0.05, 0.1, 0.5, 1]
147+
animation.values = [0, 300, 750, 1000]
148+
case .centerToLeft:
149+
animation.keyTimes = [0, 0.1, 0.5, 1]
150+
animation.values = [-1000, -750, -100, 0]
151+
}
144152
let cells = emitterLayer.emitterCells ?? []
145153
for cell in cells {
146154
if let name = cell.name {
147-
emitterLayer.add(animation, forKey: "emitterCells.\(name).yAcceleration")
155+
switch mode {
156+
case .topToBottom:
157+
emitterLayer.add(animation, forKey: "emitterCells.\(name).yAcceleration")
158+
case .centerToLeft:
159+
emitterLayer.add(animation, forKey: "emitterCells.\(name).xAcceleration")
160+
}
148161
}
149162
}
150163
}
@@ -155,3 +168,23 @@ private extension ConfettiLayerView {
155168
return radians.value
156169
}
157170
}
171+
172+
private extension ConfettiMode {
173+
var emitterShape: CAEmitterLayerEmitterShape {
174+
switch self {
175+
case .topToBottom:
176+
return .line
177+
case .centerToLeft:
178+
return .point
179+
}
180+
}
181+
182+
func emitterPosition(in rect: CGRect) -> CGPoint {
183+
switch self {
184+
case .topToBottom:
185+
return CGPoint(x: rect.midX, y: -10)
186+
case .centerToLeft:
187+
return CGPoint(x: rect.midX, y: rect.midY)
188+
}
189+
}
190+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
public enum ConfettiMode {
2+
case topToBottom
3+
case centerToLeft
4+
}

Sources/ConfettiKit/ConfettiShotView.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ final class ConfettiShotView: UIView {
44
private let backgroundLayerView: ConfettiLayerView
55
private let foregroundLayerView: ConfettiLayerView
66

7-
init(images: [UIImage]) {
8-
backgroundLayerView = ConfettiLayerView(images: images, birthRate: 20, scale: 0.6, speed: 0.95)
9-
foregroundLayerView = ConfettiLayerView(images: images, birthRate: 48, scale: 0.8, scaleRange: 0.1)
7+
init(mode: ConfettiMode, images: [UIImage], birthRate1: Float, birthRate2: Float) {
8+
backgroundLayerView = ConfettiLayerView(mode: mode, images: images, birthRate: birthRate1, scale: 0.6, speed: 0.95)
9+
foregroundLayerView = ConfettiLayerView(mode: mode, images: images, birthRate: birthRate2, scale: 0.8, scaleRange: 0.1)
1010
backgroundLayerView.alpha = 0.5
1111
super.init(frame: .zero)
1212
addSubview(backgroundLayerView)

Sources/ConfettiKit/ConfettiView.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import UIKit
22

33
public final class ConfettiView: UIView {
4+
public var birthRate1: Float = 48
5+
public var birthRate2: Float = 20
6+
7+
private let mode: ConfettiMode
48
private let images: [UIImage]
59
private var shootings: [Shooting] = []
610

7-
public init(images: [UIImage]) {
11+
public init(mode: ConfettiMode = .topToBottom, images: [UIImage]) {
12+
self.mode = mode
813
self.images = images
914
super.init(frame: .zero)
1015
clipsToBounds = true
@@ -22,7 +27,7 @@ public final class ConfettiView: UIView {
2227
}
2328

2429
public func shoot() {
25-
let view = ConfettiShotView(images: images)
30+
let view = ConfettiShotView(mode: mode, images: images, birthRate1: birthRate1, birthRate2: birthRate2)
2631
view.frame = bounds
2732
addSubview(view)
2833
let shooting = Shooting(view: view)

0 commit comments

Comments
 (0)