Reversed animation with repeatForever() - swiftui

I'm trying to implement an error badge with a pulsating circle in the background. whenever I click on it multiple time it pulses inwards rather than outwards. I'm unable to understand this behaviour. Please suggest me the correct approach.
Image(systemName: "exclamationmark")
.background(
ZStack{
Circle()
.fill(Color.red.opacity(0.3))
.frame(width: 20, height: 20)
Circle()
.fill(Color.red.opacity(0.4))
.frame(width: 20, height: 20)
.scaleEffect(carError ? 1.5 : 1)
.onTapGesture(perform: {
carError.toggle()
})
}
.animation(carError ? Animation.easeIn(duration: 0.7).repeatForever() : nil)
)
.font(.footnote)
.foregroundColor(Color("ColorError"))
.position(x: 100, y: 100)

You just need to toggle back to .default animation instead of nil
.animation(carError ? Animation.easeIn(duration: 0.7).repeatForever() : .default)

Related

How to detect whether the scrollview was scrolled or not in SwiftUI?

I have a horizontal ScrollView and would like to provide a function that when the first element is not visible anymore an arrow should appear to signalize that there are some other elements.
I've tried the DragGesture() to read the translation.width but it seems to be buggy in combination with the ScrollView.
So I'm looking for a way to detect whether the ScrollView was scrolled and how far. Is there any way?
private let gridItems = [GridItem(.flexible())]
ScrollView(.horizontal, showsIndicators: false) {
HStack{
LazyHGrid(rows: gridItems){
ForEach(viewModel.imageOptions, id: \.self){ image in
ZStack {
Image(image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
.background(Color.white)
.clipShape(Circle())
.background(
Circle()
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.4), radius: 3, x: 5.0, y: 5.0)
)
.onTapGesture {
print("selected")
}
.padding(15)
Circle()
.strokeBorder(Color.orange, lineWidth: 5)
.frame(width: 70, height: 70)
}
}
}
}
}
}

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

Clipped not actually clips the Image in SwiftUI

I am trying to clip the image and as we see the UI it looks fine but actually it doesn't clip the image which causes other UI elements to unresponsive.
Here the code I am using.
struct ImageContentView: View {
var urls:[String] = [
"https://lh3.googleusercontent.com/proxy/80im-IBfLODpLDj8d02uEpSVIhqdjen6H6CeFwgRBxeua-Dgw0R3WONFj1Gk8CwB_MufmC9rQ8qHjyWMejwFcJ1PA2s8AAu5WVsmJA=s0-d",
"https://wallpaperaccess.com/full/530919.jpg"
]
var body: some View {
ScrollView{
VStack{
Button(action: {
}, label: {
Text("Hello")
})
VStack(spacing: 20.0) {
ForEach(self.urls, id:\.self) { url in
WebImage(url: URL.init(string: url)!)
.resizable()
.aspectRatio(contentMode: ContentMode.fill)
.frame(height: UIScreen.main.bounds.size.width * 0.5)
.clipped()
.cornerRadius(10.0)
.shadow(color: Color.red, radius: 10.0, x: 0, y: 0)
}
}.padding()
}
}
}
}
Here is fixed part (tested with Xcode 12 / iOS 14)
VStack(spacing: 20.0) {
ForEach(self.urls, id:\.self) { url in
WebImage(url: URL.init(string: url)!)
.resizable()
.aspectRatio(contentMode: ContentMode.fill)
.frame(height: UIScreen.main.bounds.size.width * 0.5)
.clipped()
.cornerRadius(10.0)
.shadow(color: Color.red, radius: 10.0, x: 0, y: 0)
}.contentShape(Rectangle()) // << here !!
}.padding()
Note: I don't know what is your WebImage but with Image and local images it was reproduced as well, so fix was tested.
iOS 13 and +
An other solution is combine compositingGroup and clipped:
That wraps this view in a compositing group and clips.
Note: Respect this order compositingGroup and clipped
Image(..)
.compositingGroup()
.clipped()
I have tried for the above methods but still not working.
Here is another solution for someone who have the same struggle, maybe this can help you :) This line is to disable the tap gesture of the image.
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.contentShape(RoundedRectangle(cornerRadius: 8))
.allowsHitTesting(false) // <- this is the line

What is the purpose of .compositingGroup() in SwiftUI?

I cannot figure out what compositingGroup() is. At first, I thought it is something like Merging layers in Photoshop. But it was not. Because .shadow() effects to the overlay and background views respectively even if I use .compositingGroup().
So far, I've found 2 differences when I use .compositingGroup()
Text doesn't have shadows.
The shadow size of the overlay view is slightly smaller than the above one.
What is the purpose of compositingGroup?
struct ContentView: View {
var body: some View {
VStack(spacing: 50) {
Text("Without\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.shadow(color: .blue, radius: 5)
Text("With\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.compositingGroup() // <--- I added .compositingGroup() here.
.shadow(color: .blue, radius: 5)
}
}
}
This modifier makes the following modifiers be applied to the view as a whole and not to each particular subview separately
Here's an example to better illustrate this:
struct ContentView: View {
let circles: some View = ZStack {
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.red)
.offset(y: -25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.blue)
.offset(x: -25, y: 25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.green)
.offset(x: 25, y: 25)
}
var body: some View {
VStack(spacing: 100) {
circles
circles
.opacity(0.5)
circles
.compositingGroup()
.opacity(0.5)
}
}
}
So in your case the shadow is applied to the whole view rather than separately to the Text and overlaying RoundedRectangle
Use it when wanting to apply effects like opacity or shadow to a group of views and not each contained element by itself.
It seems like that .shadow() modifier will add both inner and outer shadow. It means that if the view is not "solid", for example, it has a "hole", .shadow() will add shadow like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.shadow(color: .blue, radius: 5)
Click to see the image
So, if you do not want the inner shadow, you need to make your view be "solid", like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.shadow(color: .blue, radius: 5)
Click to see the image
However, something goes wrong again, the inner shadow doesn't disappear.
That's because I forgot to apply the .compositingGroup() modifier.
As #ramzesenok mentioned, .compositingGroup() makes the following modifiers be applied to the view as a whole and not to each particular subview separately.
So, change the code a little bit:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.compositingGroup()
.shadow(color: .blue, radius: 5)
Click to see the image
There is only outer shadow now.

ZStack has extra space at the top

The following code show weird extra space at the top. Anyone know what's the issue?
ZStack(alignment: .top) {
Image(imageName)
.resizable()
.aspectRatio(contentMode: .fill)
.scaleEffect(imageScale)
.blur(radius: imageScale > 1 ? 5 : 0)
.animation(.easeOut)
}
.frame(width: selected ? UIScreen.main.bounds.width : width ,
height: selected ? UIScreen.main.bounds.height : height)
.cornerRadius(30)
.shadow(color: Color.black.opacity(0.3), radius: 15, x: 0, y: 20)
I just figured out the solution. It's .cornerRadius(30) that cause the problem. Adding .edgesIgnoringSafeArea(.all) will not fix the issue unless you remove .cornerRadius(30). I hope this answer help someone else.