Home > Enterprise >  SwiftUI Canvas with 3D Transformation
SwiftUI Canvas with 3D Transformation

Time:01-07

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:

enter image description here

How to proceed:

  1. draw a white rectangle and save it as an image in your project ("confetti") enter image description here
  2. create a new file of type "SpriteKit particle file"
  3. give it these values, but you can play around with them enter image description here
  4. 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)
            }
  • Related