0

I'm writing a SwiftUI app in which I'd like to have objects animate slowly when they change location and quickly when they disappear. I can't figure out how to achieve that.

Take this example. There are 8 slots, left to right. Each has a box that it can show in one of three "positions": ➊ top, ➋ bottom, or ➌ not at all. When a button is pressed, each box changes location in its slot, meaning that it can appear, disappear, or move (top ↔ bottom). Here's what it looks like:

Animated GIF of example

And the code that produces it:

enum Pos: Int, CaseIterable { case up = 0, down, none /// randomly return a _different_ value from the current one var newValue: Pos { Pos(rawValue: (rawValue+Int.random(in: 1...2)) % 3)!} } struct ContentView: View { /// The positions of 8 squares @State private var posArray: [Pos] = Array(repeating: Pos.none, count: 8).map{$0.newValue} var body: some View { VStack { HStack(spacing: 1) { ForEach(0 ..< posArray.count, id: \.self) { i in VStack(spacing: 0) { // Spacer block on top (if needed) if posArray[i] != .up { Color.clear.frame(width: 40, height: 40) } // Numbered block (if needed) Group { if posArray[i] != .none { Color.primary .frame(width: 40, height: 40) .overlay { Text((i+1).formatted()) .foregroundStyle(.background) } } } .transition(.move(edge: posArray[i] == .up ? .top : .bottom) .combined(with: .opacity)) // Spacer block on top (if needed) if posArray[i] != .down {Color.clear.frame(width: 40, height: 40) } } } }.padding(4).border(Color.green, width: 2) Button("Animate Change") { posArray = posArray.map{ $0.newValue } } }.padding(20) .animation(.easeOut(duration: 2), value: posArray) } } 

I want just the transitions (i.e., appear/disappear) to animate quickly with Animation.easeOut(duration:0.5) but don't know how to achieve that.

In SwiftUI one can animate different modifiers differently by nesting multiple instances of .animation(), and placing the modifiers at different levels of nesting. But this approach doesn't work with .transition. (If I place one .animation() inside the .transition and one outside, the inner animation winds up applying to everything.)

Also, I am seeking a solution that uses implicit animation. It might be possible to refactor the example here to use .withAnimation() (an explicit animation), but it would be difficult to apply that to actual app I need to create.

1
  • Ah, yes - thank you, @Sweeper! Commented 8 hours ago

1 Answer 1

0

If I understand correctly, you want the opacity change to happen quickly, but the move in/out should happen slowly.

If so... try applying an .animation modifier to the .transition:

Group { // ... content as before } .transition( .move(edge: posArray[i] == .up ? .top : .bottom) .combined(with: .opacity) .animation(.easeOut(duration: 0.5)) // 👈 added ) 

Animation


EDIT If, on the other hand, you want the opacity + move transition to happen quickly, but you want to keep the .animation modifier at the end of the code with the 2 second duration, then try adding another .animation modifier to the Group, in addition to the change above:

Group { // ... content as before } .transition( .move(edge: posArray[i] == .up ? .top : .bottom) .combined(with: .opacity) .animation(.easeOut(duration: 0.5)) ) .animation(.easeOut(duration: 0.5), value: posArray) // 👈 also added 

Animation

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.