4

Here's a basic example of what doesn't work:

import SwiftUI

struct Test : View {
    @State var swapped = false

    var body: some View {
        if swapped { Color.green }
        Color.blue.tapAction {
            withAnimation { self.swapped.toggle() }
        }
        if !swapped { Color.green }
    }
}

SwiftUI has no way to figure out that I think of the first Color.green and the second Color.green as the same view, so of course the animation just fades one of them out while fading the other one in at the new location. I'm looking for the way to indicate to SwiftUI that these are the same view, and to animate it to the new location. I discovered the .id() modifier with great excitement because I believed that it would give me the effect that I want:

import SwiftUI

struct Test : View {
    @State var swapped = false

    var body: some View {
        if swapped { Color.green.id("green") }
        Color.blue.tapAction {
            withAnimation { self.swapped.toggle() }
        }
        if !swapped { Color.green.id("green") }
    }
}

This, unfortunately, does not work either. I'm unbelievably excited about SwiftUI, but it seems to me that the ability to change the structure of the view hierarchy while preserving view identity is quite important. The actual use case which prompted me to think about this is that I have a handful of views which I'm trying to create a fan animation for. The simplest way would be to have the items in a ZStack in one state so that they are all on top of each other, and then to have them in a VStack in the fanned out state, so that they're vertically spread out and all visible. Changing from a ZStack to a VStack of course counts as a change to the structure and therefore all continuity between the states is lost and everything just cross fades. Does anyone know what to do about this?

jeremyabannister
  • 3,796
  • 3
  • 16
  • 25

2 Answers2

1

You can do this with SwiftUI 2 introduced in iOS 14 / macOS 10.16:

@Namespace private var animation
…
MyView.matchedGeometryEffect(id: "myID", in: animation)
iMaddin
  • 982
  • 1
  • 14
  • 23
paxos
  • 877
  • 5
  • 11
-1

I think I have the animation part of your question down, but unfortunately, I don't think it will keep the same instance of the view. I tried taking green out into a variable to see if that works, but if I understand SwiftUI correctly, that doesn't mean the same instance of the view will be shared in two places.

What I'm doing is adding a transition when the green view is added/removed. This way, the view moves to the location of its replacement before disappearing.

struct Test : View {
    @State var swapped = false
    let green = Color.green

    var body: some View {
        HStack {
            if swapped {
                green
                    .transition(.offset(CGSize(width: 200, height: 0)))
                    .animation(.basic())
            }
            Color.blue.animation(.basic()).tapAction {
                withAnimation { self.swapped.toggle() }
            }
            if !swapped {
                green.transition(.offset(CGSize(width: -200, height: 0)))
                .animation(.basic())
            }
        }
    }
}

This solution is quick-and-dirty and uses hard-coded values based on the iPhone 6/7/8 portrait screen size

Benjamin Kindle
  • 1,736
  • 12
  • 23
  • This is not a solution to the question, this overrides SwiftUI in a very hard coded way. He wanted a way to tell SwiftUI to do it. – Gusutafu Jul 04 '19 at 07:54
  • This would not help to go from a ZStack to a VStack. Clearly he is asking for a way to use the layout to position and animate the views. – B Roy Dawson Feb 22 '20 at 18:06