4

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.

Justin A
  • 3,891
  • 2
  • 18
  • 22
  • Next should be helpful https://stackoverflow.com/a/63536007/12299030. – Asperi Jan 08 '21 at 05:29
  • 1
    Does this answer your question? [Delay a repeating animation in SwiftUI with between full autoreverse repeat cycles](https://stackoverflow.com/questions/65509795/delay-a-repeating-animation-in-swiftui-with-between-full-autoreverse-repeat-cycl) – pawello2222 Jan 08 '21 at 15:09
  • It comes close. But my images are not just scaled-up versions of each other. The heart images are a little more detailed and closely resemble actual hearts. – Justin A Jan 08 '21 at 15:12
  • 1
    Now with two possible solutions you have something to *try* at least. Please show your code and we'll try to help you fix it. – pawello2222 Jan 08 '21 at 15:47
  • @pawello2222 please take a look. I just updated my question. Also, I have added my attempt which does nothing. My goal is to loop the animating of these two particular images to simulate the heart pumping in a loop. – Justin A Jan 09 '21 at 02:09
  • Please make your attempt reproducible - what are *settings* in `.frame(width: settings.unitHeartViewImageSize, height: settings.unitHeartViewImageSize)`? – pawello2222 Jan 09 '21 at 18:06
  • @pawello2222 I have simplified it considerably. Removed all the variables. Thanks again for your patience and for taking the time to look at this. – Justin A Jan 09 '21 at 23:00
  • @JustinA I'm not sure I understand what you want to achieve but here are two tips: 1) if you want to follow with your example you can specify animations directly in the `withAnimation` block like: `withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) { ... }` 2) I still believe you can use the animation from https://stackoverflow.com/a/65511599/8697793 - just display a different image basing on the `heartState` property. – pawello2222 Jan 12 '21 at 00:24

3 Answers3

10

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.

James Castrejon
  • 567
  • 6
  • 16
0

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.

:)

Felix Uff
  • 11
  • 3
0

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

arango_86
  • 4,236
  • 4
  • 40
  • 46