How do I create a looping heartbeat animation in swiftUI? - 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.

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.

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.
:)

Related

SwiftUI - Sheet shows Data half way down the sheet

I am building a small APP where I wanna present a Sheet when a Button is pressed, it all works fine, but when the Sheet is presented the Data starts half way down the Screen rather than at the Top. I am using a ScrollView in the Sheet and above the ScrollView I have a Text and an Image which is Static.
I tried pretty much everything I could find and think of without any success. So now I am hoping someone out there can help me with this.
Here is what it looks like:
Here is the start of the code:
import SwiftUI
struct PopupView: View {
#State var painted = CGFloat(10)
#State var unpainted = CGFloat(1)
#State var sPainted = CGFloat(5)
#State var sUnpainted = CGFloat(6)
var body: some View {
Color.white
VStack(alignment: .leading, spacing: 0) {
HStack {
Text("T'IS IS STATIC HEADER TXT")
.tracking(2)
.foregroundColor(Color.euGray)
.fontWeight(.bold)
.font(.system(size: 12))
.padding(.leading, 100)
.padding(.trailing, 20)
Image("close")
.frame(width: 35, height: 35, alignment: .trailing)
}
}.frame(width: 400, alignment: .topLeading)
ScrollView(showsIndicators: false) {
VStack(alignment: .center) {
Image("qr")
.resizable()
.clipped()
.padding(.top)
.frame(width: 320, height: 320, alignment: .center)
.padding(.top, 5)
Text("JOHN APPLESEED")
.foregroundColor(Color.nameGray)
.fontWeight(.heavy)
.font(Font.custom("Source Sans Pro" ,size: 27))
.padding(.top, 10)
Text("01.01.1976")
.foregroundColor(.gray)
.fontWeight(.regular)
.font(.system(size: 13))
}.frame(width: 320)

SwiftUI MagnificationGesture() delay

I have a standart app with a view, which you can scale in/out by pinch gesture.
It's work, but I have a little delay at first, it look like jumpy zoom. Does anybody know a solution to make it work smoother ?
Example of code here:
VStack {
Image("image")
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: 200)
.scaleEffect(scale)
.gesture(MagnificationGesture()
.updating($scale, body: { (value, scale, trans) in
scale = value.magnitude
})
)
}
In order to solve the "jumpy zoom" you only need to add an animation.
Maybe something like this for example :
Image("image")
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: 200)
.scaleEffect(scale)
.gesture(MagnificationGesture()
.updating($scale, body: { (value, scale, trans) in
scale = value.magnitude
})
)
.animation(Animation.easeInOut(duration: 2.0), value: scale)// Animation to solve the "jumpy zoom"

How to position plus sign right in the middle of circle

i trying to position plus sign in the middle of circle, but it doesnt work properly and goes a little bit lower
.navigationBarItems(trailing: Button(action: {print("add")}, label: {
Circle()
.accentColor(.green)
.frame(width: 30, height: 30, alignment: .center)
.overlay(Text("+").accentColor(.white), alignment: .center)
}))
use Image(systemName: "plus").foregroundColor(.white) instead of Text("+")
In text "+" symbol doesn't have to be in the middle of the view because of text layout.
SFSymbols are more convenient in this regard. Also you can specify size with .font(.system(size: 10))
Try this button. Here size is the same as yours, but the tappable area is larger.
struct YourReusableButton: View {
var action: (() -> Void)? = nil
var body: some View {
Button(action: buttonAction) {
ZStack {
Circle()
.frame(width: 30, height: 30)
.foregroundColor(.green)
Image(systemName: "plus")
.foregroundColor(.white)
.imageScale(.small)
.frame(width: 44, height: 44)
}
}.padding()
}
func buttonAction() { action?() }
}
you can use offset to adjust the text position, like this:
Text("+").offset(x: -1, y: -2)
You can use Text's baselineOffset modifier to adjust the text vertical offset. But that's not a good idea.
You should use an image object such as SFSymbol image or PNG image.

Why does putting an SF symbol in a UIImage and then in a SwiftUI Image View result in a blurry image?

I'm trying to write a public init that takes a UIImage, but I want to allow both a normal, actual UIImage made from say a photo (or otherwise drawn) OR use the SF Symbols, but I am stumped as to why the SF Symbols are blurred. What am I missing?
Here is the code I've got:
struct TempUIImageView: View {
var defaultImage: UIImage = UIImage(systemName: "globe")!
var defaultImageString: String = "globe"
var body: some View {
VStack {
Image(uiImage: defaultImage)
.resizable()
.scaledToFill()
.aspectRatio(contentMode: .fit)
Image(systemName: defaultImageString)
.resizable()
.scaledToFill()
.aspectRatio(contentMode: .fit)
}
.padding()
}
}
i'm hoping to use a solution in this:
#State private var displayedImage: UIImage?
...
private var displayImage: some View {
Image(uiImage: displayedImage!)
.resizable()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.scaledToFill()
.aspectRatio(contentMode: .fit)
.clipShape(Circle())
.shadow(radius: 4)
}
But displayedImage could be a UIImage OR an SF Symbol.
(The forced unwrap is dealt with in other code, but this is preliminary code anyways)
When you use Image(systemName:) the system draws to the screen using vector graphics, resulting in sharp, un-blurry renderings.
However, UIImage, including, it seems UIImage(systemName:) is a bitmapped image, at a set resolution. If you scale it up (as you're doing in your second block of code), you'll get blurriness, because you're not getting the benefits of the vector graphics.
Using a pattern like the following would allow you to conditionally display a Image(uiImage:) or Image(systemName:) and still use the same set of modifiers for each:
struct ContentView: View {
#State var displayedImage : UIImage?
var imageToDisplay: Image {
if let displayedImage = displayedImage {
return Image(uiImage: displayedImage)
} else {
return Image(systemName: "camera.circle")
}
}
var body: some View {
imageToDisplay
.resizable()
.scaledToFill()
.aspectRatio(contentMode: .fit)
.frame(width: 400, height: 400)
}
}

Race condition when using List with async-loaded rows and laid out with GeometryReader

This is a follow-up question from Content hugging priority behaviour in SwiftUI.
I have a List with async-loaded images for each row, which has its height set using a GeometryReader. Full code here:
struct CountryCell: View {
let country: Country
#State var childSize: CGSize = .init(width: 0, height: 50)
var body: some View {
HStack {
AsyncImage(url: Endpoints.flag(countryCode: country.flagCode).url, placeholder: Image("flag"))
.aspectRatio(contentMode: .fit)
.frame(width: DeviceMetrics.size.width * 0.25, height: self.childSize.height)
VStack(alignment: .leading, spacing: 5) {
Text("Country: ").bold() + Text(self.country.name)
Text("Capital: ").bold() + Text(self.country.capital)
Text("Currency: ").bold() + Text(self.country.currency)
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.background(
GeometryReader { proxy -> AnyView in
DispatchQueue.main.async {
self.childSize = proxy.size
}
return AnyView(Color.clear)
})
}
}
}
Run it, the images won't replace the placeholder (maybe 1 in 10 will randomly show up), although the network requests are made. I can't figure it out, but have a hunch it's a race condition during triggering layout by the GeometryReader and the AsyncImage. If you replace:
.frame(width: DeviceMetrics.size.width * 0.25, height: self.childSize.height)
with:
.frame(width: DeviceMetrics.size.width * 0.25)
then the images will show up correctly. Similarly, if you comment out the GeometryReader, things will start to work too.
Any hints would be much appreciated.