Skip to content

SwiftUI ScrollView monitoring content position and adding a 'SnapTo' alignment

License

Notifications You must be signed in to change notification settings

supervisor194/ScrollViewTest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ScrollViewTest

Copyright (c) 2022 Solver7 Corporation

SwiftUI ScrollView monitoring content position and adding a 'SnapTo' alignment

This example was to investigate how to build a ScrollView that could track its content's position and 'snap' the contents on a fixed interval or window of size N. If we have 100 items to scroll and a window size of 5, we want to see 5 items at a time with the leading item being i % N = 1. There is always a model.selection as it is a @Published var selection: Int. One could easily make this optional with a few changes.

There are many good examples of building a PreferencKey to track child View geometries. The one used here is very similar. The change events are managed with a Publisher and debounce of .2 seconds. This allows us to pause before snaping the alignment.

self.originPublisher = self.origin
           .debounce(for: .seconds(0.2), scheduler: DispatchQueue.main)
           .dropFirst()
           .eraseToAnyPublisher()

The View containing the ScrollView utlizes Task with async/await to compute the alignments and keeps commands on the @MainActor to perform the proxy.scrollTo(...) and be sure that we have 1) the proper leading item and 2) the following N-1 items visible before passing control back to the app logic. In the View:

.onChange(of: model.selected) { v in
    Task.detached {
        while await model.notShowing() {
            async let showing = model.doScrollSnap(proxy)
            if await showing {
                break
            }
            try? await Task.sleep(nanoseconds: 100000000)
        }
    }
}

And in the model:

@MainActor
func setupSubscription(_ proxy: ScrollViewProxy) {
    subscription = originPublisher.sink { [unowned self] v in
        let target = self.snap()
        withAnimation {
            proxy.scrollTo(target, anchor: .leading)
        }
        let mod = selected % N
        let pos = mod == 0 ? N - 1 : mod - 1
        if selected != target + pos {
            selected = target + pos
        }
    }
}

Some onAppear and onDisappear are used to activate observation (subscription to preference change events) and to update a model.visibleItems : [Int:Bool].

Code at github

About

SwiftUI ScrollView monitoring content position and adding a 'SnapTo' alignment

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages