How to extend color beyond the top edge in SwiftUI

66 Views Asked by At

I have placed a linear gradient area at the top of a scrollview as below:

struct ExperimentView: View {
  
  var body: some View {
    NavigationStack {

        
        ScrollView {
          VStack {
            LinearGradient(
              colors: [
                .blue.opacity(0.5),
                .clear
              ],
              startPoint: .top,
              endPoint: .bottom
            )
            .frame(height: 200)
            
            Text("Element")
            Text("Element")
            Text("Element")
            
          }
      }
        .ignoresSafeArea(.all)
    }
  }
}

The problem is, the scroll view can be pulled downward and reveal the background color above the top edge.

So is there a way to extend the blue color upward beyond the top edge, so when pulled downward, it still looks continuing upward?

I tried to add a solid color above the gradient header, and set and offset to the VStack inside the scrollview:

struct ExperimentView: View {
  
  let height = UIScreen.main.bounds.height
  
  var body: some View {
    NavigationStack {
      
      
      ScrollView {
        VStack(spacing:0) {
          
          Color.blue.opacity(0.5)
            .frame(height: height)
          
          LinearGradient(
            colors: [
              .blue.opacity(0.5),
              .clear
            ],
            startPoint: .top,
            endPoint: .bottom
          )
          .frame(height: 200)
          
          Text("Element")
          Text("Element")
          Text("Element")
          
        }
        .offset(y: -height)
      }
      .ignoresSafeArea(.all)
    }
  }
}

While it seems to work with pulling down, the offset would leave a space with the height of the screen at the bottom of the scrollable area, leaving a full blank page when scrolling up, rather than bouncing back when reaching the last element inside the scrollview.

So is there a better way to solve this?

2

There are 2 best solutions below

4
Benzy Neez On BEST ANSWER

You could move the gradient to the background of the ScrollView instead:

NavigationStack {
    ScrollView {
        VStack {
            Text("Element")
            Text("Element")
            Text("Element")
        }
    }
    .frame(maxWidth: .infinity)
    .background(alignment: .top) {
        LinearGradient(
            colors: [
                .blue.opacity(0.5),
                .clear
            ],
            startPoint: .top,
            endPoint: .bottom
        )
        .ignoresSafeArea()
        .frame(height: 200)
    }
}

EDIT As you point out in your comment, this makes the gradient fixed. If you want it to move with the VStack then I would suggest moving it to the background of the VStack instead. Also:

  • give the gradient more height and then apply an offset to move the extra height off-screen
  • to prevent the "join" from being noticeable, I found it works best to use color stops that start off-screen with an opaque color
  • apply top padding to either the VStack or to the first text element, to move the text down below the gradient (if required).
ScrollView {
    VStack {
        Text("Element")
        Text("Element")
        Text("Element")
    }
    .padding(.top, 200)
    .frame(maxWidth: .infinity)
    .background(alignment: .top) {
        LinearGradient(
            stops: [
                .init(color: .blue, location: 0),
                .init(color: .blue, location: 0.5),
                .init(color: .clear, location: 1),
            ],
            startPoint: .top,
            endPoint: .bottom
        )
        .frame(height: 800)
        .ignoresSafeArea()
        .offset(y: -600)
    }
}
.ignoresSafeArea(.all)

Alternatively, to build the VStack in the same way as before, the gradient could be moved to the background of a placeholder that is the first item in the VStack:

ScrollView {
    VStack {
        Color.clear
            .frame(height: 200)
            .background(alignment: .top) {
                // the LinearGradient, exactly as above
            }
        Text("Element")
        Text("Element")
        Text("Element")
    }
}
.ignoresSafeArea(.all)

1
Sweeper On

You can add some more blue color to the top of the LinearGradient by using background/overlay and offsetting the color by the height of the LinearGradient:

LinearGradient(
    colors: [
        .blue.opacity(0.5),
        .clear
    ],
    startPoint: .top,
    endPoint: .bottom
)
.frame(height: 200)
.background(alignment: .bottom) {
    Color.blue.opacity(0.5)
        .frame(height: 2000)
        .offset(y: -200)
}