3

Is it possible to SET ScrollView offset in SwiftUI?

I have made a custom tab bar that uses a Switch/Case to change views. However my views all contain vertical ScrollViews. I understand that each time I switch between Views they are destroyed, and thus the scrollView offset is lost.

I have used the following approach to GET ScrollView Offset, however I am now not sure how I can use this information. I have seen there is now ScrollTo but this seems to only work with an ID.

Is it possible to use ScrollTo with an Offset in some way?

In general, what I'm trying to achieve is standard Tab bar behaviour where a user returns to the same position they left each Tab

Any help is appreciated. Also, please let me know if this is bad for performance as I am a novice. Thanks.

private struct ScrollViewOffsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGPoint = .zero
    static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { }
}

struct ScrollViewWithOffset<T: View>: View {
    let axes: Axis.Set
    let showsIndicator: Bool
    let offsetChanged: (CGPoint) -> Void
    let content: T
    
    init(axes: Axis.Set = .vertical,
         showsIndicator: Bool = false,
         offsetChanged: @escaping (CGPoint) -> Void = { _ in },
         @ViewBuilder content: () -> T
    ) {
        self.axes = axes
        self.showsIndicator = showsIndicator
        self.offsetChanged = offsetChanged
        self.content = content()
    }
    
    var body: some View {
        ScrollView(axes, showsIndicators: showsIndicator) {
            GeometryReader { proxy in
                Color.clear.preference(
                    key: ScrollViewOffsetPreferenceKey.self,
                    value: proxy.frame(in: .named("scrollView")).origin
                )
            }
            .frame(width: 0, height: 0)
            content
        }
        .coordinateSpace(name: "scrollView")
        .onPreferenceChange(ScrollViewOffsetPreferenceKey.self, perform: offsetChanged)
    }
}

Used like so...

ScrollViewWithOffset { point in
    scrollViewOffset = point.y
} content: {
    ScrollViewReader { proxy in
        LazyVStack(spacing: 4) {
            ForEach(0..<10, id: \.self) { i in
                Item()
                    .id(i)
            }
        }
    }
}
4
  • 1
    You can do it with UIKit easily a simple wrapper might be worth it. Commented Oct 25, 2022 at 14:06
  • Hey, thanks for your response. Yeah, I was hoping to avoid that if possible but it makes sense. I saw another solution mentioning hiding and showing the views in a ZStack through .opacity. Would this be bad for performance? Commented Oct 25, 2022 at 15:17
  • Maybe, especially if there are many items. Commented Oct 25, 2022 at 15:39
  • You could also attempt to store all the positions of your item frames, and then use that to calculate an item to scroll to with an anchor. I have never tried it fully, but I did once have to track all the positions of the frame for some scrolling if needed logic. Commented Sep 18 at 15:30

1 Answer 1

2

iOS 18 will provide a new struct ScrollPosition.

If you target iOS 17 and earlier, you may use the offset of HStack or use an idea from Daniel Saidi which uses a GeometryReader.

Doordash has implemented a wrapper around UIScrollView to use in SwiftUI.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.