Compare commits

..

No commits in common. "master" and "0.3.0" have entirely different histories.

10 changed files with 104 additions and 354 deletions

View File

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ConfettiSwiftUI"
BuildableName = "ConfettiSwiftUI"
BlueprintName = "ConfettiSwiftUI"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ConfettiSwiftUITests"
BuildableName = "ConfettiSwiftUITests"
BlueprintName = "ConfettiSwiftUITests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ConfettiSwiftUITests"
BuildableName = "ConfettiSwiftUITests"
BlueprintName = "ConfettiSwiftUITests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ConfettiSwiftUI"
BuildableName = "ConfettiSwiftUI"
BlueprintName = "ConfettiSwiftUI"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 KiB

View File

@ -26,12 +26,9 @@ let package = Package(
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "ConfettiSwiftUI",
dependencies: [],
path: "Sources"),
dependencies: []),
.testTarget(
name: "ConfettiSwiftUITests",
dependencies: ["ConfettiSwiftUI"],
path: "Tests"),
dependencies: ["ConfettiSwiftUI"]),
]
)

284
README.md
View File

@ -1,105 +1,138 @@
# ConfettiSwiftUI
# :confetti_ball: ConfettiSwiftUI :confetti_ball:
<img src="https://img.shields.io/badge/PLATFORM-IOS%20|%20MACOS-lightgray?style=for-the-badge" />&nbsp;&nbsp;&nbsp;<img src="https://img.shields.io/badge/LICENSE-MIT-lightgray?style=for-the-badge" />&nbsp;&nbsp;&nbsp;<img src="https://img.shields.io/badge/MADE WITH-SWIFTUI-orange?style=for-the-badge" />
Swift package for displaying configurable confetti animation.
### Customizable Confetti Animations in SwiftUI
Find the [demo project here](https://github.com/simibac/ConfettiSwiftUIDemo).
<p align="center">
<img src="./Gifs/native_default_iphone.png" width="200" width="480"/>
<p float="center">
<img src="./Gifs/default.gif" width="400" />
</p>
## 🌄 Example
### Installation:
<p align="center">
<img src="./Gifs/default.gif" width="150" />
<img src="./Gifs/make-it-rain.gif" width="150"/>
<img src="./Gifs/explosion.gif" width="150" />
<img src="./Gifs/color.gif" width="150" />
</p>
It requires iOS 14 and Xcode 12!
## 🔭 Overview
In Xcode go to `File -> Swift Packages -> Add Package Dependency` and paste in the repo's url: `https://github.com/simibac/ConfettiSwiftUI` and select master branch.
This is an open-source library to use with SwiftUI. It allows you to create and customize confetti animations.
### Usage
- Built with pure SwiftUI.
- Select from default confetti shapes or inject emojis as text.
- Configure the radius and angles of the explosion.
- Trigger animation with one state change multiple times.
## 🔨Support
If you like the project, don't forget to `put star 🌟`.
<a href="https://brianmacdonald.github.io/Ethonate/address#0xCBa97323b4cA2fF9330827faF306065da7aA338F">
<img src="https://brianmacdonald.github.io/Ethonate/svg/eth-donate-blue.svg"/>
</a>
<!-- <a href="mailto:simibac2@icloud.com"><img src="https://img.shields.io/badge/EMAIL-SIMON-informational?style=for-the-badge&logo=minutemailer&logoColor=white"></a>&nbsp;&nbsp;&nbsp;<a href="https://www.linkedin.com/in/simon-bachmann-73b695151/" target="_blank"><img src="https://img.shields.io/badge/LINKEDIN-informational?style=for-the-badge&logo=linkedin&logoColor=white" ></a>&nbsp;&nbsp;&nbsp;<a href="https://www.paypal.com/donate?business=6H8D2EDR6LBX6&no_recurring=0&item_name=Thanks+for+supporting+open+source+contributions%21&currency_code=CHF" target="_blank"><img src="https://img.shields.io/badge/Donate-informational?style=for-the-badge&logo=paypal&logoColor=white" ></a> -->
## 🧭 Navigation
- [💻 Installation](#-installation)
- [Swift Package Manager](#swift-package-manager)
- [Manually](#manually)
- [🧳 Requirements](#-requirements)
- [🛠 Usage](#-usage)
- [Parameters](#parameters)
- [Configurator Application With Live Preview](#configurator-application-with-live-preview)
- [Examples](#examples)
- [👨‍💻 Contributors](#-contributors)
- [✍️ Author](#-author)
- [📃 License](#-license)
- [📦 Projects](#-projects)
## 💻 Installation
### Swift Package Manager
The [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. Its integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
To integrate `ConfettiSwiftUI` into your Xcode project using Xcode 12, specify it in `File > Swift Packages > Add Package Dependency...`:
```ogdl
https://github.com/simibac/ConfettiSwiftUI.git, :branch="master"
```
---
### Manually
If you prefer not to use any of dependency managers, you can integrate `ConfettiSwiftUI` into your project manually. Put `Sources/ConfettiSwiftUI` folder in your Xcode project. Make sure to enable `Copy items if needed` and `Create groups`.
## 🧳 Requirements
- iOS 14.0+ | macOS 11+
- Swift 5+
## 🛠 Usage
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).
Import the package in the file you would like to use it: `import ConfettiSwiftUI`
```swift
import ConfettiSwiftUI
import SwiftUI
struct ContentView: View {
@State private var counter: Int = 0
@State var counter:Int = 0
var body: some View {
Button("🎉") {
counter += 1
}
.confettiCannon(counter: $counter)
ZStack{
Text("🎉").font(.system(size: 50)).onTapGesture(){counter += 1}
ConfettiCannon(counter: $counter)
}
}
}
```
### Demo
Added an example project, with **iOS** target: https://github.com/simibac/ConfettiSwiftUIDemo
## Configurations
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 float="center">
<img src="./Gifs/configurator.png" width="300" />
<img src="./Gifs/examples.png" width="300" />
</p>
#### Default Configuration
<p float="center">
<img src="./Gifs/default.gif" width="300" />
</p>
Code
```swift
ConfettiCannon(counter: $counter1)
```
#### Color and Size Configuration
<p float="center">
<img src="./Gifs/color.gif" width="300" />
</p>
Code:
```swift
ConfettiCannon(counter: $counter2, colors: [.red, .black], confettiSize: 20)
```
#### Repeat Configuration
<p float="center">
<img src="./Gifs/repeat.gif" width="300" />
</p>
Code:
```swift
ConfettiCannon(counter: $counter3, repetitions: 3, repetitionInterval: 0.7)
```
#### Firework Configuration
<p float="center">
<img src="./Gifs/explosion.gif" width="300" />
</p>
Code:
```swift
ConfettiCannon(counter: $counter4, num: 50, openingAngle: Angle(degrees: 0), closingAngle: Angle(degrees: 360), radius: 200)
```
#### Emoji Configuration
<p float="center">
<img src="./Gifs/heart.gif" width="300" />
</p>
Code:
```swift
ConfettiCannon(counter: $counter5, confettis: [.text("❤️"), .text("💙"), .text("💚"), .text("🧡")])
```
#### Endless Configuration
<p float="center">
<img src="./Gifs/constant.gif" width="300" />
</p>
Code:
```swift
ConfettiCannon(counter: $counter6, num:1, confettis: [.text("💩")], confettiSize: 20, repetitions: 100, repetitionInterval: 0.1)
```
#### Make-it-Rain Configuration
<p float="center">
<img src="./Gifs/make-it-rain.gif" width="300" />
</p>
Code:
```swift
ConfettiCannon(counter: $counter7, num:1, confettis: [.text("💵"), .text("💶"), .text("💷"), .text("💴")], confettiSize: 30, repetitions: 50, repetitionInterval: 0.1)
```
### Parameters
| parameter | type | description | default |
| ------------------ | -------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| -------------------- | ------------ | ----------------------------------------------------- | ------------------------------------------------------- |
| counter | Binding<Int> | on any change of this variable triggers the animation | 0 |
| num | Int | amount of confettis | 20 |
| confettis | [ConfettiType] | list of shapes and text | [.shape(.circle), .shape(.triangle), .shape(.square), .shape(.slimRectangle), .shape(.roundedCross)] |
@ -114,95 +147,8 @@ struct ContentView: View {
| repetitions | Int | number of repetitions for the explosion | 0 |
| repetitionInterval | Double | duration between the repetitions | 1.0 |
### 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.
<p align="center">
<img src="./Gifs/configurator.png" width="150" />
<img src="./Gifs/examples.png" width="150" />
</p>
### Examples
#### Color and Size Configuration
<p align="center">
<img src="./Gifs/color.gif" width="150" />
</p>
```swift
.confettiCannon(counter: $counter, colors: [.red, .black], confettiSize: 20)
```
#### Repeat Configuration
<p align="center">
<img src="./Gifs/repeat.gif" width="150" />
</p>
```swift
.confettiCannon(counter: $counter, repetitions: 3, repetitionInterval: 0.7)
```
#### Firework Configuration
<p align="center">
<img src="./Gifs/explosion.gif" width="150" />
</p>
```swift
.confettiCannon(counter: $counter, num: 50, openingAngle: Angle(degrees: 0), closingAngle: Angle(degrees: 360), radius: 200)
```
#### Emoji Configuration
<p align="center">
<img src="./Gifs/heart.gif" width="150" />
</p>
```swift
.confettiCannon(counter: $counter, confettis: [.text("❤️"), .text("💙"), .text("💚"), .text("🧡")])
```
#### Endless Configuration
<p align="center">
<img src="./Gifs/constant.gif" width="150" />
</p>
```swift
.confettiCannon(counter: $counter, num:1, confettis: [.text("💩")], confettiSize: 20, repetitions: 100, repetitionInterval: 0.1)
```
#### Make-it-Rain Configuration
<p align="center">
<img src="./Gifs/make-it-rain.gif" width="150" />
</p>
```swift
.confettiCannon(counter: $counter, num:1, confettis: [.text("💵"), .text("💶"), .text("💷"), .text("💴")], confettiSize: 30, repetitions: 50, repetitionInterval: 0.1)
```
## 👨‍💻 Contributors
All issue reports, feature requests, pull requests and GitHub stars are welcomed and much appreciated.
## ✍️ Author
Simon Bachmann
## 📃 License
`ConfettiSwiftUI` is available under the MIT license. See the [LICENSE](https://github.com/simibac/ConfettiSwiftUI/blob/master/LICENSE) file for more info.
## 📦 Projects
## Projects
The following projects have integrated ConfettiSwiftUI in their App.
- [Basic Code](https://basiccode.de) available on the [AppStore](https://apps.apple.com/de/app/basiccode/id1562309250)
---
- [Jump Up](#-overview)
- [Basic Code](https://basiccode.de) avaliable on the [AppStore](https://apps.apple.com/de/app/basiccode/id1562309250)

View File

@ -19,7 +19,6 @@ public enum ConfettiType:CaseIterable, Hashable {
case shape(Shape)
case text(String)
case sfSymbol(symbolName: String)
public var view:AnyView{
switch self {
@ -33,8 +32,6 @@ public enum ConfettiType:CaseIterable, Hashable {
return AnyView(RoundedCross())
case let .text(text):
return AnyView(Text(text))
case .sfSymbol(let symbolName):
return AnyView(Image(systemName: symbolName))
default:
return AnyView(Circle())
}
@ -51,8 +48,8 @@ public struct ConfettiCannon: View {
@StateObject private var confettiConfig:ConfettiConfig
@State var animate:[Bool] = []
@State var finishedAnimationCounter = 0
@State var firstAppear = false
@State var finishedAnimationCouter = 0
@State var firtAppear = false
@State var error = ""
/// renders configurable confetti animaiton
@ -82,6 +79,7 @@ public struct ConfettiCannon: View {
radius:CGFloat = 300,
repetitions:Int = 0,
repetitionInterval:Double = 1.0
) {
self._counter = counter
var shapes = [AnyView]()
@ -115,22 +113,22 @@ public struct ConfettiCannon: View {
public var body: some View {
ZStack{
ForEach(finishedAnimationCounter..<animate.count, id:\.self){ i in
ForEach(finishedAnimationCouter..<animate.count, id:\.self){ i in
ConfettiContainer(
finishedAnimationCounter: $finishedAnimationCounter,
finishedAnimationCouter: $finishedAnimationCouter,
confettiConfig: confettiConfig
)
}
}
.onAppear(){
firstAppear = true
firtAppear = true
}
.onChange(of: counter){value in
if firstAppear{
if firtAppear{
for i in 0...confettiConfig.repetitions{
DispatchQueue.main.asyncAfter(deadline: .now() + confettiConfig.repetitionInterval * Double(i)) {
animate.append(false)
if(value > 0 && value < animate.count){
if(value < animate.count){
animate[value-1].toggle()
}
}
@ -142,7 +140,7 @@ public struct ConfettiCannon: View {
@available(iOS 14.0, macOS 11.0, watchOS 7, tvOS 14.0, *)
struct ConfettiContainer: View {
@Binding var finishedAnimationCounter:Int
@Binding var finishedAnimationCouter:Int
@StateObject var confettiConfig:ConfettiConfig
@State var firstAppear = true
@ -155,7 +153,7 @@ struct ConfettiContainer: View {
.onAppear(){
if firstAppear{
DispatchQueue.main.asyncAfter(deadline: .now() + confettiConfig.animationDuration) {
self.finishedAnimationCounter += 1
self.finishedAnimationCouter += 1
}
firstAppear = false
}
@ -182,32 +180,12 @@ struct ConfettiView: View{
return spinDirections.randomElement()!
}
func getRandomExplosionTimeVariation() -> CGFloat {
CGFloat((0...999).randomElement()!) / 2100
}
func getAnimationDuration() -> CGFloat {
return 0.2 + confettiConfig.explosionAnimationDuration + getRandomExplosionTimeVariation()
}
func getAnimation() -> Animation {
return Animation.timingCurve(0.1, 0.8, 0, 1, duration: getAnimationDuration())
}
func getDistance() -> CGFloat {
return pow(CGFloat.random(in: 0.01...1), 2.0/7.0) * confettiConfig.radius
}
func getDelayBeforeRainAnimation() -> TimeInterval {
confettiConfig.explosionAnimationDuration * 0.1
}
var body: some View{
ConfettiAnimationView(shape:getShape(), color:getColor(), spinDirX: getSpinDirection(), spinDirZ: getSpinDirection())
.offset(x: location.x, y: location.y)
.opacity(opacity)
.onAppear(){
withAnimation(getAnimation()) {
withAnimation(Animation.timingCurve(0.61, 1, 0.88, 1, duration: confettiConfig.explosionAnimationDuration)) {
opacity = confettiConfig.opacity
let randomAngle:CGFloat
@ -217,13 +195,13 @@ struct ConfettiView: View{
randomAngle = CGFloat.random(in: CGFloat(confettiConfig.openingAngle.degrees)...CGFloat(confettiConfig.closingAngle.degrees + 360)).truncatingRemainder(dividingBy: 360)
}
let distance = getDistance()
let distance = CGFloat.random(in: 0.5...1) * confettiConfig.radius
location.x = distance * cos(deg2rad(randomAngle))
location.y = -distance * sin(deg2rad(randomAngle))
}
DispatchQueue.main.asyncAfter(deadline: .now() + getDelayBeforeRainAnimation()) {
DispatchQueue.main.asyncAfter(deadline: .now() + confettiConfig.explosionAnimationDuration) {
withAnimation(Animation.timingCurve(0.12, 0, 0.39, 0, duration: confettiConfig.rainAnimationDuration)) {
location.y += confettiConfig.rainHeight
opacity = confettiConfig.fadesOut ? 0 : confettiConfig.opacity
@ -247,9 +225,9 @@ struct ConfettiAnimationView: View {
@State var move = false
@State var xSpeed:Double = Double.random(in: 0.501...2.201)
@State var xSpeed:Double = Double.random(in: 1...2)
@State var zSpeed = Double.random(in: 0.501...2.201)
@State var zSpeed = Double.random(in: 1...2)
@State var anchor = CGFloat.random(in: 0...1).rounded()
var body: some View {
@ -282,7 +260,7 @@ class ConfettiConfig: ObservableObject {
self.radius = radius
self.repetitions = repetitions
self.repetitionInterval = repetitionInterval
self.explosionAnimationDuration = Double(radius / 1300)
self.explosionAnimationDuration = Double(radius / 1500)
self.rainAnimationDuration = Double((rainHeight + radius) / 200)
}

View File

@ -1,80 +0,0 @@
//
// 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
)
}
}
}