5

I am trying to make a simple SWiftUI ScrollView where I can set and get the value of the ScrollView bounds offset via a binding. I have the following which compiles and works fine as a ScrollView but I am unable to actually set and get the offset and propagate it back to the ContentView where the ScrollView is hosted.

I have the following:

struct MyScrollView<Content>: NSViewRepresentable where Content: View {
    private var content: Content
    let offset: Binding<CGFloat>
    
    
    init(offset: Binding<CGFloat>, @ViewBuilder content: () -> Content) {
        self.content = content()
        self.offset = offset
    }


    func makeNSView(context: NSViewRepresentableContext<MyScrollView>) ->TheScrollView {
        let view = TheScrollView(offset: offset)
        
        view.hasVerticalScroller = true
        view.hasHorizontalScroller = true
    
        let document = NSHostingView(rootView: content)
        document.translatesAutoresizingMaskIntoConstraints = false
        view.documentView = document
 
        return view
    }

    
    func updateNSView(_ view: TheScrollView, context: NSViewRepresentableContext<MyScrollView>) {
    }
    
}


class TheScrollView: NSScrollView, ObservableObject{
    private var subscriptions: Set<AnyCancellable> = []
    var offset: Binding<CGFloat>
    
    init(offset: Binding<CGFloat>){
        self.offset = offset
        
        super.init(frame: .zero)

        NotificationCenter.default
            .publisher(for: NSScrollView.boundsDidChangeNotification, object: self.contentView.documentView)
            .sink() { _ in
                let view = self.contentView
                print(view.bounds.origin.y)                     // <- I do get this
                self.offset.wrappedValue = view.bounds.origin.y // This does nothing
            }
            .store(in: &subscriptions)
    }
    
    required init?(coder: NSCoder){
        fatalError("init(coder:) has not been implemented")
    }
}

MyScrollView is hosted in a contentView like this:

import SwiftUI
import Combine

struct ContentView: View{
    @State var offset: CGFloat = 10.0{
        didSet{
            print("Offset \(offset)")
        }
    }
    
    var body: some View{
        MyScrollView(offset: $offset){
            ZStack{
                Rectangle().foregroundColor(.clear).frame(width: 1200, height: 1000)
                Rectangle().foregroundColor(.blue).frame(width: 100, height: 100)
            }
        }
    }
}

As you can see the offset value is passed from the @State var into MyScollView and then into TheScrollView, which is a subclass of NSScrollView. From there I have a simple notification to get the bounds change and set the binding. However setting the binding does nothing to the actual value in the binding and it definitely doesn't propagate back to the ContentView. Also, the address of offset changes up the hierarchy so it looks like I am passing a Binding to a Binding into TheScrollView rather than the original binding, but I cant seem to fix it.

Can anyone see what I am doing wrong?

Sunderam Dubey
  • 1
  • 11
  • 20
  • 40
Peter
  • 852
  • 8
  • 16

1 Answers1

4

It is State - it is updated when is used in body, so instead use like below:

demo

struct ContentView: View{
    @State var offset: CGFloat = 10.0

    var body: some View {
         VStack {
            Text("Offset: \(offset)")          // << here !!
             MyScrollView(offset: $offset){
                    ZStack{
                         Rectangle().foregroundColor(.clear).frame(width: 1200, height: 1000)
                         Rectangle().foregroundColor(.blue).frame(width: 100, height: 100)
                    }
             }
         }
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thanks again Asperi - I would have taken a long time to figure that out. My intention with the didSet on the offset var was to send the value off elsewhere. In case anyone else is wondering how to do that there are a bunch of ways to do it, as explained here: https://stackoverflow.com/questions/56550713/how-can-i-run-an-action-when-a-state-changes – Peter Aug 04 '21 at 06:51