Search code examples
swiftuilayout

Scroll view naturally going beyond safe area?


I was expecting the scroll view to honour the safe area. Why wouldn't it here? And what is scrollClipDisabled ?

enter image description here

struct ContentView: View {
    
    var body: some View {
        ZStack {
            Color.red
            PrimaryView()
                .scrollClipDisabled(false)
        }
    }
}

struct PrimaryView: View {
    
    var body: some View {
        ScrollView(.horizontal) {
            HStack {
                ForEach(0...100, id: \.self) { _ in
                    hello
                }
            }
            .padding()
        }
    }
    
    private var hello: some View {
        HStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
    }
}

Solution

  • I agree that it is strange that the ScrollView seems determined to ignore the safe area edges, even when you set .scrollClipDisabled(false) as you are doing. I find the double-negative labelling a bit confusing too: (clip disabled == false) means (clip enabled == true).

    If you add a border to the ScrollView, you will see that the frame is observing the safe area edges, but the scrollable content is not:

    ScrollView(.horizontal) {
        // ...
    }
    .border(.yellow, width: 2)
    

    Screenshot

    One workaround is to add a .clipShape to the ScrollView:

    PrimaryView()
        .scrollClipDisabled(false)
        .clipShape(Rectangle())
    

    However, using this approach, the scroll indicator disappears off to the sides.

    If the scroll indicator needs to be visible, as it is in your example, then another workaround is to add nominal horizontal padding. This breaks the contact with the safe area edges, so the edges are no longer ignored. I found that 0.1 was not enough, but 0.2 seems to work:

    PrimaryView()
        .scrollClipDisabled(false)
        .padding(.horizontal, 0.2)
    

    Screenshot