Search code examples
swiftmacosscrollscrollviewswiftui

SwiftUI: How to set position of ScrollView to Top Left? (both scrolls is enabled)


ScrollView(){
//no matter
}

Position is Top Left, but content will have smaller width than I needed

(2 strings in red rectangle must be single string, so need to enable both scrolls )

enter image description here


ScrollView([Axis.Set.horizontal, Axis.Set.vertical]) {
//no matter
}

In this case ScrollView's content have correct size for me.

But position of scrollView's content is centred ( both: horizontally and vertically )

How can I change default position to Top Left in case of ScrollView configured to both scrolls?


Solution

  • Update: Xcode 13.4 / macOS 12.4

    The issue is still there, but now the solution is simpler using ScrollViewReader:

    struct TestTwoAxisScrollView: View {
        var body: some View {
            ScrollViewReader { sp in
                ScrollView([.horizontal, .vertical]) {
                    VStack {
                        ForEach(0..<100) { _ in
                            self.row()
                        }
                    }
                    .border(Color.green)
                    .id("root")
                }
                .border(Color.gray)
                .padding()
                .onAppear {
                    sp.scrollTo("root", anchor: .topLeading)
                }
            }
        }
    
        func row() -> some View {
            Text(test)
                .border(Color.red) // uncomment to see border
        }
    }
    

    Original

    Here is possible approach. Tested with Xcode 11.2 / macOS 10.15.3

    Demo:

    demo

    Code (complete testable module, borders are added for better visibility of each component):

    import SwiftUI
    
    let test = """
    <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12118" systemVersion="16A323" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    """
    
    struct ScrollViewHelper: NSViewRepresentable {
        func makeNSView(context: NSViewRepresentableContext<ScrollViewHelper>) -> NSView {
            let view = NSView(frame: .zero)
            DispatchQueue.main.async { // << must be async, so view got into view hierarchy
                view.enclosingScrollView?.contentView.scroll(to: .zero)
                view.enclosingScrollView?.reflectScrolledClipView(view.enclosingScrollView!.contentView)
            }
            return view
        }
    
        func updateNSView(_ nsView: NSView, context: NSViewRepresentableContext<ScrollViewHelper>) {
        }
    }
    
    struct TestTwoAxisScrollView: View {
        var body: some View {
            ScrollView([.horizontal, .vertical]) {
                VStack {
                    ForEach(0..<100) { _ in
                        self.row()
                    }
                }
                .background(ScrollViewHelper()) // << active part !!
                .border(Color.green) // uncomment to see border
            }
            .border(Color.gray)
            .padding()
        }
    
        func row() -> some View {
            Text(test)
                .border(Color.red) // uncomment to see border
        }
    }
    
    struct TestTwoAxisScrollView_Previews: PreviewProvider {
        static var previews: some View {
            TestTwoAxisScrollView()
        }
    }