As shown in the code below, my CardContentView
consists of elements arranged vertically in a VStack
. However, when I place it within an overlay, it behaves as if it were in a ZStack
. Why is this so?
import SwiftUI
struct TestView: View {
@Namespace var namespace
@State var isShowDetail = false
@State var selectedCardIndex: Int?
var body: some View {
VStack {
Rectangle()
.fill(.gray.opacity(0.05))
.ignoresSafeArea()
}
.overlay {
if let selectedCardIndex, isShowDetail {
DetailView(selectedCardIndex)
.transition(.asymmetric(insertion: .identity, removal: .offset(y: 5)))
}
}
.overlay {
CardContentView()
}
}
@ViewBuilder
func TesttView(_ index: Int) -> some View {
Rectangle()
.frame(width: .infinity, height: 100)
.foregroundColor(.red.opacity(0.3))
.overlay {
Text("\(index)")
}
}
@ViewBuilder
func DetailView(_ index: Int) -> some View {
VStack {
TesttView(index)
.matchedGeometryEffect(id: index, in: namespace)
}
}
@ViewBuilder
func CardContentView() -> some View {
VStack {
ScrollView(.vertical, showsIndicators: false) {
ForEach(0..<10) { index in
VStack {
if index == selectedCardIndex && isShowDetail {
Rectangle()
.fill(.clear)
} else {
TesttView(index)
.matchedGeometryEffect(id: index, in: namespace)
.offset(y: isShowDetail ? 1000 : 0)
.rotation3DEffect(
.init(degrees: 10),
axis: (x: 1.0, y: 0.0, z: 0.0),
anchor: .center,
anchorZ: 0.0,
perspective: 1.0
)
.onTapGesture {
selectedCardIndex = index
withAnimation(.easeInOut(duration: 5)) {
isShowDetail.toggle()
}
}
.padding()
}
}
}
}
}
.overlay {
if let selectedCardIndex, isShowDetail {
VStack {
HStack {
Image(systemName: "chevron.left")
.onTapGesture {
withAnimation {
isShowDetail.toggle()
}
}
Spacer()
Text("Detail")
.font(.system(size: 20, weight: .bold))
}
DetailView(selectedCardIndex)
Spacer()
}
}
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
When I remove CardContentView from the overlay and place it in a VStack, it returns to normal.
var body: some View {
VStack {
CardContentView()
}
.overlay {
if let selectedCardIndex, isShowDetail {
DetailView(selectedCardIndex)
.transition(.asymmetric(insertion: .identity, removal: .offset(y: 5)))
}
}
.overlay {
// CardContentView()
}
}
Your CardContentView
behaves as if it were in a ZStack
when placed within an overlay because of how SwiftUI handles the layout and layering of views.
When you use the overlay modifier, it overlays the content on top of the base view. However, in your case, you are overlaying multiple views on top of each other within the VStack
. SwiftUI uses a ZStack
internally to handle this overlaying of views. This is why you observe behavior similar to a ZStack
.
When you remove the CardContentView
from the overlay and place it in a VStack, the views are arranged one after another as expected because VStack
stacks views vertically.