3 Things About ScrollView You Should Know in SwiftUI

Introduction

ScrollView is a scrollable view in SwiftUI. ScrollView can scroll horizontally, vertically, or both, but does not provide zooming functionality. In addition, many functions are used differently from UIScrollView. Three common functions in iOS.

1.   Disable Scroll Bounces

There are exceptions to everything. There is no exception to scroll bounces. Sometimes, developers want to disable scroll bounces for reasons. Then developers should set UIScrollView.appearance().bounces to false in the constructor of the view, which uses ScrollView.

import SwiftUI

struct ContentView: View {
    
    init() {
        UIScrollView.appearance().bounces = false
    }
    
    var body: some View {
        ScrollView {
            Text("Hello, world!")
                .padding()
        }
    }
}

However, there is a disadvantage that UIScrollView.appearance().bounces is a global setting. In other words, it can be overridden by any other views. Therefore, developers should think carefully about when and where they should change the scroll bounce settings.

2. Programmatic scrolling by ScrollViewReader

Developers also control scroll programmatically for reasons. For example, a floating menu always highlights the current section when readers scroll through the long story, and user can scroll to his favorite section by clicking the section name on the floating menu. Developers can control the scroll by inserting a ScrollViewReader as a child of the ScrollView they want to control. Next, mark controls in the ScrollView by the id modifier. Finally, developers can call scrollTo(_:anchor:) to make the ScrollView scrolling to the control immediately. Reference the code below, each button (except several leading buttons, because ScrollView does not allow leading space, but why ScrollView allows trailing space? I am confused.) scrolls to the center of the screen when it is clicked.

struct ContentView: View {
    var body: some View {
        ScrollView {
            ScrollViewReader { proxy in
                ForEach(0..<100) { i in
                    Button("No. \(i)") {
                        proxy.scrollTo(i, anchor: .center)
                    }.id(i)
                    .frame(maxWidth: .infinity)
                }
            }
        }
    }
}

Also, wrapping scrollTo(_:anchor:) with the withAnimation makes the transition animated.

withAnimation {
    proxy.scrollTo(i, anchor: .center)
}

The screenshot below can help you know how it works clearly.

Screenshot with animation

3. Get the Content Offset (Scroll Position)

Developers often need to precisely control animations based on scroll position and speed. A more common example, when the user scrolls down, the navigation bar needs to be narrowed or even hidden to provide more space for the content display area.

Firstly, define a struct named OffsetPreferenceKey that implements PerferenceKey protocol.

SwiftUI has a mechanism that allows us to “attach” some attributes to our views. These attributes are called Preferences, and these may be passed up the view hierarchy easily. There’s even the possibility of installing a callback that executes whenever these preferences change.

Inspecting the View Tree – Part 1: PreferenceKey
struct OffsetPreferenceKey: PreferenceKey {
  static var defaultValue: CGFloat = .zero
  static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {}
}

Next, get the y position of the top child of the ScrollView in the ScrollView coordinate space and attach the value to OffsetPreferenceKey.

struct ContentView: View {
    
    @State var offsetY: CGFloat = 0
    
    var body: some View {
        VStack {
            Text("Offset in Y Axis: \(offsetY)")
            ScrollView {
                Text("0")
                    .frame(maxWidth: .infinity)
                    .background(
                        GeometryReader { proxy in
                            Color.clear
                                .preference(
                                    key: OffsetPreferenceKey.self,
                                    value: proxy.frame(in: .named("scroll")).minY
                                )
                        })
                ForEach(1..<100) { i in
                    Text("\(i)")
                }
            }
            .background(Color.red)
            .coordinateSpace(name: "scroll")
            .onPreferenceChange(OffsetPreferenceKey.self) { value in
                offsetY = value
            }
        }
    }
}

Finally, read the offset value from OffsetPreferenceKey in the ScrollView and execute your custom code in the callback clousure.

Conclusion

In summary, we discussed how to implement three common features with ScrollView in SwiftUI, which are disable scroll bounces, programmatic scrolling by ScrollViewReader, and getting the content offset. As you have seen, SwiftUI is different from UIKit, but more practice can help you learn it quickly. Now, open your Xcode and try them out!

Also welcome to comment on this blog and share this blog to your social media or any other place.

Leave a Reply

Your email address will not be published. Required fields are marked *