WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: How can I get a child view background to extend beyond ScrollView safe area?

Forums > SwiftUI

I have a ScrollView that's full of views that each have their own unique background colour. This works great in every orientation except iPhones with FaceID in landscape orientation. In this one particular case, I can't find a way to avoid showing the ScrollView's background.

Any ideas for how I can play with safe area insets such that I can get the unique background colours to go the full width of the screen?

This is a simplified version of what I'm working with in my app.

struct ContentView: View {
    let colours = [
        Color.blue,
        Color.green,
        Color.red,
        Color.yellow,
        Color.pink,
        Color.brown,
        Color.purple
    ]

    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(0 ..< colours.count) { index in
                    Section {
                        Text("View \(index)")
                            .font(.title2)
                    } header: {
                        HStack{
                            Text("Section Header \(index)")
                                .font(.title)

                            Spacer()
                        }
                    }
                    .frame(maxWidth: .infinity)
                    .background(colours[index])
                }
            }
        }
    }
}

Things I've tried:

  • Setting ScrollView to .edgesIgnoringSafeArea(.horizontal), but then then titles get clipped by the notch.
  • Setting .background(colours[index].edgesIgnoringSafeArea(.all)), but it has no effect since it's being constrained by the ScrollView.
  • Giving ScrollView a background with a LinearGradient of all the colours, but that background size is fixed to the size of the screen, so it doesn't align with the child view colours.

Any help would be greatly appreciated!

1      

Hi Matthew

I didn't find a simple solution with a single modifier... so you could consider my solution like "cheating".

The idea is to allow the ScrollView extend over the whole screen by ignoring the safe area and then adding the safe area back to the content after drawing the background. The information how big the safe area on leading and trailing sides is I use the GeometryReaders GeometryProxy.safeAreaInsets.

I hope this solves also your problem.

Philipp

GeometryReader { proxy in
    ScrollView {
        LazyVStack {
            ForEach(0 ..< colours.count) { index in
                Section {
                    Text("View \(index)")
                        .font(.title2)
                } header: {
                    HStack{
                        Text("Section Header \(index)")
                            .font(.title)

                        Spacer()
                    }
                }
                .frame(maxWidth: .infinity)
                .padding(.horizontal, max(proxy.safeAreaInsets.leading, proxy.safeAreaInsets.trailing))
                .background(colours[index])
            }
        }
    }
    .edgesIgnoringSafeArea(.horizontal)
}

1      

Thanks Philipp! This is twice now you've swooped in with awesome solutions for me :) I really appreciate it!!

Your GeometryReader solution works great. I had been looking for something with safe area insets, but unfortunately I couldn't get the modifiers @twostraws describes here to work properly. I didn't know that GeometryReader provided that kind of info (and frankly wouldn't have thought to use it like that even if I did lol), but I think it's as elegant a solution as SwiftUI has for most things.

Before I saw your answer, I came up with a super ugly hack that kind of worked that I'll share here in case it helps someone else solve a different kind of problem. It uses GeometryReader in the background to determine if the view is in landscape, and if so, applies conditional padding.

Your solution is much cleaner and less fragile than this!

    @State private var screenSize = CGSize()

    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(0 ..< colours.count) { index in
                    Section {
                        Text("View \(index)")
                            .font(.title2)
                    } header: {
                        HStack{
                            Text("Section Header \(index)")
                                .font(.title)
                                .padding(.leading, iPhonePadding)

                            Spacer()
                        }
                    }
                    .frame(maxWidth: .infinity)
                    .background(colours[index])
                }
            }
        }
        .edgesIgnoringSafeArea(.horizontal)
        .background(
            GeometryReader { geo in
                Color.clear
                    .onAppear {
                        screenSize = geo.size
                    }
                    .onChange(of: geo.size) { newValue in
                        screenSize = newValue
                    }
            }
        )
    }

    var iPhonePadding: CGFloat {
        if UIDevice.current.userInterfaceIdiom == .phone &&
            screenSize.width > screenSize.height {
            return 50
        }

        return 0
    }

1      

Hacking with Swift is sponsored by Fernando Olivares

SPONSORED Fernando's book will guide you in fixing bugs in three real, open-source, downloadable apps from the App Store. Learn applied programming fundamentals by refactoring real code from published apps. Hacking with Swift readers get a $10 discount!

Read the book

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.