Dan
Dan

Reputation: 663

SwiftUI ScrollView ScrollTo Offset (NOT ID)

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

Upvotes: 3

Views: 2084

Answers (1)

mahal tertin
mahal tertin

Reputation: 3414

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.

Upvotes: 2

Related Questions