Created
July 7, 2020 06:16
-
-
Save DevAndArtist/e50ddb6cc157d563786657bf30a411f9 to your computer and use it in GitHub Desktop.
Revisions
-
DevAndArtist created this gist
Jul 7, 2020 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,124 @@ import Combine import SwiftUI // To re-align the cells to the center we use a workaround through debouncing // the nearest cell ID which is computed every time the scroll view moves. // // HOWEVER there is a bug that still needs to be solved: // If you drag the scroll view and hold, the debounce event will still happen // and reposition the cell. // // To solve the issue we need `isTracking` state for the scroll view, which // we'll use to filter out unwanted events. struct PickerTest: View { final class _Helper: ObservableObject { let _subject: PassthroughSubject<Int, Never> // This object should never update. let objectWillChange = Empty<Never, Never>(completeImmediately: false) var yInitial: CGFloat? let idPublisher: AnyPublisher<Int, Never> init() { let subject = PassthroughSubject<Int, Never>() self._subject = subject self.idPublisher = subject .debounce(for: 0.5, scheduler: DispatchQueue.main) .eraseToAnyPublisher() } func scrollTo(id: Int) { _subject.send(id) } } // Make sure that `_Helper` object is instantiated only once, // and its instance is reused during every `body` call. @StateObject var _helper = _Helper() func action(with proxy: ScrollViewProxy, id: Int) -> () -> Void { return { withAnimation { proxy.scrollTo(id, anchor: UnitPoint(x: 0.5, y: 0.5)) } } } var body: some View { ScrollViewReader { proxy in HStack { ScrollView .init { VStack(spacing: 0) { // top inset GeometryReader .init { proxy -> Color in // compute y offset let yGlobal = proxy.frame(in: .global).origin.y let yInitial = _helper.yInitial ?? yGlobal _helper.yInitial = yInitial let yOffset = yInitial - yGlobal // compute closest id and clamp it let id = Int((yOffset / 40).rounded()) let clampedID = min(40, max(0, id)) // forward the id to the debouncing publisher _helper.scrollTo(id: clampedID) // return a transparent view return Color.clear } .frame(width: 0, height: 120) // items LazyVStack(spacing: 0) { ForEach .init(0 ... 40, id: \.self) { id in Button { withAnimation { proxy.scrollTo(id, anchor: UnitPoint(x: 0.5, y: 0.5)) } } label: { Text("\(id)") .frame(width: 100, height: 40) .background(Color.green) .border(Color.blue, width: 1) } } .border(Color.red, width: 1) } // bottom inset Color .clear .frame(width: 0, height: 120) } } .frame(width: 200, height: 280) .border(Color.black, width: 1) .background( Color .orange .frame(width: 200, height: 40) ) .onReceive(_helper.idPublisher) { id in withAnimation { proxy.scrollTo(id, anchor: UnitPoint(x: 0.5, y: 0.5)) } } VStack { Button("scroll to 0", action: action(with: proxy, id: 0)) Button("scroll to 40", action: action(with: proxy, id: 40)) } } } } } struct PickerTest_Previews: PreviewProvider { static var previews: some View { PickerTest() } }