Home > Software design >  Swift UI Scroll View: Set frame for drag indicator
Swift UI Scroll View: Set frame for drag indicator

Time:02-14

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.

CodePudding user response:

It is just a re-arrangement of stack views:

Try the following: (and also remove the spacer)

import SwiftUI

struct ProblemView: View {
    var body: some View {
        VStack{
        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 {
            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()
    }
}

CodePudding user response:

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()
    }
}
  • Related