Justin A
Justin A

Reputation: 4731

How do I create a looping heartbeat animation in swiftUI?

I am trying to create an animation where a small heart icon is pumping. I have the two images I believe are sufficient to create the effect, but I have no idea how to create this animation effect. I have tried several things and none of them seem to work.

Any help you can offer will be greatly appreciated.

Here's what I have so far:

@State var show : Bool = false
    var body: some View {
        VStack(alignment: .trailing){
            ZStack{
                BlackView()
                if(show){
                    Image("heartOrgan1")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 50, height:50)
                        .hidden()
                        
                    Image("heartOrgan")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 50, height: 50)
                    
                } else {
                    Image("heartOrgan1")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 50, height: 50)
                        
                    Image("heartOrgan")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 50, height: 50)
                        .hidden()
                }
            }
            .onAppear(){
                
                withAnimation { self.show.toggle()}
            }
        }
    }

The general idea is to loop the switching between two heart images that represent a heart beating. I am interested in using these particular heart images as they look like actual hearts, and I like that.

Upvotes: 4

Views: 4284

Answers (3)

arango_86
arango_86

Reputation: 4425

Check out this code from Apple Documentation. I am pretty sure it works.

This is not using any images.

import SwiftUI

struct Heart: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        
        path.move(to: CGPoint(x: rect.midX, y: rect.maxY ))
        
        path.addCurve(to: CGPoint(x: rect.minX, y: rect.height/4),
                      control1:CGPoint(x: rect.midX, y: rect.height*3/4),
                      control2: CGPoint(x: rect.minX, y: rect.midY))
        
        path.addArc(center: CGPoint( x: rect.width/4,y: rect.height/4),
                    radius: (rect.width/4),
                    startAngle: Angle(radians: Double.pi),
                    endAngle: Angle(radians: 0),
                    clockwise: false)
        path.addArc(center: CGPoint( x: rect.width * 3/4,y: rect.height/4),
                    radius: (rect.width/4),
                    startAngle: Angle(radians: Double.pi),
                    endAngle: Angle(radians: 0),
                    clockwise: false)
        
        path.addCurve(to: CGPoint(x: rect.midX, y: rect.height),
                      control1: CGPoint(x: rect.width, y: rect.midY),
                      control2: CGPoint(x: rect.midX, y: rect.height*3/4))
        return path
    }
}

struct ResetHeart: View {
    var body: some View {
        Heart()
            .frame(width: 100, height: 100)
            .foregroundColor(.red)
            .shadow(color: .pink, radius: 10)
            .frame(width: 300, height: 300)
           
    }
}

struct PulsingHeart: View {
    @State private var heartPulse: CGFloat = 1
    var body: some View {
        Heart()
            .frame(width: 100, height: 100)
            .foregroundColor(.red)
            .scaleEffect(heartPulse)
            .shadow(color: .pink, radius: 10)
            .onAppear{
                withAnimation(.easeInOut.repeatForever(autoreverses: true)) {
                    heartPulse = 1.25 * heartPulse
                }
            }
    }
}

struct HeartPulseView: View {
    @State private var pulsing = false
    
    var body: some View {
        VStack {
            Spacer()
            ZStack {
                if pulsing {
                    PulsingHeart()
                } else {
                    ResetHeart()
                }
            }
            Spacer()
            PlayResetButton(animating: $pulsing)
        }
        .navigationTitle("Basic Animation")
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct HeartPulseView_Previews: PreviewProvider {
    static var previews: some View {
        HeartPulseView()
    }
}

Refer the following link for more examples https://developer.apple.com/tutorials/sample-apps/animatingshapes

Upvotes: 0

Felix Uff
Felix Uff

Reputation: 21

I would slightly modify the great answer provided by Jame to make the animation a little bit more realistic, just by changing the linear animation for a spring animation like this:

struct ContentView: View {

@State private var animationAmount: CGFloat = 1

var body: some View {
    ZStack {
        Color.black

        Image(systemName: "heart.fill")
            .resizable()
            .frame(width: 50, height: 50)
            .foregroundColor(.red)
            .scaleEffect(animationAmount)
            .animation(
                .spring(response: 0.2, dampingFraction: 0.3, blendDuration: 0.8) // Change this line
                .delay(0.2)
                    .repeatForever(autoreverses: true),
                value: animationAmount)
            .onAppear {
                animationAmount = 1.2
            }
    }
}

}

You can play with repetition, dampingFraction and blenDuration parameters to get a more customised animation.

:)

Upvotes: 1

James Castrejon
James Castrejon

Reputation: 645

You don't necessarily need two images for this. You can use one image and apply the scale effect on it. Make the one image scale up and add a delay to it. Then make it repeat.

Example:

@State private var animationAmount: CGFloat = 1

var body: some View {
    ZStack {
        Color.black

        Image(systemName: "heart.fill")
            .resizable()
            .frame(width: 50, height: 50)
            .foregroundColor(.red)
            .scaleEffect(animationAmount)
            .animation(
                .linear(duration: 0.1)
                    .delay(0.2)
                    .repeatForever(autoreverses: true),
                value: animationAmount)
            .onAppear {
                animationAmount = 1.2
            }
    }
}

You can also change the decimal value in inside the delay() to have different heartbeats. The heartbeat looks consistent with delays between 0.1 - 0.4.

Upvotes: 10

Related Questions