Contents

rccoop/horizontalscrollpicker

Very simply, `HorizontalScrollPicker` is a SwiftUI view like a `Picker`, but horizontal.

What Is It?

Very simply, HorizontalScrollPicker is a SwiftUI view like a Picker, but horizontal.

[HorizontalScrollPickerDemo]

Installation

Requirements

  • iOS 18+

Swift Package Manager

In your own package, add the following to your dependencies:

dependencies: [
  .package(url: "https://github.com/RCCoop/HorizontalScrollPicker", .upToNextMajor(from: "1.0.0"))
]

Usage

The implementation of HorizontalScrollPicker borrows as heavily as I could from Picker and Layout, with the largest difference being that for the selection property you'll need to use the PickerSelection property wrapper rather than a Binding.

struct MyView: View {
    @PickerSelection var selection: Animal = .cow
    
    var body: some View {
        HorizontalScrollPicker(
            Animal.allCases,
            id: \.self,
            selection: _selection) 
        { 
            AnimalView($0)
        }
    }
}

The PickerSelection property wrapper's type should match the id of the picker's data type. It will update as you scroll back and forth in the picker, but you can also set the picker's position manually:

  • $selection - the projectedValue returns a Binding, and you can set its wrappedValue directly or use the Binding in any other type.
  • selection.setSelection(:) sets the picker's position instantly.
  • selection.scrollToSelection(:animation:completion:) sets the position with an animation and optional completion closure.

What's In Here?

I started making this package with loftier ambitions, but ended up using it just as a personal playground for testing and learning about a few SwiftUI tools. I'm mostly publishing it here in hopes that you, dear reader, will find it useful in your app or to see an example of some of these tools.

Layout Protocol

HorizontalScrollPicker lays out the cells that you pass in using a custom HorizontalScrollerLayout, for no particularly good reason. The custom layout works that same as a HStack or HStackLayout, with no noticeable performance improvement.

I had hoped I could get some better performance, or mimic a LazyHStack's lazy loading, but eventually gave up. Building the custom layout was interesting, though, so I leave it in here for you to check out.

(Using a LazyHStack wasn't an option, either, since using PickerSelection to set the picker's position requires knowing the frames of each cell from the beginning, which LazyHStack doesn't allow)

ScrollTargetBehavior Protocol

More successfully, HorizontalScrollPicker uses a custom ScrollTargetBehavior to ensure that the scroller always lands centered on the target cell. See CenterAlignedScrollBehavior.

ScrollPosition

Setting the scroller's position using scrollPosition(id:anchor:) almost works perfectly, but failed to get the initial position of the scroller just right, leaving it off-center of the initial selection. So instead I used scrollPosition(_:), which uses the ScrollPosition type, and I calculate the xOffset manually to get just the right value.

Using ScrollPosition in the internal ViewModel updates the picker's selection based on its ScrollGeometry, as well as allows us to set the position of the scroller by calling positionState.scrollTo(x:).

Package Metadata

Repository: rccoop/horizontalscrollpicker

Default branch: main

README: README.md