I'm trying to copying the Colorful Confetti effect in iMessage using SwiftUI Canvas and I am wondering how to apply 3D transformation on each particle.
I have tried to add a projectionTransform in order to apply a CATransform3D
, but it rotates all the canvas, not a particular particle, which is not the effect I want.
Currently, I use the very basic ForEach(particles.indices, id: \.self)
loop to create each particle and use .rotation3DEffect
to apply that transformation, but it may result in a performance issue (so, I tried to use .drawingGroup()
).
Is there any solutions to apply 3D transformation to a particular particle in a Canvas??
My code (using ForEach
loop):
GeometryReader { proxy in
let size = proxy.size
TimelineView(.animation) { timeline in
let _: () = {
let now = timeline.date.timeIntervalSinceReferenceDate
model.update(at: now)
}()
ZStack {
ForEach(model.particles.indices, id: \.self) { index in
let particle = model.particles[index]
particle.shape
.fill(particle.color)
.rotation3DEffect(.degrees(particle.degrees), axis: (x: particle.x, y: particle.y, z: particle.z))
.frame(width: particle.frame.width, height: particle.frame.height)
.position(particle.frame.origin)
.tag(index)
}
}
.frame(width: size.width, height: size.height)
.drawingGroup()
}
.contentShape(Rectangle())
.gesture(
DragGesture(minimumDistance: 0)
.onEnded { _ in model.loadEffect(in: size) }
)
.task { model.loadEffect(in: size) }
}
CodePudding user response:
I would rather go a different way and use SpriteKit Particle Emitter
, I'm pretty sure the iMessage effect is made the same way:
How to proceed:
- draw a white rectangle and save it as an image in your project ("confetti")
- create a new file of type "SpriteKit particle file"
- give it these values, but you can play around with them
- and here is a code example integrating it in SwiftUI
import SwiftUI
import SpriteKit
class GameScene: SKScene {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: self)
if let explosion = SKEmitterNode(fileNamed: "MyParticle") {
explosion.position = location
addChild(explosion)
}
}
}
struct ContentView: View {
var scene: SKScene {
let scene = GameScene()
scene.size = CGSize(width: 400, height: 900)
scene.scaleMode = .aspectFit
return scene
}
var body: some View {
SpriteView(scene: scene)
.ignoresSafeArea()
}
}
CodePudding user response:
I'm not an expert in Canvas
, this is how far I got: Individual shapes rotate, but only 2D, maybe it helps.
There should be a .transform()
function for 3DAffineTranformRotate ...
TimelineView(.animation) { timeline in
Canvas(rendersAsynchronously: true) { context, size in
let now = timeline.date.timeIntervalSinceReferenceDate
model.update(at: now, size: size)
for index in model.particles.indices {
let particle = model.particles[index]
let p = particle.shape
.rotation(Angle(degrees: particle.degrees), anchor: .center)
// .transform(CGAffineTransform(rotationAngle: particle.degrees))
.path(in: particle.frame)
context.fill(p, with: .color(particle.color))
}
}
.frame(width: size.width, height: size.height)
}