swiftuivstackzstack

SwiftUI: Arrange middle layer in ZStack dependent on top layer


I would like to implement a UI, which has

  1. a background image, which is fullscreen and scales with the device size
  2. a gradient above the background image and below the main UI, which is aligned bottom and it's size is dependent of the positioning of one element in the main UI
  3. the main UI with multiple VStacks of interface elements. There is a top area and a bottom area which are both fix aligned and sized, and a variable spacer in between

This mockup shows the intended arrangement with a 3 layer ZStack (from left to right): view mockup

This is what I got in SwiftUI:

struct TestView: View {
    
    var body: some View {
        ZStack() {
            // BG Image
            Image("BgImg")
                .resizable()
                .scaledToFill()
                .frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.width)
            
            // Gradient
            VStack {
                Spacer()
                Rectangle()
                    .fill(
                        LinearGradient(gradient: Gradient(stops: [
                            Gradient.Stop(color: Color(.black).opacity(0.0), location: 0),
                            Gradient.Stop(color: Color(.black), location: 0.5),
                        ]), startPoint: .top, endPoint: .bottom)
                    )
                    // HOW TO GET THE RIGHT SIZE HERE?
                    .frame(height:500)
            }
            
            // Main UI
            VStack {
                // VStack 1
                VStack {
                    Text("ABC")
                    Text("DEF")
                }.frame(height:260)
                
                // Fixed spacer
                Spacer().frame(height:26)
                
                // VStack 2
                VStack {
                    Text("123").foregroundColor(.white)
                    Text("456").foregroundColor(.white)
                }.frame(height:162)
                
                // Variable spacer
                Spacer()
                
                // VStack 2
                VStack {
                    Text("!!!").foregroundColor(.white)
                    Text("???").foregroundColor(.white)
                }.frame(height:304)
            }
            // Fixed spacer top
            .padding(.top, 26)
            // Fixed spacer bottom
            .padding(.bottom, 32)
            
        }
        .edgesIgnoringSafeArea(.all)
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
            .previewDevice("iPhone 13 Pro")
            .preferredColorScheme(.light)
    }
}

My questions are:

  1. How can I size the gradient the right way?
  2. Why does the gradient now align to the bottom, and why does the background image not scale to fullscreen, see bottom of the preview screenshot

Xcode preview


Solution

  • For the Image not taking the fullscreen, you need to let that Image ignore the safe area, not just the ZStack;

    You need to read the height up till the end of the stack using a GeometryReader then assign it to a value used to size the gradient.