skip to Main Content

I have following problem. I want to create a vertical ScrollView with many rows. At the bottom of the view I have an info bar which appears over the scroll view because I put all the items in a ZStack. Here is my code and what it produces:

struct ProblemView: View {
    

    var body: some View {
        ZStack {
            ScrollView(.vertical, showsIndicators: true) {
                
                VStack {
                    ForEach(0..<100, id:.self) {i in
                        HStack {
                            Text("Text (i)")
                                .foregroundColor(.red)
                            Spacer()
                            Image(systemName: "plus")
                                .foregroundColor(.blue)
                        }
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding()
                        Divider()
                        
                    }
                }
            }
            
            VStack {
                Spacer()
                HStack {
                    Text("Some Info here")
                    Image(systemName: "info.circle")
                        .foregroundColor(.blue)
                }
                .padding()
                .frame(maxWidth: .infinity)
                .ignoresSafeArea()
                .background(.ultraThinMaterial)
                
            }
            
        }
    }

    
}

struct ProblemView_Previews: PreviewProvider {
    static var previews: some View {
        ProblemView()
    }
}

GIF

As you can see the drag indicator is hidden behind the info frame. Also the last item can’t be seen because it is also behind the other frame. What

I want is that the drag indicator stops at this info frame. Why am I using a ZStack and not just a VStack? I want that this opacity effect behind the info frame, you get when you scroll.

2

Answers


  1. We cannot control offset of indicator, but we can make all needed views visible by injecting last empty view with the same height (calculated dynamically) as info panel.

    Here is possible approach. Tested with Xcode 13.2 / iOS 15.2

    demo

    struct ProblemView: View {
        @State private var viewHeight = CGFloat.zero
    
    
        var body: some View {
            ZStack {
                ScrollView(.vertical, showsIndicators: true) {
    
                    VStack {
                        ForEach(0..<100, id:.self) {i in
                            HStack {
                                Text("Text (i)")
                                    .foregroundColor(.red)
                                Spacer()
                                Image(systemName: "plus")
                                    .foregroundColor(.blue)
                            }
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .padding()
                            Divider()
    
                        }
                        Color.clear
                            .frame(minHeight: viewHeight)     // << here !!
                    }
                }
    
                VStack {
                    Spacer()
                    HStack {
                        Text("Some Info here")
                        Image(systemName: "info.circle")
                            .foregroundColor(.blue)
                    }
                    .padding()
                    .frame(maxWidth: .infinity)
                    .ignoresSafeArea()
                    .background(.ultraThinMaterial)
                    .background(GeometryReader {
                        Color.clear.preference(key: ViewHeightKey.self,
                                               value: $0.frame(in: .local).size.height)
                    })
    
                }
    
            }
            .onPreferenceChange(ViewHeightKey.self) {
                self.viewHeight = $0
            }
        }
    }
    
    struct ViewHeightKey: PreferenceKey {
        static var defaultValue: CGFloat { 0 }
        static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
            value = value + nextValue()
        }
    }
    
    Login or Signup to reply.
  2. A edit on my preview post has been added and therefore I cannot edit it… I am just gonna post the answer as an other one then.

    This is the code that fixes your problem:

    import SwiftUI
    
    struct ProblemView: View {
      var body: some View {
        ScrollView {
            
            
            VStack {
                ForEach(0..<100, id:.self) {i in
                    HStack {
                        Text("Text (i)")
                          .foregroundColor(.red)
                        Spacer()
                        Image(systemName: "plus")
                          .foregroundColor(.blue)
                    }
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding()
                    Divider()
                }
          }
          .frame(maxWidth: .infinity)
        }
        .safeAreaInset(edge: .bottom) { // 👈🏻
            VStack {
                
                HStack {
                    Text("Some Info here")
                    Image(systemName: "info.circle")
                        .foregroundColor(.blue)
                }
                .padding()
                .frame(maxWidth: .infinity)
                .ignoresSafeArea()
                .background(.ultraThinMaterial)
                
            }
      }
    }
    }
    
    struct ProblemView_Previews: PreviewProvider {
        static var previews: some View {
            ProblemView()
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search