Post

Replies

Boosts

Views

Activity

Reply to ScrollViews Sync SwiftUI
With the current API of ScrollView, this is not possible. While you can get the contentOffset of the scrollView using methods that are widely available on the internet, the ScrollViewReader that is used to programmatically scroll a ScrollView only allows you to scroll to specific views, instead of to a contentOffset. To achieve this functionality, you are going to have to wrap UIScrollView. Here is an implementation, although it isn't 100% stable, and is missing a good amount of scrollView functionality: import SwiftUI import UIKit public struct ScrollableView<Content: View>: UIViewControllerRepresentable {     @Binding var offset: CGPoint     var content: () -> Content     public init(_ offset: Binding<CGPoint>, @ViewBuilder content: @escaping () -> Content) {         self._offset = offset         self.content = content     }     public func makeUIViewController(context: Context) -> UIScrollViewViewController {         let vc = UIScrollViewViewController()         vc.hostingController.rootView = AnyView(self.content())         vc.scrollView.setContentOffset(offset, animated: false)         vc.delegate = context.coordinator         return vc     }     public func updateUIViewController(_ viewController: UIScrollViewViewController, context: Context) {         viewController.hostingController.rootView = AnyView(self.content())         // Allow for deaceleration to be done by the scrollView         if !viewController.scrollView.isDecelerating {             viewController.scrollView.setContentOffset(offset, animated: false)         }     }     public func makeCoordinator() -> Coordinator {         Coordinator(contentOffset: _offset)     }     public class Coordinator: NSObject, UIScrollViewDelegate {         let contentOffset: Binding<CGPoint>         init(contentOffset: Binding<CGPoint>) {             self.contentOffset = contentOffset         }         public func scrollViewDidScroll(_ scrollView: UIScrollView) {             contentOffset.wrappedValue = scrollView.contentOffset         }     } } public class UIScrollViewViewController: UIViewController {     lazy var scrollView: UIScrollView = UIScrollView()     var hostingController: UIHostingController<AnyView> = UIHostingController(rootView: AnyView(EmptyView()))     weak var delegate: UIScrollViewDelegate?     public override func viewDidLoad() {         super.viewDidLoad()         self.scrollView.delegate = delegate         self.view.addSubview(self.scrollView)         self.pinEdges(of: self.scrollView, to: self.view)         self.hostingController.willMove(toParent: self)         self.scrollView.addSubview(self.hostingController.view)         self.pinEdges(of: self.hostingController.view, to: self.scrollView)         self.hostingController.didMove(toParent: self)     }     func pinEdges(of viewA: UIView, to viewB: UIView) {         viewA.translatesAutoresizingMaskIntoConstraints = false         viewB.addConstraints([             viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),             viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),             viewA.topAnchor.constraint(equalTo: viewB.topAnchor),             viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor),         ])     } } struct ScrollableView_Previews: PreviewProvider {     static var previews: some View {         Wrapper()     }     struct Wrapper: View {         @State var offset: CGPoint = .init(x: 0, y: 50)         var body: some View {             HStack {                 ScrollableView($offset, content: {                     ForEach(0...100, id: \.self) { id in                         Text("\(id)")                     }                 })                 ScrollableView($offset, content: {                     ForEach(0...100, id: \.self) { id in                         Text("\(id)")                     }                 })                 VStack {                     Text("x: \(offset.x) y: \(offset.y)")                     Button("Top", action: {                         offset = .zero                     })                     .buttonStyle(.borderedProminent)                 }                 .frame(width: 200)                 .padding()             }         }     } }
Feb ’22