Compare commits
23 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
2a5797c5ad | |
![]() |
2c51255034 | |
![]() |
8d3a15d0aa | |
![]() |
860ef2cb88 | |
![]() |
f062bdef3f | |
![]() |
8451defe73 | |
![]() |
b507d48d68 | |
![]() |
4458eb45da | |
![]() |
7b86d9f838 | |
![]() |
5b6caf2d10 | |
![]() |
0791408495 | |
![]() |
b0ae15a2d9 | |
![]() |
726499f9e5 | |
![]() |
2ea81e9fad | |
![]() |
9a19a70676 | |
![]() |
c0cf2e286b | |
![]() |
316598db8d | |
![]() |
a4a5a539a1 | |
![]() |
9a71edc711 | |
![]() |
712d8bc157 | |
![]() |
97fa443e5c | |
![]() |
c9ecbe03a3 | |
![]() |
d718f4ee18 |
|
@ -26,9 +26,12 @@ let package = Package(
|
||||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||||
.target(
|
.target(
|
||||||
name: "ConfettiSwiftUI",
|
name: "ConfettiSwiftUI",
|
||||||
dependencies: []),
|
dependencies: [],
|
||||||
|
path: "Sources"),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "ConfettiSwiftUITests",
|
name: "ConfettiSwiftUITests",
|
||||||
dependencies: ["ConfettiSwiftUI"]),
|
dependencies: ["ConfettiSwiftUI"],
|
||||||
|
path: "Tests"),
|
||||||
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
34
README.md
34
README.md
|
@ -79,19 +79,21 @@ If you prefer not to use any of dependency managers, you can integrate `Confetti
|
||||||
First, add `import ConfettiSwiftUI` on every `swift` file you would like to use `ConfettiSwiftUI`. Define a integer as a state varable which is responsible for triggering the animation. Any change to that variable will span a new animation (increment and decrement).
|
First, add `import ConfettiSwiftUI` on every `swift` file you would like to use `ConfettiSwiftUI`. Define a integer as a state varable which is responsible for triggering the animation. Any change to that variable will span a new animation (increment and decrement).
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
import SwiftUI
|
|
||||||
import ConfettiSwiftUI
|
import ConfettiSwiftUI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@State var counter:Int = 0
|
|
||||||
|
@State private var counter: Int = 0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack{
|
Button("🎉") {
|
||||||
Text("🎉").font(.system(size: 50)).onTapGesture(){counter += 1}
|
counter += 1
|
||||||
ConfettiCannon(counter: $counter)
|
|
||||||
}
|
}
|
||||||
|
.confettiCannon(counter: $counter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
@ -115,11 +117,11 @@ struct ContentView: View {
|
||||||
### Configurator Application With Live Preview
|
### Configurator Application With Live Preview
|
||||||
|
|
||||||
You can use the configurator app in [demo project here](https://github.com/simibac/ConfettiSwiftUIDemo) to make your desired animation or get inspired by one of the many examples.
|
You can use the configurator app in [demo project here](https://github.com/simibac/ConfettiSwiftUIDemo) to make your desired animation or get inspired by one of the many examples.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./Gifs/configurator.png" width="150" />
|
<img src="./Gifs/configurator.png" width="150" />
|
||||||
<img src="./Gifs/examples.png" width="150" />
|
<img src="./Gifs/examples.png" width="150" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
@ -130,7 +132,7 @@ You can use the configurator app in [demo project here](https://github.com/simib
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
ConfettiCannon(counter: $counter2, colors: [.red, .black], confettiSize: 20)
|
.confettiCannon(counter: $counter, colors: [.red, .black], confettiSize: 20)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Repeat Configuration
|
#### Repeat Configuration
|
||||||
|
@ -140,7 +142,7 @@ ConfettiCannon(counter: $counter2, colors: [.red, .black], confettiSize: 20)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
ConfettiCannon(counter: $counter3, repetitions: 3, repetitionInterval: 0.7)
|
.confettiCannon(counter: $counter, repetitions: 3, repetitionInterval: 0.7)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Firework Configuration
|
#### Firework Configuration
|
||||||
|
@ -150,7 +152,7 @@ ConfettiCannon(counter: $counter3, repetitions: 3, repetitionInterval: 0.7)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
ConfettiCannon(counter: $counter4, num: 50, openingAngle: Angle(degrees: 0), closingAngle: Angle(degrees: 360), radius: 200)
|
.confettiCannon(counter: $counter, num: 50, openingAngle: Angle(degrees: 0), closingAngle: Angle(degrees: 360), radius: 200)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Emoji Configuration
|
#### Emoji Configuration
|
||||||
|
@ -160,7 +162,7 @@ ConfettiCannon(counter: $counter4, num: 50, openingAngle: Angle(degrees: 0), clo
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
ConfettiCannon(counter: $counter5, confettis: [.text("❤️"), .text("💙"), .text("💚"), .text("🧡")])
|
.confettiCannon(counter: $counter, confettis: [.text("❤️"), .text("💙"), .text("💚"), .text("🧡")])
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Endless Configuration
|
#### Endless Configuration
|
||||||
|
@ -170,7 +172,7 @@ ConfettiCannon(counter: $counter5, confettis: [.text("❤️"), .text("💙"), .
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
ConfettiCannon(counter: $counter6, num:1, confettis: [.text("💩")], confettiSize: 20, repetitions: 100, repetitionInterval: 0.1)
|
.confettiCannon(counter: $counter, num:1, confettis: [.text("💩")], confettiSize: 20, repetitions: 100, repetitionInterval: 0.1)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Make-it-Rain Configuration
|
#### Make-it-Rain Configuration
|
||||||
|
@ -180,7 +182,7 @@ ConfettiCannon(counter: $counter6, num:1, confettis: [.text("💩")], confettiSi
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
ConfettiCannon(counter: $counter7, num:1, confettis: [.text("💵"), .text("💶"), .text("💷"), .text("💴")], confettiSize: 30, repetitions: 50, repetitionInterval: 0.1)
|
.confettiCannon(counter: $counter, num:1, confettis: [.text("💵"), .text("💶"), .text("💷"), .text("💴")], confettiSize: 30, repetitions: 50, repetitionInterval: 0.1)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 👨💻 Contributors
|
## 👨💻 Contributors
|
||||||
|
|
|
@ -19,6 +19,7 @@ public enum ConfettiType:CaseIterable, Hashable {
|
||||||
|
|
||||||
case shape(Shape)
|
case shape(Shape)
|
||||||
case text(String)
|
case text(String)
|
||||||
|
case sfSymbol(symbolName: String)
|
||||||
|
|
||||||
public var view:AnyView{
|
public var view:AnyView{
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -32,6 +33,8 @@ public enum ConfettiType:CaseIterable, Hashable {
|
||||||
return AnyView(RoundedCross())
|
return AnyView(RoundedCross())
|
||||||
case let .text(text):
|
case let .text(text):
|
||||||
return AnyView(Text(text))
|
return AnyView(Text(text))
|
||||||
|
case .sfSymbol(let symbolName):
|
||||||
|
return AnyView(Image(systemName: symbolName))
|
||||||
default:
|
default:
|
||||||
return AnyView(Circle())
|
return AnyView(Circle())
|
||||||
}
|
}
|
||||||
|
@ -48,8 +51,8 @@ public struct ConfettiCannon: View {
|
||||||
@StateObject private var confettiConfig:ConfettiConfig
|
@StateObject private var confettiConfig:ConfettiConfig
|
||||||
|
|
||||||
@State var animate:[Bool] = []
|
@State var animate:[Bool] = []
|
||||||
@State var finishedAnimationCouter = 0
|
@State var finishedAnimationCounter = 0
|
||||||
@State var firtAppear = false
|
@State var firstAppear = false
|
||||||
@State var error = ""
|
@State var error = ""
|
||||||
|
|
||||||
/// renders configurable confetti animaiton
|
/// renders configurable confetti animaiton
|
||||||
|
@ -112,22 +115,22 @@ public struct ConfettiCannon: View {
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
ZStack{
|
ZStack{
|
||||||
ForEach(finishedAnimationCouter..<animate.count, id:\.self){ i in
|
ForEach(finishedAnimationCounter..<animate.count, id:\.self){ i in
|
||||||
ConfettiContainer(
|
ConfettiContainer(
|
||||||
finishedAnimationCouter: $finishedAnimationCouter,
|
finishedAnimationCounter: $finishedAnimationCounter,
|
||||||
confettiConfig: confettiConfig
|
confettiConfig: confettiConfig
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear(){
|
.onAppear(){
|
||||||
firtAppear = true
|
firstAppear = true
|
||||||
}
|
}
|
||||||
.onChange(of: counter){value in
|
.onChange(of: counter){value in
|
||||||
if firtAppear{
|
if firstAppear{
|
||||||
for i in 0...confettiConfig.repetitions{
|
for i in 0...confettiConfig.repetitions{
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + confettiConfig.repetitionInterval * Double(i)) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + confettiConfig.repetitionInterval * Double(i)) {
|
||||||
animate.append(false)
|
animate.append(false)
|
||||||
if(value < animate.count){
|
if(value > 0 && value < animate.count){
|
||||||
animate[value-1].toggle()
|
animate[value-1].toggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,7 +142,7 @@ public struct ConfettiCannon: View {
|
||||||
|
|
||||||
@available(iOS 14.0, macOS 11.0, watchOS 7, tvOS 14.0, *)
|
@available(iOS 14.0, macOS 11.0, watchOS 7, tvOS 14.0, *)
|
||||||
struct ConfettiContainer: View {
|
struct ConfettiContainer: View {
|
||||||
@Binding var finishedAnimationCouter:Int
|
@Binding var finishedAnimationCounter:Int
|
||||||
@StateObject var confettiConfig:ConfettiConfig
|
@StateObject var confettiConfig:ConfettiConfig
|
||||||
@State var firstAppear = true
|
@State var firstAppear = true
|
||||||
|
|
||||||
|
@ -152,7 +155,7 @@ struct ConfettiContainer: View {
|
||||||
.onAppear(){
|
.onAppear(){
|
||||||
if firstAppear{
|
if firstAppear{
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + confettiConfig.animationDuration) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + confettiConfig.animationDuration) {
|
||||||
self.finishedAnimationCouter += 1
|
self.finishedAnimationCounter += 1
|
||||||
}
|
}
|
||||||
firstAppear = false
|
firstAppear = false
|
||||||
}
|
}
|
||||||
|
@ -188,7 +191,7 @@ struct ConfettiView: View{
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAnimation() -> Animation {
|
func getAnimation() -> Animation {
|
||||||
return Animation.timingCurve(0, 1, 0, 1, duration: getAnimationDuration())
|
return Animation.timingCurve(0.1, 0.8, 0, 1, duration: getAnimationDuration())
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDistance() -> CGFloat {
|
func getDistance() -> CGFloat {
|
||||||
|
@ -196,7 +199,7 @@ struct ConfettiView: View{
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDelayBeforeRainAnimation() -> TimeInterval {
|
func getDelayBeforeRainAnimation() -> TimeInterval {
|
||||||
confettiConfig.explosionAnimationDuration * 0.1
|
confettiConfig.explosionAnimationDuration * 0.1
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View{
|
var body: some View{
|
||||||
|
@ -279,7 +282,7 @@ class ConfettiConfig: ObservableObject {
|
||||||
self.radius = radius
|
self.radius = radius
|
||||||
self.repetitions = repetitions
|
self.repetitions = repetitions
|
||||||
self.repetitionInterval = repetitionInterval
|
self.repetitionInterval = repetitionInterval
|
||||||
self.explosionAnimationDuration = Double(radius / 1400)
|
self.explosionAnimationDuration = Double(radius / 1300)
|
||||||
self.rainAnimationDuration = Double((rainHeight + radius) / 200)
|
self.rainAnimationDuration = Double((rainHeight + radius) / 200)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
//
|
||||||
|
// View+ConfettiCannon.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Abdullah Alhaider on 24/03/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public extension View {
|
||||||
|
|
||||||
|
/// renders configurable confetti animaiton
|
||||||
|
///
|
||||||
|
/// - Usage:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// import SwiftUI
|
||||||
|
///
|
||||||
|
/// struct ContentView: View {
|
||||||
|
///
|
||||||
|
/// @State private var counter: Int = 0
|
||||||
|
///
|
||||||
|
/// var body: some View {
|
||||||
|
/// Button("Wow") {
|
||||||
|
/// counter += 1
|
||||||
|
/// }
|
||||||
|
/// .confettiCannon(counter: $counter)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - counter: on any change of this variable the animation is run
|
||||||
|
/// - num: amount of confettis
|
||||||
|
/// - colors: list of colors that is applied to the default shapes
|
||||||
|
/// - confettiSize: size that confettis and emojis are scaled to
|
||||||
|
/// - rainHeight: vertical distance that confettis pass
|
||||||
|
/// - fadesOut: reduce opacity towards the end of the animation
|
||||||
|
/// - opacity: maximum opacity that is reached during the animation
|
||||||
|
/// - openingAngle: boundary that defines the opening angle in degrees
|
||||||
|
/// - closingAngle: boundary that defines the closing angle in degrees
|
||||||
|
/// - radius: explosion radius
|
||||||
|
/// - repetitions: number of repetitions of the explosion
|
||||||
|
/// - repetitionInterval: duration between the repetitions
|
||||||
|
///
|
||||||
|
@ViewBuilder func confettiCannon(
|
||||||
|
counter: Binding<Int>,
|
||||||
|
num: Int = 20,
|
||||||
|
confettis: [ConfettiType] = ConfettiType.allCases,
|
||||||
|
colors: [Color] = [.blue, .red, .green, .yellow, .pink, .purple, .orange],
|
||||||
|
confettiSize: CGFloat = 10.0,
|
||||||
|
rainHeight: CGFloat = 600.0,
|
||||||
|
fadesOut: Bool = true,
|
||||||
|
opacity: Double = 1.0,
|
||||||
|
openingAngle: Angle = .degrees(60),
|
||||||
|
closingAngle: Angle = .degrees(120),
|
||||||
|
radius: CGFloat = 300,
|
||||||
|
repetitions: Int = 0,
|
||||||
|
repetitionInterval: Double = 1.0
|
||||||
|
) -> some View {
|
||||||
|
ZStack {
|
||||||
|
self
|
||||||
|
ConfettiCannon(
|
||||||
|
counter: counter,
|
||||||
|
num: num,
|
||||||
|
confettis: confettis,
|
||||||
|
colors: colors,
|
||||||
|
confettiSize: confettiSize,
|
||||||
|
rainHeight: rainHeight,
|
||||||
|
fadesOut: fadesOut,
|
||||||
|
opacity: opacity,
|
||||||
|
openingAngle: openingAngle,
|
||||||
|
closingAngle: closingAngle,
|
||||||
|
radius: radius,
|
||||||
|
repetitions: repetitions,
|
||||||
|
repetitionInterval: repetitionInterval
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue