ScrollKit Documentation

repository·main·Indexed 21 days ago

https://github.com/danielsaidi/scrollkit

A SwiftUI library for advanced scrolling features on Apple platforms. It provides tools for offset tracking via ScrollViewWithOffsetTracking and ScrollViewOffsetTracker, as well as components for stretchy, sticky headers using ScrollViewWithStickyHeader. The library includes utility views like ScrollViewHeaderGradient and ScrollViewHeaderImage, and View extensions for managing status bar visibility and rounded header overlaps.

Tokens
3K
Snippets
8
Records
11
Agent score
75%

What's inside ScrollKit

  1. Overview of ScrollKit features

    main

    ScrollKit is a Swift SDK designed to add advanced scrolling capabilities to SwiftUI applications. Key features include:

    • Offset Tracking: Monitor the scroll position within a view.
    • Sticky Headers: Implement header views that stretch and transform during pull-down gestures and stick to the top of the scroll view when scrolling up.

    Note: For backwards compatibility, the library currently uses traditional scroll implementations rather than the newest SwiftUI ScrollView APIs, though this is expected to change in future versions.

  2. Use namespaces to group strings in l10n-gen

    main

    You can use dot-notation in keys to create a nested string hierarchy. This helps group strings together and reduces the risk of merge conflicts.

    For example, a key named Experiments.DebugScreen.Title will be transformed into the following public key format: .l10n.experiments.debugScreen.title

    Customizing the Root Namespace

    You can customize the l10n root namespace name. This is useful when parsing multiple different string catalogs to prevent generated key collisions by wrapping each collection in a unique root namespace.

    Input Key: Experiments.DebugScreen.Title
    Output Key: .l10n.experiments.debugScreen.title
  3. Track scroll offset with `ScrollViewWithOffsetTracking` or `ScrollViewOffsetTracker`

    main

    ScrollKit provides two primary ways to track scroll offsets in SwiftUI:

    1. ScrollViewWithOffsetTracking: A wrapper view that triggers an onScroll action whenever the content is scrolled.
    2. ScrollViewOffsetTracker + .scrollViewOffsetTracking(action:): Use the ScrollViewOffsetTracker inside a scrollable container (like a List) and apply the .scrollViewOffsetTracking modifier to that container to receive the offset in a closure.
    // Option 1: Using ScrollViewWithOffsetTracking
    struct MyView: View {
        @State private var offset = CGPoint.zero
        
        func handleOffset(_ scrollOffset: CGPoint) {
            self.offset = scrollOffset
        }
    
        var body: some View {
            ScrollViewWithOffsetTracking(onScroll: handleOffset) {
                // Add your scroll content here
            }
        }
    }
    
    // Option 2: Using ScrollViewOffsetTracker with a modifier
    List {
        ScrollViewOffsetTracker {
            ForEach(0...100, id: \.self) {
                Text("\($0)")
                    .frame(width: 200, height: 200)
            }
        }
    }
    .scrollViewOffsetTracking { offset in
        print(offset)
    }
  4. Generate public key wrappers with l10n-gen

    main

    The l10n-gen tool generates public key wrappers for a string catalog's internal auto-generated keys. This allows internal Xcode-generated keys (which are normally inaccessible from other targets) to be used across different targets by utilizing the .module bundle for proper localization.

    Usage Modes

    1. Catalog to File: Parse a specific from catalog and write keys to a to target file path.
    2. Package Module Mode: Parse any package module string catalog at a package-relative catalogPath and write it to a package-relative targetPath.
    # Run help to see specific usage examples
    swift run l10n-gen --help
    
    # Use the convenience script
    ./scripts/l10n-gen.script
  5. Control status bar visibility on scroll

    main

    To manage status bar appearance (e.g., hiding it until the user starts scrolling), you can use two approaches:

    1. Manual Control: Use StatusBarVisibleState (an ObservableObject) and apply the .statusBarVisible(_:) modifier to your root content. This allows you to manually update the state based on scroll position.
    2. Automatic Control: Use the .statusBarHiddenUntilScrolled(offset:) modifier. This is an experimental feature that automatically handles the state based on the provided offset.

    Note: The automatic feature is experimental and may exhibit glitches in certain scenarios.

    // Manual Control
    struct ContentView: View {
        @StateObject private var state = StatusBarVisibleState()
    
        var body: some View {
            NavigationStack {
                // ...
            }
            .statusBarVisible(state)
        }
    }
    
    // Automatic Control (Experimental)
    // .statusBarHiddenUntilScrolled(offset: someOffset)
  6. Set up a scroll view with a sticky header using `ScrollViewWithStickyHeader`

    main

    Use ScrollViewWithStickyHeader to create a scroll view where the header stretches when pulled down and sticks to the top when scrolling.

    Key parameters:

    • header: The view to be used as the header.
    • headerHeight: The resting height of the header.
    • headerMinHeight: The minimum height the header can shrink to.
    • headerStretch: A Boolean to enable/disable the stretching effect.
    • contentCornerRadius: An optional corner radius for the content mask.
    • onScroll: An optional closure (CGPoint, CGFloat) -> Void that provides the current scroll offset and the visibleHeaderRatio (a value from 0 to 1 representing how much of the header is visible below the navigation bar).
    struct MyView: View {
        @State
        private var offset = CGPoint.zero
        @State
        private var visibleRatio = CGFloat.zero
    
        var body: some View {
            ScrollViewWithStickyHeader(
                header: stickyHeader,
                headerHeight: 250,
                headerMinHeight: 150,
                headerStretch: false,
                contentCornerRadius: 20,
                onScroll: handleScroll
            ) {
                // Add your scroll content here, e.g. a `LazyVStack`
            }
        }
    
        func handleScroll(_ offset: CGPoint, visibleHeaderRatio: CGFloat) {
            self.offset = offset
            self.visibleRatio = visibleHeaderRatio
        }
    
        func stickyHeader() -> some View {
            ZStack {
                Color.red
                ScrollViewHeaderGradient() // Default dark gradient
                Text("Scroll offset: \(offset.y)")
            }
        }
    }
  7. Implement a stretchy, sticky header with `ScrollViewWithStickyHeader`

    main

    The ScrollViewWithStickyHeader component allows you to create a header that stretches when pulled down and sticks to the top when scrolling up.

    Configuration Options:

    • header: The view to be used as the header.
    • headerHeight: The resting (default) height of the header.
    • headerMinHeight: The minimum height the header can shrink to.
    • headerStretch: A Boolean to enable or disable the stretching effect.
    • contentCornerRadius: An optional corner radius applied to the content mask.
    • onScroll: An optional closure (CGPoint, CGFloat) -> Void that provides the current scroll offset and the visibleHeaderRatio (the ratio of the header currently visible).
    import SwiftUI
    import ScrollKit
    
    struct MyView: View {
    
        @State
        private var scrollOffset = CGPoint.zero
    
        @State
        private var visibleRatio = CGFloat.zero
    
        var body: some View {
            ScrollViewWithStickyHeader(
                header: stickyHeader,
                headerHeight: 250,
                headerMinHeight: 150,
                headerStretch: false,
                contentCornerRadius: 20,
                onScroll: handleScroll
            ) { 
                // Add your scroll content here, e.g. a `LazyVStack` 
            }
        }
    
        func handleScroll(_ offset: CGPoint, visibleHeaderRatio: CGFloat) {
            self.scrollOffset = offset
            self.visibleRatio = visibleHeaderRatio
        }
    
        func stickyHeader() -> some View {
            ZStack {
                Color.red
                ScrollViewHeaderGradient()  // By default a dark gradient
                Text("Scroll offset: \(scrollOffset.y)")
            }
        }
    }
  8. Track scroll offset with `ScrollViewWithOffsetTracking`

    main

    Use ScrollViewWithOffsetTracking to detect and react to the current scroll position. It provides the current offset (as a CGPoint) within its closure, allowing you to perform actions like logging or updating state based on the scroll position.

    ScrollViewWithOffsetTracking { offset in
        print(offset)
    } content: {
        // Add your scroll content here, e.g. a `LazyVStack`
    }
  9. Use ScrollKit View extensions

    main

    The library provides several View extensions for advanced scrolling UI:

    • .hideStatusBarUntilScrolled(using:): Hides the status bar based on an observable value.
    • .scrollViewContentWithRoundedHeaderOverlap(_:cornerRadius:): Allows a view to overlap a static header view with rounded corners.
    • .scrollViewHeaderWithRoundedContentCorners(cornerRadius:): Creates a rounded, inverse content mask used by ScrollViewWithStickyHeader to allow content to scroll under a rounded header.
  10. Use ScrollKit header utility views

    main

    ScrollKit includes specialized views for managing header aesthetics:

    • ScrollViewHeader: A header view that stretches when pulled down and scrolls away with the content.
    • ScrollViewHeaderGradient: A discrete color gradient designed to improve text readability when placed over light images.
    • ScrollViewHeaderImage: A view that takes a custom image and automatically adjusts its aspect ratio to function as a stretchy scroll view header.