0

With SwiftUI 2.0 we can:

ScrollView() {
   ScrollViewReader { proxy in
      DummyContent()
         .onTapGesture {
            proxy.scroll(to: item...)
         }
   }
}

to scroll to a particular child inside a ScrollView.

I'm playing with 'from scratch' implementation of a ScrollView without using AppKit (custom implementation for the sake of learning and extending default ScrollView)

I managed to implement scrolling and clipping content in my ScrollViewer implementation, I want to implement scroll(to:), zoom(to:) functionality via ScrollViewerProxy like Apple did it with ScrollViewProxy. I can't figure out how Apple's ScrollViewProxy is able to manipulate parent view offset?

This is sample implementation of my ScrollViewerProxy and ScrollViewerReader:

struct ScrollViewerProxy {
   @State var offset: CGSize = .zero
   
   func scroll(to origin: CGPoint) {
      let newOrigin = CGPoint(x: origin.x, y: origin.y)
      offset = CGSize(width: newOrigin.x, height: newOrigin.y)
   }
}

struct ScrollViewerReader<Content: View>: View {
   let content: (ScrollViewerProxy) -> Content
   
   @State private var offset: CGSize = .zero
   private var scrollViewerProxy = ScrollViewerProxy()
   
   init(@ViewBuilder _ content: @escaping (ScrollViewerProxy) -> Content) {
      self.content = content
   }
   
   var body: some View {
      content(scrollViewerProxy)
         .offset( ??? ) // Can't do scrollViewerProxy.offset since it won't trigger ScrollViewerReader update
   }
}

// Bellow is code from the custom ScrollView implementation, not so important, putting it here for for completeness' sake

struct ScrollViewer<Content: View>: View {
   let content: Content

   var body: some View {
      ZStack {
         GeometryReader { geometry
            content 
               .offset(offset)
               .onPreferenceChange(ContentPreferenceKey.self) { preferences in
                  guard let p = preferences.first else { fatalError() }
                  contentRect = p.frame
                  ....
               // I monitor content frame changes via PreferenceKey
            }
         }
      }
   }
}

// Usage:
ScrollViewer {
   ScrollViewerReader { proxy in
      DummyContent()
         .onTapGesture {
            let random = CGPoint(x: -Int.random(in: 0...400), y: 0)
            proxy.scroll(to: random)
         }
    }
}
Sinisa Drpa
  • 887
  • 1
  • 5
  • 16
  • I'd be nice to have an easily-copy-and-pastable [mre] to experiment with. In general, I wouldn't expect your implementation to work with a `@State` hidden inside a sub property of a view (eg what you have in `ScrollViewerProxy`) – jnpdx May 19 '21 at 16:34
  • I'll put it on Github, the code for scrolling/clipping is too big to put it here. Tried to put a minimal example, scrolling is not so important, just wondering what trick Apple applied so that ScrollViewProxy manipulates ScrollViewReader? – Sinisa Drpa May 19 '21 at 16:41
  • I assume they're using a `PreferenceKey` or something like it to pass the value up the view hierarchy. I don't think you need to show all of the scrolling code, either, but something copy-and-pastable to start with without stubbing out anything would be good. – jnpdx May 19 '21 at 16:42
  • @jnpdx I presume it also, I'm using ```PreferencKey``` for scrolling, but what's strange to me with Apple's ```ScrollViewProxy```, is that on ```ScrollViewProxy``` you call ```proxy.scroll(to: item...)``` to affect parent, which probably means ```ScrollViewProxy``` has some internal state which when changed affects ```ScrollViewReader```... – Sinisa Drpa May 19 '21 at 16:53
  • Does this answer your question https://stackoverflow.com/a/58708206/12299030? – Asperi May 19 '21 at 17:08
  • @Asperi Thanks for the pointers, in the answers from the link this one has it implemented: https://github.com/Amzd/ScrollViewProxy/blob/master/Sources/ScrollViewProxy/ScrollViewProxy.swift, but it's using NSScrollView in the background, since SwiftUI source code isn't available I guess Apple did similar trick with ```ScrollViewProxy``` – Sinisa Drpa May 19 '21 at 17:36

0 Answers0