1

I'm trying to build a simple animated overlay. Ideally, the dark background fades in (which it's doing now) and the white card slides up from the bottom edge (using .transition(.move(edge: .bottom).

Here's my ContentView.swift file:

struct Overlays: View {
    
    @State var showOverlay = false
    
    var body: some View {
        NavigationView {
            Button {
                withAnimation(.spring()) {
                    showOverlay.toggle()
                }
            } label: {
                Text("Open overlay")
            }
            .navigationTitle("Overlay demo")
        }
        .overlay {
            if showOverlay {
                CustomOverlay(
                    overlayPresented: $showOverlay,
                    overlayContent: "This is a real basic overlay, and it should be sliding in from the bottom."
                )
            }
        }
    }
}

And here's my CustomOverlay.swift file:

struct CustomOverlay: View {
    
    @Binding var overlayPresented: Bool
    
    let overlayContent: String
    
    var body: some View {
        ZStack(alignment: .bottom) {
            overlayBackground
            overlayCard
        }
    }
}

extension CustomOverlay {
    
    var overlayBackground: some View {
        Color.black.opacity(0.6)
            .ignoresSafeArea(.all)
            .onTapGesture {
                withAnimation(.spring()) {
                    overlayPresented = false
                }
            }
    }

    var overlayCard: some View {
        VStack(spacing: 16) {
            overlayText
            overlayCloseButton
        }
        .padding()
        .frame(maxWidth: .infinity)
        .background(.white)
        .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
        .padding()
        .transition(.move(edge: .bottom))
    }
    
    var overlayText: some View {
        Text(overlayContent)
    }
    
    var overlayCloseButton: some View {
        Button {
            withAnimation(.spring()) {
                overlayPresented = false
            }
        } label: {
            Text("Close")
        }
    }
}

This doesn't appear to work. The entire overlay is fading in/out.

https://i.stack.imgur.com/aURFq.jpg

If I move the .transition(.move(edge: .bottom) to the CustomOverlay ZStack the entire overlay slides in from the bottom which looks super goofy.

What am I doing wrong?

ragavanmonke
  • 409
  • 3
  • 13
  • Small remark which may not be related but the overlay contents is inside a if : you could use opacity 0-1 instead of if – Ptit Xav Aug 12 '22 at 21:19

1 Answers1

3

After some more experimentation, I've found something pretty cool.

Our main ContentView.swift file:

struct Overlays: View {
    
    @State var showOverlay = false
    
    var body: some View {
        NavigationView {
            Button {
                withAnimation(.easeInOut(duration: 0.25)) {
                    showOverlay.toggle()
                }
            } label: {
                Text("Open overlay")
            }
            .navigationTitle("Overlay demo")
        }
        .overlay {
            if showOverlay {
                // Here's the overlay background, which we can animate independently
                OverlayBackground(
                    overlayPresented: $showOverlay
                )
                .transition(.opacity)
                // Explicit z-index as per https://stackoverflow.com/a/58512696/1912818
                .zIndex(0)
                
                // Here's the overlay content card, which we can animate independently too!
                OverlayContent(
                    overlayPresented: $showOverlay,
                    overlayContent: "This is a real basic overlay, and it should be sliding in from the bottom."
                )
                .transition(.move(edge: .bottom).combined(with: .opacity))
                // Explicit z-index as per https://stackoverflow.com/a/58512696/1912818
                .zIndex(1)
            }
        }
    }
}

And here's OverlayBackground.swift (the background):

struct OverlayBackground: View {
    
    @Binding var overlayPresented: Bool
    
    var body: some View {
        Color.black.opacity(0.6)
            .ignoresSafeArea(.all)
            .onTapGesture {
                withAnimation(.easeInOut(duration: 0.25)) {
                    overlayPresented = false
                }
            }
    }
}

And lastly OverlayContent.swift:

struct OverlayContent: View {
    
    @Binding var overlayPresented: Bool
    
    let overlayContent: String
    
    var body: some View {
        VStack {
            Spacer()
            overlayCard
        }
    }
}

extension OverlayContent {

    var overlayCard: some View {
        VStack(spacing: 16) {
            overlayText
            overlayCloseButton
        }
        .padding()
        .frame(maxWidth: .infinity)
        .background(.white)
        .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
        .padding()
    }
    
    var overlayText: some View {
        Text(overlayContent)
    }
    
    var overlayCloseButton: some View {
        Button {
            withAnimation(.easeInOut(duration: 0.25)) {
                overlayPresented = false
            }
        } label: {
            Text("Close")
        }
    }
}

The result: https://i.stack.imgur.com/WoAHy.jpg

ragavanmonke
  • 409
  • 3
  • 13