Swiftui Multiple Animations for Same Element

SwiftUI synchronizing multiple SwiftUI animations with delays

The fix for the out of sync views is to put them all in one view and that view syncs everything simultaneously. It took me some time to work out the pattern, but here it is:

struct MyRepeatingItemView: View {

@State var isTranslated = false

var body: some View {
ZStack {
Rectangle()
.fill(Color.red)
.frame(width: 256, height: 256)
.opacity(isTranslated ? 0 : 0.25)
.scaleEffect(isTranslated ? 1 : 0.75)
Rectangle()
.fill(Color.red)
.frame(width: 256, height: 256)
.opacity(0.25)
.scaleEffect(isTranslated ? 0.75 : 0.5)
Rectangle()
.fill(Color.red)
.frame(width: 256, height: 256)
.opacity(0.25)
.scaleEffect(isTranslated ? 0.5 : 0.25)
Rectangle()
.fill(Color.red)
.frame(width: 256, height: 256)
.opacity(0.25)
.scaleEffect(isTranslated ? 0.25 : 0)
}
.onAppear {
withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
isTranslated.toggle()
}
}
}
}

struct MyRepeatingContainerView: View {

var body: some View {
ZStack {
Color.blue
.frame(width: 256, height: 2)
Color.blue
.frame(width: 2, height: 256)

MyRepeatingItemView()
}
}
}

Swift iOS multiple successive multiple animations with same UIView?

i think the error is that you mix here 2 things:

  • UIView.animate -> animate controls and properties
  • UIImageView.startAnimating -> start a loop of images in an UIImageView

but they don't do the same they are very independent. but UIView animation is normally for an other use case. only one thing is that they maybe have the same duration like your UIImageView animation, but you don't set the duration of your UIImageView. maybe when you set the animation duration of your image view to the duration of UIView animation then it is done on the same time range.

myImageView.animationDuration = 2;

and for the second loop

myImageView.animationDuration = 4;

Other Solutions

the thing is you need to know when an image loop ist completed. but there is no event for this (i did not found any)

there are some solutions on StackOverflow for this:

1 performSelector afterDelay

Set a timer to fire after the duration is done. for this you need also to add an animation duration to your image view:

myImageView.animationDuration = 0.7;

solution from here: how to do imageView.startAnimating() with completion in swift?:

When the button is pressed you can call a function with a delay

self.performSelector("afterAnimation", withObject: nil, afterDelay: imageView1.animationDuration)

Then stop the animation and add the last image of imageArray to the imageView in the afterAnimation function

func afterAnimation() {
imageView1.stopAnimating()
imageView1.image = imageArray.last
}

2 Timer

this is similar to performSelector afterDelay but with NSTimer.

you find a description here UIImageView startAnimating: How do you get it to stop on the last image in the series? :

1) Set the animation images property and start the animation (as in
your code in the question)

2) Schedule an NSTimer to go off in 'animationDuration' seconds time

3) When the timer goes off, [...]

add to point 3: then start the next animation

3 CATransaction

solution from here: Cocoa animationImages finish detection (you need to convert it to swift 3 syntax but it can be a starting point

Very old question, but I needed a fix now, this works for me:

[CATransaction begin];
[CATransaction setCompletionBlock:^{
DLog(@"Animation did finish.");
}];

UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.window.bounds];
imageView.animationDuration = 0.3 * 4.0;
imageView.animationImages = @[[UIImage AHZImageNamed:@"Default2"],
[UIImage AHZImageNamed:@"Default3"],
[UIImage AHZImageNamed:@"Default4"],
[UIImage AHZImageNamed:@"Default5"]];
imageView.animationRepeatCount = 1;
[self.window addSubview:imageView];

[imageView startAnimating];

[CATransaction commit];

4 Offtopic: Manual do the Image Animation

thats a little offtopic, because here they do the image animation manually. for your use case you just change the logic which image from index is visible. count forward until last image, then backwards until first image. and then stop the loop. not nice solution but in this solution is added a image transition animation:

Solution from here: Adding Image Transition Animation in Swift

class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!

let images = [
UIImage(named: "brooklyn-bridge.jpg")!,
UIImage(named: "grand-central-terminal.jpg")!,
UIImage(named: "new-york-city.jpg"),
UIImage(named: "one-world-trade-center.jpg")!,
UIImage(named: "rain.jpg")!,
UIImage(named: "wall-street.jpg")!]
var index = 0
let animationDuration: NSTimeInterval = 0.25
let switchingInterval: NSTimeInterval = 3

override func viewDidLoad() {
super.viewDidLoad()

imageView.image = images[index++]
animateImageView()
}

func animateImageView() {
CATransaction.begin()

CATransaction.setAnimationDuration(animationDuration)
CATransaction.setCompletionBlock {
let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(self.switchingInterval * NSTimeInterval(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_main_queue()) {
self.animateImageView()
}
}

let transition = CATransition()
transition.type = kCATransitionFade
/*
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromRight
*/
imageView.layer.addAnimation(transition, forKey: kCATransition)
imageView.image = images[index]

CATransaction.commit()

index = index < images.count - 1 ? index + 1 : 0
}
}

Implement it as a custom image view would be better.

Animations higher in the SwiftUI view hierarchy overriding nested animations

You need to link your internal animation to specific state variable, then it will be independent.

Tested with Xcode 12 / iOS 14

  var body: some View {
Text("Example")
.opacity(opacity)
.animation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true),
value: opacity) // << here !!
.onAppear() {
self.opacity = 1
}
}

How to get different components to have different animations?

Transition works when specific view appeared, but in your code ZStack loads views and then appears as a whole.

Here is a fix:

    HStack() {
ZStack() {
if animate { // << here !!

Rectangle()
.frame(height: 50)
.transition(.slide)
Image(systemName: "figure.mixed.cardio")
.foregroundColor(.white)
.transition(.scale)
}

}
}

SwiftUI - animation determined by multiple properties doesn't animate, or updates weirdly

The problem is in the value of animation(_:value:). From the documentation:

A value to monitor for changes.

You're only passing in the first property toggle1IsOn, so when the other ones are changed, the animation doesn't react. Instead, you should pass in the condition that causes the green to be shown:

value: toggle1IsOn && toggle2IsOn && toggle3IsOn

As long as value conforms to Equatable it's fine. You can also move it into a separate property, shouldFlash, so you don't repeat code.

struct ContentView: View {
@State var toggle1IsOn = false
@State var toggle2IsOn = false
@State var toggle3IsOn = false

var body: some View {

/// accumulate into 1 constant
let shouldFlash = toggle1IsOn && toggle2IsOn && toggle3IsOn

VStack {
Toggle("Toggle 1", isOn: $toggle1IsOn)
Toggle("Toggle 2", isOn: $toggle2IsOn)
Toggle("Toggle 3", isOn: $toggle3IsOn)

Rectangle()
.fill(
shouldFlash
? Color.green
: Color.gray
)
.animation(
shouldFlash
? .default.repeatForever(autoreverses: true)
: .default,
value: shouldFlash /// here!
)
}
}
}

Result:

Animation runs without issues

SwiftUI multiple vertical lists row animations not working

You can enable the animations on the List view:

List(visitManager.inProgress) { $call in
CallView(call: $call)
}
.animation(.default)

Or wrap the changes in a withAnimation block:

.onTapGesture { withAnimation { call.state = .accepted } }

As for the animation between the columns, you can get something like that with .matchedGeometryEffect. afaik it will always look a bit crumbly between List, to make it look good you need to use a VStack (but then loose all the comfort of the List view). For example:

import SwiftUI

extension Call {
enum State: Equatable, CaseIterable {
case inProgress
case accepted
case rejected
}
}

class CallManager: ObservableObject {
@Published var calls: [Call]
init(visits: [Call] = []) { calls = visits }
}

struct Call: Identifiable & Equatable & Hashable {
let id = UUID()
var state: State
}

struct ContentView: View {
@Namespace var items

@StateObject var visitManager = CallManager(visits: [
Call(state: .inProgress),
Call(state: .accepted),
Call(state: .accepted),
Call(state: .inProgress),
Call(state: .inProgress),
Call(state: .rejected),
])

var body: some View {
HStack(alignment: .top) {
ForEach(Call.State.allCases, id: \.self) { state in
VStack {
List(visitManager.calls.filter { $0.state == state }) { call in
CallView(call: call)
.id(call.id)
.matchedGeometryEffect(id: call.id, in: items)
.onTapGesture {
if let idx = visitManager.calls.firstIndex(where: { $0.id == call.id }) {
withAnimation {
visitManager.calls[idx].state = .rejected
}
}
}
}
}
}
}
}
}

struct CallView: View & Identifiable {
var call: Call
var id: UUID { call.id }

var body: some View {
Text(call.id.uuidString.prefix(15))
.foregroundColor(call.state.color)
}
}

extension Call.State {
var color: Color {
switch self {
case .inProgress: return .blue
case .rejected: return .red
case .accepted: return .green
}
}
}

A single component's animation is messing up the whole view — SwiftUI

You need to remove the animation(nil) in the AvatarComponent

You also need to change all .frame(width: 352) to .frame(width: UIScreen.main.bounds.size.width)



Related Topics



Leave a reply



Submit