skip to Main Content

In my app, I have a ScrollView that has one static subview as a header and a LazyVStack with multiple elements that is filled with data from the view model. Additionally, if this "dynamic" data is empty, instead of the LazyVStack, I show a placeholder view. Here’s a simplified structure of the view:

var body: some View {
  GeometryReader { geometry in
    ScrollView {
      HStack(spacing: .zero) {
        Text("This is my greeting to you")
        Spacer()
      }
      if greetings.isEmpty {
        PlaceholderView()
          .frame(height: geometry.size.height)
      } else {
        LazyVStack {
          ForEach(greetings, id: .self) {
            Text($0)
          }
        }
      }
    }
  }
}
private var greetings: [String]

PlaceholderView:

VStack {
  Image(systemName: "hand.wave")
  Text("Hello world!")
}

I want PlaceholderView to be displayed in the middle of the available space (i.e., in this case, the entire screen height minus the height of the HStack on the top.) Many somewhat similar questions here suggest using GeometryReader, but it doesn’t help in this case:

GeometryReader { geometry in
  ScrollView {
    HStack(spacing: .zero) {
      Text("This is my greetings to you")
      Spacer()
    }
    if greetings.isEmpty {
      ExampleView()
        .frame(height: geometry.size.height)
    } else {
      LazyVStack {
        ForEach(greetings, id: .self) {
          Text($0)
        }
      }
    }
  }
}

Using the code above, PlaceholderView takes more space than needed. In my case, it should be something like geometry.size.height - hStackHeight:

Is there a way to make PlaceholderView take exactly the screen height minus HStack‘s height? Any guidance is much appreciated.

2

Answers


  1. If you wrap PlaceholderView in a second GeometryReader then you can use it to get the vertical displacement in the global coordinate space (this being the hStackHeight you referred to). This can be subtracted from the height:

    if greetings.isEmpty {
        GeometryReader { proxy2 in
            PlaceholderView()
                .frame(height: geometry.size.height
                       - proxy2.frame(in: .global).minY)
        }
    } else {
    

    PlaceholderView

    The only thing with this solution is that the height grows when the message at the top is pushed away using a scroll action. This means the message and the placeholder don’t move in sync, which seems a bit odd. So I would probably opt for suppressing the ScrollView when there are no greetings to display, as per the answer by superpuccio.


    EDIT Finally worked it out. An additional VStack is necessary inside the ScrollView. Then the adjustment can use the coordinate space of the VStack. This resolves the issue of the non-synced scroll movement:

    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                VStack(spacing: 0) {
                    HStack(spacing: .zero) {
                        Text("This is my greeting to you")
                        Spacer()
                    }
                    if greetings.isEmpty {
                        GeometryReader { proxy2 in
                            PlaceholderView()
                                .frame(height: geometry.size.height -
                                       proxy2.frame(in: .named("VStack")).minY
                                )
                        }
                    } else {
                        LazyVStack {
                            ForEach(greetings, id: .self) {
                                Text($0)
                            }
                        }
                    }
                }
                .coordinateSpace(name: "VStack")
            }
        }
    }
    
    Login or Signup to reply.
  2. If you don’t need the placeholder to bounce up and down you can just avoid using a ScrollView in case there’s no data to show. I mean:

    struct SomeView: View {
        @State private var greetings = [String]()
    
        var body: some View {
            if greetings.isEmpty {
                VStack {
                    header
                    placeholder
                }
            } else {
                ScrollView {
                    header
                    LazyVStack {
                        ForEach(greetings, id: .self) {
                            Text($0)
                        }
                    }
                }
            }
        }
    
        @ViewBuilder
        private var placeholder: some View {
            VStack {
                Image(systemName: "hand.wave")
                Text("Hello World")
            }
            .frame(maxHeight: .infinity)
        }
    
        @ViewBuilder
        private var header: some View {
            HStack(spacing: .zero) {
                Text("This is my greeting to you")
                Spacer()
            }
        }
    }
    

    The result is:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search