stonko1994 / SimultaneouslyScrollView

Simultaneously scrolling ScrollViews with SwiftUI support! ↕️🚀
MIT License
44 stars 14 forks source link

SimultaneouslyScrollView

Simultaneously scrolling ScrollViews with SwiftUI support


Build SwiftLint

Installation

Using Swift Package Manager

Swift Package Manager is a tool for managing the distribution of Swift frameworks. It integrates with the Swift build system to automate the process of downloading, compiling, and linking dependencies.

Using Xcode

To integrate using Xcode 13, open your Project file and specify it in Project > Package Dependencies using the following URL:

https://github.com/stonko1994/SimultaneouslyScrollView.git

Usage

import SimultaneouslyScrollView

Synchronize multiple UIScrollViews

1) Create an SimultaneouslyScrollViewHandler instance by using the factory method and the create function:

    let simultaneouslyScrollViewHandler = SimultaneouslyScrollViewHandlerFactory.create()

1) Register UIScrollViews that should be synchronized:

    simultaneouslyScrollViewHandler.register(scrollView: scrollView)

SwiftUI support

To enable simultaneously scrolling in SwiftUI we need to utilize another library that allows access to the underlying UIScrollView for a SwiftUI.ScrollView.

SwiftUI-Introspect 🚀

Synchronize multiple ScrollViews

1) Follow the installataion steps from SwiftUI-Introspect

    Recommended is to use version 1.0.0 or higher.

1) Import Introspect in addition to SimultaneouslyScrollView

    import SimultaneouslyScrollView
    import SwiftUIIntrospect

1) Access the UIScrollView from your ScrollView and register it to the SimultaneouslyScrollViewHandler.

    ScrollView {
        ...
    }
    .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) {
        viewModel.simultaneouslyScrollViewHandler.register(scrollView: $0)
    }

1) That's it 🥳🎉

I recommend storing the `simultaneouslyScrollViewHandler` inside some view-model. E.g. an `@ObservedObject` or a `@StateObject`.

How it works

SwiftUI doesn't provide any API to specify the contentOffset for ScrollViews. Therefore we need to access the underlying UIKit element and set the contentOffset there. This is where SwiftUI-Introspect comes in handy by providing access to the UIKit elements.

As every redraw of the View creates a new ScrollView and a new UIScrollView instance, it is important not to store strong references of the registered UIScrollViews. The SimultaneouslyScrollViewHandler manages this using a custom implementation using the WeakObjectStore.

When a ScrollView is scrolled by the user, the SimultaneouslyScrollViewHandler gets notified about this via the UIScrollViewDelegate. When this happens, the contentOffset of every other registered UIScrollView will be adapted to the new contentOffset of the currently scrolled UIScrollView.

Example

I use this package in one of my own Apps that is currently in the Appstore. So there shouldn't be any issues using this in any production code except the possibility that new SwiftUI versions could break SwiftUI-Introspect.

Please note that this introspection method might break in future SwiftUI releases. Future implementations might not use the same hierarchy, or might not use UIKit elements that are being looked for. Though the library is unlikely to crash, the .introspect() method will not be called in those cases.

Download Scoretastic 🥳


Buy Me A Coffee