'SwiftUI: Animate offset to slide in from off screen

I'm trying to animate in a view the bottom of its parent view. This is relatively easy to do by animating the offset, like so:

struct ContentView: View {

    @State var isShowingBanner = true

    var bannerOffset: CGFloat {
        isShowingBanner ? 0 : 60
    }

    var body: some View {
        VStack {
            VStack {
                Spacer()

                BannerView()
                    .offset(y: bannerOffset)
            }
            .border(Color.black, width: 1.0)
            .clipped()


            Spacer()

            Button("Toggle Banner") {
                withAnimation {
                    isShowingBanner.toggle()
                }
            }
        }
        .padding()
    }
}

Tapping the toggle button animates the offset

The glaringly obvious problem is that this just uses an arbitrary value for the animated offset, and this quickly breaks when considering dynamic type

the animated offset is less than the height of the view

My question is:

Is there a way to properly determine the height of BannerView to correctly adjust this animation. Or is there a better way to achieve this effect?

Thanks all



Solution 1:[1]

It can be done just with transition, like

demo

Tested with Xcode 13.3 / iOS 15.4

struct ContentView: View {

    @State var isShowingBanner = true

    var body: some View {
        VStack {
            VStack {
                Spacer()

                if isShowingBanner {
                    BannerView()
                        .transition(.move(edge: .bottom))  // << here !!
                }
            }
// >> empty container should not shrink !!
            .frame(maxWidth: .infinity, maxHeight: .infinity) 
            .border(Color.black, width: 1.0)
            .clipped()


            Spacer()

            Button("Toggle Banner") {
                withAnimation {
                    isShowingBanner.toggle()
                }
            }
        }
        .padding()
    }
}

Solution 2:[2]

If you want to determine the Height of the BannerView() You can use GeometryReader. I have created BannerView() Just for example bellow : -

   struct BannerView() : View {

        @Binding var height : CGFloat

        var body: some View {
            VStack{
               GeometryReader { proxy in
                  Rectangle().fill(.green).onAppear {
                      height = proxy.size.height
                  }
               }
            }.frame(height : 100)
        }
   }

So, the Binding value will provide height of the bannerView to your MainView(). You can use that to determine your offset.

   @State var isShowingBanner = true
   @State var offsetHeight : CGFloat = 0        

    var body: some View {
        VStack {
            VStack {
                Spacer()

                BannerView(height: $offsetHeight)
                    .offset(y: isShowingBanner ? 0 : offsetHeight)
            }
            .border(Color.black, width: 1.0)
            .clipped()


            Spacer()

            Button("Toggle Banner") {
                withAnimation {
                    isShowingBanner.toggle()
                }
            }
        }
        .padding()
    }

Hope you found this useful.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Asperi
Solution 2 Namra Parmar