🚀

Fixing ScrollView Clipping: Allow Shadows to Overflow in SwiftUI

(Updated on )

TL;DR: In SwiftUI, ScrollView defaults to clipping content that exceeds its frame, which most noticeably cuts off shadows. In iOS 17+, using the .scrollClipDisabled() modifier is the standard solution. For legacy projects, you need to modify the underlying UIScrollView’s clipsToBounds property.

The Problem

For performance optimization and view hierarchy management, SwiftUI’s ScrollView behaves as if clipsToBounds = true by default. This means that if your child views have large shadows, glow effects, or use offset to move outside the scrolling area, these parts will be ruthlessly cut off, resulting in a UI that looks “sliced.”

Solutions

Solution 1: The Native Way (iOS 17+)

Starting with iOS 17 (watchOS 10, macOS 14), Apple introduced a dedicated modifier: scrollClipDisabled.

Swift
struct ScrollClipDisabledDemo: View {
    var body: some View {
        ScrollView {
            VStack(spacing: 40) {
                ForEach(0..<5) { i in
                    CardView(index: i)
                }
            }
            .padding()
        }
        // Key code: Disable clipping on the ScrollView
        // This allows the card's shadow to remain visible even if it extends outside the ScrollView area
        .scrollClipDisabled() 
        .padding(20) // Give the ScrollView itself some padding to demonstrate the overflow effect
    }
}

struct CardView: View {
    let index: Int
    var body: some View {
        RoundedRectangle(cornerRadius: 16)
            .fill(.blue.gradient)
            .frame(height: 100)
            // Large radius shadow, usually clipped by default
            .shadow(color: .black.opacity(0.5), radius: 20, x: 0, y: 10)
            .overlay(Text("Item \(index)").foregroundStyle(.white))
    }
}

Demo of scrollClipDisabled

Solution 2: Legacy Support (Introspect)

For projects that must support iOS 16 and below, we need to access the underlying UIScrollView instance and set clipsToBounds to false. The SwiftUI-Introspect library allows us to do this safely.

Swift
import SwiftUIIntrospect

ScrollView {
    // Content code...
}
.introspect(.scrollView, on: .iOS(.v15, .v16, .v17)) { scrollView in
    // Disable clipping to allow content to draw outside of bounds
    scrollView.clipsToBounds = false
    
    // Note: Once clipping is disabled, content might overlap the NavigationBar or TabBar
    // You may need to manually adjust zIndex or layout hierarchy
}

Considerations

  1. Hit Testing: Although scrollClipDisabled allows content to visually overflow, the interactive area (e.g., tap targets) for the overflowing parts might still be constrained by the original frame, depending on the view hierarchy.
  2. Performance Impact: Disabling clipping increases the rendering area. Be mindful of scrolling performance if your list contains many complex shadows or blur effects.

Further Reading

Related Tips

Subscribe to Fatbobman

Weekly Swift & SwiftUI highlights. Join developers.

Subscribe Now