diff --git a/SwiftUI-Particles.xcodeproj/project.pbxproj b/Example/SwiftUI-Particles.xcodeproj/project.pbxproj
similarity index 100%
rename from SwiftUI-Particles.xcodeproj/project.pbxproj
rename to Example/SwiftUI-Particles.xcodeproj/project.pbxproj
diff --git a/SwiftUI-Particles.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/SwiftUI-Particles.xcodeproj/project.xcworkspace/contents.xcworkspacedata
similarity index 100%
rename from SwiftUI-Particles.xcodeproj/project.xcworkspace/contents.xcworkspacedata
rename to Example/SwiftUI-Particles.xcodeproj/project.xcworkspace/contents.xcworkspacedata
diff --git a/SwiftUI-Particles.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/SwiftUI-Particles.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
similarity index 100%
rename from SwiftUI-Particles.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
rename to Example/SwiftUI-Particles.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
diff --git a/SwiftUI-Particles/AppDelegate.swift b/Example/SwiftUI-Particles/AppDelegate.swift
similarity index 100%
rename from SwiftUI-Particles/AppDelegate.swift
rename to Example/SwiftUI-Particles/AppDelegate.swift
diff --git a/SwiftUI-Particles/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/SwiftUI-Particles/Assets.xcassets/AppIcon.appiconset/Contents.json
similarity index 100%
rename from SwiftUI-Particles/Assets.xcassets/AppIcon.appiconset/Contents.json
rename to Example/SwiftUI-Particles/Assets.xcassets/AppIcon.appiconset/Contents.json
diff --git a/SwiftUI-Particles/Assets.xcassets/Contents.json b/Example/SwiftUI-Particles/Assets.xcassets/Contents.json
similarity index 100%
rename from SwiftUI-Particles/Assets.xcassets/Contents.json
rename to Example/SwiftUI-Particles/Assets.xcassets/Contents.json
diff --git a/SwiftUI-Particles/Assets.xcassets/spark.imageset/Contents.json b/Example/SwiftUI-Particles/Assets.xcassets/spark.imageset/Contents.json
similarity index 100%
rename from SwiftUI-Particles/Assets.xcassets/spark.imageset/Contents.json
rename to Example/SwiftUI-Particles/Assets.xcassets/spark.imageset/Contents.json
diff --git a/SwiftUI-Particles/Assets.xcassets/spark.imageset/spark.png b/Example/SwiftUI-Particles/Assets.xcassets/spark.imageset/spark.png
similarity index 100%
rename from SwiftUI-Particles/Assets.xcassets/spark.imageset/spark.png
rename to Example/SwiftUI-Particles/Assets.xcassets/spark.imageset/spark.png
diff --git a/SwiftUI-Particles/Base.lproj/LaunchScreen.storyboard b/Example/SwiftUI-Particles/Base.lproj/LaunchScreen.storyboard
similarity index 100%
rename from SwiftUI-Particles/Base.lproj/LaunchScreen.storyboard
rename to Example/SwiftUI-Particles/Base.lproj/LaunchScreen.storyboard
diff --git a/SwiftUI-Particles/ContentView.swift b/Example/SwiftUI-Particles/ContentView.swift
similarity index 100%
rename from SwiftUI-Particles/ContentView.swift
rename to Example/SwiftUI-Particles/ContentView.swift
diff --git a/SwiftUI-Particles/Info.plist b/Example/SwiftUI-Particles/Info.plist
similarity index 100%
rename from SwiftUI-Particles/Info.plist
rename to Example/SwiftUI-Particles/Info.plist
diff --git a/SwiftUI-Particles/ParticlesEmitter.swift b/Example/SwiftUI-Particles/ParticlesEmitter.swift
similarity index 100%
rename from SwiftUI-Particles/ParticlesEmitter.swift
rename to Example/SwiftUI-Particles/ParticlesEmitter.swift
diff --git a/SwiftUI-Particles/Preview Content/Preview Assets.xcassets/Contents.json b/Example/SwiftUI-Particles/Preview Content/Preview Assets.xcassets/Contents.json
similarity index 100%
rename from SwiftUI-Particles/Preview Content/Preview Assets.xcassets/Contents.json
rename to Example/SwiftUI-Particles/Preview Content/Preview Assets.xcassets/Contents.json
diff --git a/SwiftUI-Particles/SceneDelegate.swift b/Example/SwiftUI-Particles/SceneDelegate.swift
similarity index 100%
rename from SwiftUI-Particles/SceneDelegate.swift
rename to Example/SwiftUI-Particles/SceneDelegate.swift
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..067f798
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,28 @@
+// swift-tools-version:5.3
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "SwiftUI-Particles",
+ platforms: [
+ .iOS(.v13)
+ ],
+ products: [
+ // Products define the executables and libraries a package produces, and make them visible to other packages.
+ .library(
+ name: "SwiftUI-Particles",
+ targets: ["SwiftUI-Particles"]),
+ ],
+ dependencies: [
+ // Dependencies declare other packages that this package depends on.
+ // .package(url: /* package url */, from: "1.0.0"),
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+ // Targets can depend on other targets in this package, and on products in packages this package depends on.
+ .target(
+ name: "SwiftUI-Particles",
+ dependencies: [])
+ ]
+)
diff --git a/README.md b/README.md
index b399963..9cc349a 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,15 @@ Playing with particles with SwiftUI ✨
+## Installation via Swift Package Manager
+1. In your Xcode project, navigate to File → Swift Packages → Add Package Dependency...
+2. Copy and paste `https://github.com/ArthurGuibert/SwiftUI-Particles` into the search bar and click Next.
+3. For Rules, select "Branch" and set it to **master**.
+4. Click Finish.
## How to use
-Just use the the `ParticlesEmitter` class in your project as follow:
+Just use the `ParticlesEmitter` class in your project as follow:
```swift
struct ContentView: View {
diff --git a/Sources/SwiftUI-Particles/ParticlesEmitter.swift b/Sources/SwiftUI-Particles/ParticlesEmitter.swift
new file mode 100644
index 0000000..465c015
--- /dev/null
+++ b/Sources/SwiftUI-Particles/ParticlesEmitter.swift
@@ -0,0 +1,223 @@
+//
+// ParticlesEmitter.swift
+// SwiftUI-Particles
+//
+// Created by Arthur Guibert on 28/10/2019.
+// Copyright © 2019 Arthur Guibert. All rights reserved.
+//
+
+import SwiftUI
+import UIKit
+
+
+/// Class that wraps the CAEmitterCell in a class compatible with SwiftUI
+public struct ParticlesEmitter: UIViewRepresentable {
+ var center: CGPoint = .zero
+ var emitterSize: CGSize = .init(width: 1, height: 1)
+ var shape: CAEmitterLayerEmitterShape = .line
+ var cells: [CAEmitterCell] = []
+
+ public func updateUIView(_ uiView: InternalParticlesView, context: UIViewRepresentableContext) {
+ uiView.emit(from: center,
+ size: emitterSize,
+ shape: shape,
+ cells: cells)
+ }
+
+ public func makeUIView(context: Context) -> InternalParticlesView {
+ let view = InternalParticlesView()
+ view.emit(from: center,
+ size: emitterSize,
+ shape: shape,
+ cells: cells)
+ return view
+ }
+}
+
+extension ParticlesEmitter {
+ func emitterSize(_ size: CGSize) -> Self {
+ return ParticlesEmitter(center: self.center, emitterSize: size, shape: shape, cells: self.cells)
+ }
+
+ func emitterPosition(_ position: CGPoint) -> Self {
+ return ParticlesEmitter(center: position, emitterSize: self.emitterSize, shape: shape, cells: self.cells)
+ }
+
+ func emitterShape(_ shape: CAEmitterLayerEmitterShape) -> Self {
+ return ParticlesEmitter(center: self.center, emitterSize: self.emitterSize, shape: shape, cells: self.cells)
+ }
+}
+
+
+/// The container view class for the particles, as the project is using a CAEmitterLayer
+public final class InternalParticlesView: UIView {
+ private var particleEmitter: CAEmitterLayer?
+
+ /// Function that adds the emitter cells to the layer
+ /// - Parameter center: center of the emitter
+ /// - Parameter size: size of the emitter
+ /// - Parameter cells: all the CAEmitterCell
+ func emit(from center: CGPoint, size: CGSize, shape: CAEmitterLayerEmitterShape, cells: [CAEmitterCell]) {
+ if particleEmitter == nil {
+ particleEmitter = CAEmitterLayer()
+ layer.addSublayer(particleEmitter!)
+ }
+
+ particleEmitter?.emitterPosition = center
+ particleEmitter?.emitterShape = shape
+ particleEmitter?.emitterSize = size
+ particleEmitter?.emitterCells = cells
+ }
+}
+
+@_functionBuilder
+struct EmitterCellBuilder {
+ static func buildBlock(_ cells: CAEmitterCell...) -> [CAEmitterCell] {
+ Array(cells)
+ }
+}
+
+extension ParticlesEmitter {
+ init(@EmitterCellBuilder _ content: () -> [CAEmitterCell]) {
+ self.init(cells: content())
+ }
+
+ init(@EmitterCellBuilder _ content: () -> CAEmitterCell) {
+ self.init(cells: [content()])
+ }
+}
+
+class EmitterCell: CAEmitterCell {
+ override init() {
+ super.init()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ func copyEmitter() -> EmitterCell {
+ return super.copy() as! EmitterCell
+ }
+}
+
+
+extension EmitterCell {
+ /// Content for the emitter cell, it is either an image, or a circle.
+ /// NB: It could easily be extended for other shapes.
+ public enum Content {
+ case image(UIImage)
+ case circle(CGFloat)
+ }
+
+ @inlinable func content(_ content: Content) -> Self {
+ self.contents = content.image.cgImage
+ return self
+ }
+
+ @inlinable func birthRate(_ birthRate: Float) -> Self {
+ self.birthRate = birthRate
+ return self
+ }
+
+ @inlinable func lifetime(_ lifetime: Float) -> Self {
+ self.lifetime = lifetime
+ return self
+ }
+
+ @inlinable func scale(_ scale: CGFloat) -> Self {
+ self.scale = scale
+ return self
+ }
+
+ @inlinable func scaleRange(_ scaleRange: CGFloat) -> Self {
+ self.scaleRange = scaleRange
+ return self
+ }
+
+ @inlinable func scaleSpeed(_ scaleSpeed: CGFloat) -> Self {
+ self.scaleSpeed = scaleSpeed
+ return self
+ }
+
+ @inlinable func velocity(_ velocity: CGFloat) -> Self {
+ self.velocity = velocity
+ return self
+ }
+
+ @inlinable func velocityRange(_ velocityRange: CGFloat) -> Self {
+ self.velocityRange = velocityRange
+ return self
+ }
+
+ @inlinable func emissionLongitude(_ emissionLongitude: CGFloat) -> Self {
+ self.emissionLongitude = emissionLongitude
+ return self
+ }
+
+ @inlinable func emissionLatitude(_ emissionLatitude: CGFloat) -> Self {
+ self.emissionLatitude = emissionLatitude
+ return self
+ }
+
+ @inlinable func emissionRange(_ emissionRange: CGFloat) -> Self {
+ self.emissionRange = emissionRange
+ return self
+ }
+
+ @inlinable func spin(_ spin: CGFloat) -> Self {
+ self.spin = spin
+ return self
+ }
+
+ @inlinable func spinRange(_ spinRange: CGFloat) -> Self {
+ self.spinRange = spinRange
+ return self
+ }
+
+ @inlinable func color(_ color: UIColor) -> Self {
+ self.color = color.cgColor
+ return self
+ }
+
+ @inlinable func xAcceleration(_ xAcceleration: CGFloat) -> Self {
+ self.xAcceleration = xAcceleration
+ return self
+ }
+
+ @inlinable func yAcceleration(_ yAcceleration: CGFloat) -> Self {
+ self.yAcceleration = yAcceleration
+ return self
+ }
+
+ @inlinable func zAcceleration(_ zAcceleration: CGFloat) -> Self {
+ self.zAcceleration = zAcceleration
+ return self
+ }
+
+ @inlinable func alphaSpeed(_ alphaSpeed: Float) -> Self {
+ self.alphaSpeed = alphaSpeed
+ return self
+ }
+
+ @inlinable func alphaRange(_ alphaRange: Float) -> Self {
+ self.alphaRange = alphaRange
+ return self
+ }
+}
+
+fileprivate extension EmitterCell.Content {
+ var image: UIImage {
+ switch self {
+ case let .image(image):
+ return image
+ case let .circle(radius):
+ let size = CGSize(width: radius * 2, height: radius * 2)
+ return UIGraphicsImageRenderer(size: size).image { context in
+ context.cgContext.setFillColor(UIColor.white.cgColor)
+ context.cgContext.addPath(CGPath(ellipseIn: CGRect(origin: .zero, size: size), transform: nil))
+ context.cgContext.fillPath()
+ }
+ }
+ }
+}