Important
Particles v2.0 is in Pre-Release and it is in active development. If you plan on including Particles in your project, please read this additional information.
Create particle systems in a flash using a simple but powerful syntax.
import Particles
var body: some View {
ParticleSystem {
Emitter(every: 0.05) {
Particle {
Text("โจ")
}
.initialPosition(.center)
.initialVelocity(xIn: -1.0 ... 1.0, yIn: -1.0 ... 1.0)
.hueRotation(angleIn: .degrees(0) ... .degrees(360))
.glow(.white)
}
}
}
Easily integrate Particles into your SwiftUI views.
VStack {
Text(purchased ? "Thank you!" : "")
.emits(every: 0.1, if: purchased, offset: CGPoint(x: 0, y: -20)) {
Particle { Text("โค๏ธ") }
.fixAcceleration(y: 0.05)
.initialVelocity(xIn: -2.0 ... 2.0, yIn: -2.0 ... -1.5)
.transition(.scale)
}
Button("Purchase") {
purchased = true
}
}
And jump in with configurable presets.
import ParticlesPresets
ParticleSystem {
Preset.Fire()
Preset.Snow(intensity: 5)
Preset.Rain()
}
- Quickstart - install the repository
- Entities - such as
Particle
,Emitter
, andForEach
- Defining Entities - create custom
Entity
structs to use in particle systems - Modifiers - change the behavior of particles (see list)
- State Persistence - persist
ParticleSystem
simulation through state updates - Presets - browse a curated library of preset entities
- Pre-Release - this package is in pre-release, additional information here
โ ๏ธ - Performance - debugging, frame rate tips, and benchmarks
To get started, first add Particles as a Swift Package Dependency in your Xcode project:
https://github.com/benlmyers/swiftui-particles
- To begin with pre-made particles, like Fire or Rain, add and import the
ParticlesPresets
library:
import ParticlesPresets
- Or, to build your own particle systems, add and import the
Particles
library:
import Particles
Create a ParticleSystem
within your View
. Then, add some entities or presets!
struct MyView: View {
var body: some View {
ParticleSystem {
// Add entities here!
}
}
}
- iOS 15.0+
- macOS 12.0+
- watchOS 8.0+
Particles has several entities that bring life to your SwiftUI views. Some entities are built using views, and others using other entities.
A Particle
is the building block of the particle system. You can define one using a view:
Particle {
Circle().foregroundStyle(.red).frame(width: 10.0, height: 10.0)
}
An Emitter
fires new entities on a regular interval.
Emitter(every: 0.02) { // Fires every 0.02 seconds
Particle {
Text("๐")
}
.initialAcceleration(xIn: -1.0 ... 1.0, yIn: -1.0 ... 1.0)
.initialTorque(.degrees(1.0))
}
A Group
holds multiple entities. Like SwiftUI, modifiers applied to a Group will be applied to all entities inside the Group.
ParticleSystem {
Group {
Particle { Text("๐ฅ") }
Particle { Text("๐งจ") }
}
.glow(.red) // Both particles will have a red glow
}
While the name clashes with SwiftUI's, in most cases you needn't worry. The ParticleSystem
initializer tells the compiler to expect an Entity
-conforming rather than a View
-conforming Group
.
Like Group
, ForEach
holds multiple entities iterated over a collection of elements.
ParticleSystem {
ForEach([1, 2, 3, 4]) { i in
Particle { Text("\(i)") }
.initialVelocity(xIn: -1.0 ... 1.0) // Modifiers can also be applied outside of ForEach
}
}
Above, four view is registered; one for each particle. You can improve the performance of ForEach
by merging views, or in rarer cases, entity declarations:
ForEach(myLargeCollection, merges: .views) { item in
Particle {
Text("โญ๏ธ")
}
.initialPosition(xIn: 0 ... 100, yIn: 0 ... 100)
}
Here, only the first view is registered, and the rest of the entities receive the same view. To learn more about merges: .views
, see Performance.
A Lattice
creates a grid of particles that covers and samples the colors of a View
. You can customize the behavior of each particle in the Lattice
by applying modifiers.
ParticleSystem {
Lattice {
Image(systemName: "star.fill")
.resizable()
.frame(width: 100.0, height: 100.0)
.foregroundStyle(Color.red)
}
.scale(1.5)
.initialVelocity(xIn: -1.0 ... 1.0, yIn: -1.0 ... 1.0)
}
Tip
You can choose to have the lattice spawn particles along a view's edge by passing Lattice(hugging:)
.
You can define a custom entity by conforming a struct
to Entity
and providing a value for var body: some Entity
.
struct MyEmojiParticle: Entity {
var emoji: String
var body: some Entity {
Particle {
Text(emoji)
}
}
}
struct MyView: View {
var body: some View {
ParticleSystem {
MyEmojiParticle(emoji: "๐")
}
}
}
Particles has several modifiers you can apply to entities to change their behavior.
ParticleSystem {
Particle {
Image(systemName: "leaf.fill")
}
.lifetime(3) // particle lasts 3 seconds
.colorOverlay(.orange) // particle is orange
.blur(in: 0.0 ... 3.0) // particle has random blur effect
}
Some modifiers, like .initialPosition(x:y:)
, affect the initial behavior of an entity; while others, like .fixPosition(with:)
affect the behavior on each frame.
Like SwiftUI modifiers, most* entity modifiers are applied outside first, inside last. For instance, since .initialPosition(...)
sets a particle's position, applying this modifier above .initialOffset(...)
will cause the offset to not be applied. .initialOffset(...)
, which changes the position, must be written inside.
* Some rendering operations, like .colorOverlay(...)
or .hueRotation(...)
, follow a static ordering despite modifier ordering.
For more information on modifier parameters, see symbol documentation.
- Lifetime
.lifetime(...)
- Position and Offset
.initialPosition(...)
.initialOffset(...)
.fixPosition(...)
.zIndex(...)
- Velocity and Acceleration
.initialVelocity(...)
.fixVelocity(...)
.initialAcceleration(...)
.fixAcceleration(...)
.drag(...)
- Rotation and Torque
.initialRotation(...)
.fixRotation(...)
.initialTorque(...)
.fixTorque(...)
.rotation3D(x:y:z:)
- Effects
.opacity(...)
.blendMode(_:)
.colorOverlay(...)
.hueRotation(...)
.blur(...)
.scale(...)
.glow(...)
.shader(...)
- Transitions and Timing
.transition(_:on:duration:)
.delay(...)
- Custom Behavior
.onAppear(perform:)
.onUpdate(perform:)
ParticleSystem.debug()
- enables Debug Mode for the particle system, showing performance metricsParticleSystem.statePersistent(_:refreshesViews:)
- enables State Persistence for the particle systemParticleSystem.checksTouches(_:)
- option to disable touch updates for performanceEmitter.emitSingle(choosing:)
- instructsEmitter
to emit one particle at a timeEmitter.emitAll()
- instructsEmitter
to emit all passed particles at onceEmitter.maxSpawn(count:)
- stops emitting entities aftercount
are emittedLattice.customView(view:)
- customizes the view ofLattice
particles
When importing Particles
, you also have access to some useful view modifiers.
View.particleSystem(atop:offset:entities:)
- creates a particle system centered at the modified viewView.emits(every:if:atop:simultaneously:entities:)
- emits specific entities on an interval from the center of the modified viewView.dissolve(if:)
- dissolves the view (usingLattice
) ifcondition
is trueView.burst(if:)
- bursts the view ifcondition
is true
All modifiers are documented with parameter information.
Warning
Particles is in Pre-Release. The API for the four view modifiers listed above may be changed before release.
Some modifier parameters take in a closure of the form (Proxy.Context) -> <Type>
. Proxy.Context
provides information about the current proxy and the system it lives in.
Below is a list of properties of Proxy.Context
. All symbols are documented.
proxy: Proxy
.position: CGPoint
.velocity: CGVector
.acceleration: CGVector
.drag: Double
.rotation: Angle
.torque: Angle
.rotation3d: SIMD3<Double>
.zIndex: Int
.lifetime: Double
.seed: (Double, Double, Double, Double)
.opacity: Double
.hueRotation: Angle
.blur: CGFloat
.scale: CGSize
.blendMode: GraphicsContext.BlendMode
system: ParticleSystem.Data
.debug: Bool
.size: CGSize
.currentFrame: UInt
.lastFrameUpdate
.touches
(iOS).time: TimeInterval
.proxiesAlive
.proxiesSpawned
.averageFrameRate
ParticleSystem
has the ability to persist its simulation through View
state refreshes. To enable this functionality, provide a string tag to the ParticleSystem
:
struct MyView: View {
@State var foo: Bool = false
var body: some View {
VStack {
Button("Foo") { foo.toggle() }
ParticleSystem {
Emitter {
if foo {
Particle(view: { Text("๐") }).initialVelocity(withMagnitude: 1.0)
} else {
Particle(view: { Image(systemName: "star") }).initialVelocity(withMagnitude: 1.0)
}
}
}
.statePersistent("myEmitter")
}
}
}
State refreshing works on all levels of the particle system, even in views inside Particle { ... }
. You can also use if
/else
within ParticleSystem
, Emitter
, Group
, and any other entity built with EntityBuilder
.
A curated list of presets are available. These can be configured using parameters. Several additional presets will be added before the packages reaches a Release state.
ParticlesPresets is accepting preset submissions in the form of pull requests. Contributing guidelines
Warning
ParticlesPresets is in Pre-Release. The appearance of currently available presets are subject to change, as are their parameters. Avoid including presets in production code. More information
This package contains an example project where you can preview and tweak the library of available presets. To run it, open the example's Xcode project file located in Examples/ParticlesExample.
This package is in a pre-release state, which means certain parts of it are subject to change. The following is a roadmap to Release (and conseqeuently, a list of planned API changes):
- Streamlined demos for presets
- Several new preset entries
- View modifier improvements, like
.burst(if:)
or.emits(...)
Until full release, including Particles in production code is not recommended.
Particles can support the use of thousands of entities when compiled in Release scheme. As more modifiers and entities are added, ParticleSystem
's frame rate will lower.
Use ParticleSystem.debug()
to enable Debug Mode.
![Screenshot 2024-03-30 at 11 46 32โฏPM](https://private-user-images.githubusercontent.com/41796709/318246962-49c225d5-3000-4d8e-a012-19a93462cf50.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTUxMjIwMTIsIm5iZiI6MTcxNTEyMTcxMiwicGF0aCI6Ii80MTc5NjcwOS8zMTgyNDY5NjItNDljMjI1ZDUtMzAwMC00ZDhlLWEwMTItMTlhOTM0NjJjZjUwLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNTA3VDIyNDE1MlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTZkNTFiYWUwMGQ0NjQ4YzlkMzI5NjdhZjBhNzgxYWZiNTIwNDc2Mjc0MGRjNDEwNzc3MGJhM2UxZDNhOGJiMjQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.toCFQLWhzwWfdA5YO4E9Tf-QT56psxJ9yPpbp9IDKko)
You can debug a ParticleSystem
to view performance statistics, including view size, frame rate, proxy count, entity count, registered view count, proxy update time, and rendering time.
ParticleSystem
targets 60 FPS, meaning each frame must update in 1.0 / 60.0 = 0.01666..
seconds, or 16.66ms. Each frame update is roughly equal to the max of proxy update time and rendering time. If this exceeds 16ms, frames will begin to drop.
- Particles runs faster in Release compile scheme as compared to Debug.
- If you are using many entity modifiers, consider combining behavior into a single closure using
.onAppear(...)
or.onUpdate(...)
. - Modifiers with custom closures containing expensive operations will increase proxy update time.
- An increased amount of effect modifiers, like
.glow(...)
or.blur(...)
, will increase rendering time. - Use
ForEach(merges: .views)
if the view passed toParticle
is the same across ForEach's data mapping. - Use
ForEach(merges: .entities)
if mapped entities only variate in their initial properties.merges: .entities
tellsForEach
(akaGroup
) to endow each createdProxy
with properties determined by the mappedEntity
only upon birth. After the proxy is born with its initial properties, like position, rotation, or hue rotation, it's entity rules are merged to the first data mapping's upon update. - By default,
ParticleSystem
has thechecksTouches
property set to true. Set.checksTouches(false)
to improve performance.
Performance Benchmarks will be available when this packages reaches a Release state.