DEV Community

Diego Lavalle for Swift You and I

Posted on • Originally published at swiftui.diegolavalle.com

SwiftUI Scroll Magic

Scroll views in SwiftUI are very straight-forward but give us no information or control over the current position of its content. So how can we detect for instance that the user has scrolled beyond certain threshold off the top?

Scrolling

Suppose we have some scrollable content with a title bar that is outside the ScrollView.

public struct JumpingTitleBar: View {

  var scrollView: some View {
    ScrollView(.vertical) {
      Text("""
      Scroll down to see the title bar jump from top to bottom.
      …
      """)
    }
  }

  var topBar: some View {  }
  var bottomBar: some View {  }

  var body: some View {
    ZStack {
      scrollView
      VStack {
        topBar
        Spacer()
        bottomBar
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We want the title bar to jump to the bottom once the user started scrolling down the text. Let's start by adding an onTop state variable to represent this behavior.

@State var onTop = true

var body: some View {
  ZStack {
    scrollView
    VStack {
      if onTop { topBar }
      Spacer()
      if !onTop { bottomBar }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now for the magic bit of the article, how do we actually find out what the offset of the contents is at all times? For this we leverage .anchorPreference.

Anchor preferences let us compile geometry data about our descendants, of which our scrollable Text is part. We start by defining the key type with a default value and a reducer.

struct OffsetKey: PreferenceKey {
  static var defaultValue: CGFloat = 0
  static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
    value = nextValue()
  }
}
Enter fullscreen mode Exit fullscreen mode

From the text view itself we report our top anchor's y position to our ancestors.

  GeometryReader { g in
    ScrollView(.vertical) {
      Text()
      .anchorPreference(key: OffsetKey.self, value: .top) {
        g[$0].y
      }
      
Enter fullscreen mode Exit fullscreen mode

We will catch the offset value on the flip side using the onPreferenceChange() modifier on the scroll view itself.

var body: some View {
  ZStack {
    scrollView
    .onPreferenceChange(OffsetKey.self) {
    
    }
    
Enter fullscreen mode Exit fullscreen mode

At this point the only thing remaining is to update our internal state according to the received offset.


scrollView
.onPreferenceChange(OffsetKey.self) {
  if $0 < -10 {
    self.onTop = false
  } else {
    self.onTop = true
  }
}

Enter fullscreen mode Exit fullscreen mode

The title bar now slides away shortly after we start scrolling down and the footer instantly fades in. The whole action reverses when scrolling all the way back to the top.

For sample code featuring this and other techniques please checkout our working examples repo.

FEATURED EXAMPLE: Scroll Magic - See the title bar jump from top to bottom


Originally published at Swift You and I

Top comments (0)