loading...
Swift You and I

SwiftUI Scroll Magic

diegolavalle profile image Diego Lavalle Originally published at swiftui.diegolavalle.com ・2 min read

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
      }
    }
  }
}

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 }
    }
  }
}

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()
  }
}

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
      }
      

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) {
    
    }
    

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
  }
}

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

Posted on by:

diegolavalle profile

Diego Lavalle

@diegolavalle

Mad computer scientist. Builder of apps. Apple Frameworks, Web Standards, Swift, Swift UI, Advanced Javascript, React, React Native, Node.

Swift You and I

Swift You and I is a publication centered around SwiftUI, Combine and related Apple frameworks. Official app (https://swiftui.diegolavalle.com/app) with interactive examples available on the App Store.

Discussion

pic
Editor guide