Parchment Documentation

repository·main·Indexed 25 days ago

https://github.com/rechsteiner/parchment

A Swift library for paging between view controllers with highly customizable, scrolling indicators. It supports both SwiftUI via PageView and UIKit via PagingViewController, featuring memory-efficient dynamic view controller allocation and support for infinite scrolling through PagingViewControllerInfiniteDataSource.

Tokens
6.4K
Snippets
24
Records
29
Agent score
84%

What's inside Parchment

  1. Overview of Parchment features

    main

    Parchment is a library for paging between view controllers with a customizable indicator that scrolls alongside the content.

    Key features include:

    • Highly customizable: Menu items are built using UICollectionView, allowing for custom layouts and behaviors.
    • Memory-efficient: View controllers are only allocated when needed, preventing up-front initialization of large sets of controllers.
    • Infinite scrolling: Supports infinitely large data sources because view controllers are allocated dynamically during scrolling.
  2. Implement PagingViewControllerDataSource

    main

    To provide data to a PagingViewController, implement the PagingViewControllerDataSource protocol. You must define the total number of view controllers, provide a UIViewController for a specific index, and provide a PagingItem (such as PagingIndexItem) for that index.

    Note that viewControllerAt is only called for the currently selected item and its immediate siblings to optimize memory allocation.

    class ViewController: UIViewController, PagingViewControllerDataSource {
        let cities = ["Oslo", "Stockholm", "Tokyo", "Barcelona", "Vancouver", "Berlin"]
    
        func numberOfViewControllers(in pagingViewController: PagingViewController) -> Int {
            return cities.count
        }
    
        func pagingViewController(_: PagingViewController, viewControllerAt index: Int) -> UIViewController {
            return CityViewController(city: cities[index])
        }
    
        func pagingViewController(_: PagingViewController, pagingItemAt index: Int) -> PagingItem {
            return PagingIndexItem(index: index, title: cities[index])
        }
    }
    
    // Setup
    let pagingViewController = PagingViewController()
    pagingViewController.dataSource = self
  3. Implement an infinite data source with PagingViewControllerInfiniteDataSource

    main

    Use the PagingViewControllerInfiniteDataSource protocol when the number of view controllers is unknown or potentially infinite (e.g., a calendar or server-side paginated data).

    To implement this, you must:

    1. Define a custom type that conforms to PagingItem, Hashable, and Comparable to represent your data.
    2. Conform to PagingViewControllerInfiniteDataSource by implementing three methods:
      • pagingViewController(_:itemAfter:): Returns the next PagingItem after the current one.
      • pagingViewController(_:itemBefore:): Returns the previous PagingItem before the current one.
      • pagingViewController(_:viewControllerFor:): Returns the UIViewController associated with the given PagingItem.
    3. Assign the data source to the infiniteDataSource property of your PagingViewController instance.
    4. Call select(pagingItem:) to set the starting item.
    // 1. Define your custom PagingItem
    struct CalendarItem: PagingItem, Hashable, Comparable {
        let date: Date
      
        static func < (lhs: CalendarItem, rhs: CalendarItem) -> Bool {
            return lhs.date < rhs.date
        }
    }
    
    // 2. Conform to the infinite data source protocol
    extension ViewController: PagingViewControllerInfiniteDataSource {
      func pagingViewController(_: PagingViewController, itemAfter pagingItem: PagingItem) -> PagingItem? {
        let calendarItem = pagingItem as! CalendarItem
        return CalendarItem(date: calendarItem.date.addingTimeInterval(86400))
      }
      
      func pagingViewController(_: PagingViewController, itemBefore pagingItem: PagingItem) -> PagingItem? {
        let calendarItem = pagingItem as! CalendarItem
        return CalendarItem(date: calendarItem.date.addingTimeInterval(-86400))
      }
      
      func pagingViewController(_: PagingViewController, viewControllerFor pagingItem: PagingItem) -> UIViewController {
        let calendarItem = pagingItem as! CalendarItem
        return CalendarViewController(date: calendarItem.date)
      }
    }
    
    // 3. Setup the PagingViewController
    let pagingViewController = PagingViewController()
    pagingViewController.infiniteDataSource = self
    pagingViewController.select(pagingItem: CalendarItem(date: Date()))
  4. Basic usage with UIKit PagingViewController

    main

    Initialize a PagingViewController with an array of UIViewController instances. The menu items will automatically use the title property of each view controller.

    let firstViewController = UIViewController()
    let secondViewController = UIViewController()
    
    let pagingViewController = PagingViewController(viewControllers: [
      firstViewController,
      secondViewController
    ])
  5. Update selection in SwiftUI

    main

    Use a Binding for selectedIndex in PageView. When the value of the binding is updated, Parchment will automatically scroll to the corresponding index.

    @State var selectedIndex: Int = 0
    ...
    PageView(selectedIndex: $selectedIndex) {
        Page("Title 1") {
            Button("Next") {
                selectedIndex = 1
            }
        }
        Page("Title 2") {
            Text("Page 2")
        }
    }
  6. Basic usage in SwiftUI

    main

    Create a PageView by providing Page instances. Each Page requires a title and a content view. By default, menu items use the provided titles, but you can provide a custom view for the menu item using the state parameter to react to scroll progress or selection state.

    // Simple usage
    PageView {
        Page("Title 0") {
            Text("Page 0")
        }
        Page("Title 1") {
            Text("Page 1")
        }
    }
    
    // Custom menu item using state
    PageView {
        Page { state in
            Image(systemName: "star.fill")
                .rotationEffect(Angle(degrees: 90 * state.progress))
        } content: {
            Text("Page 1")
        }
    }
  7. Initialize and display a PagingViewController

    main

    To use Parchment, initialize a PagingViewController with an array of UIViewController instances that you want to display. Parchment will automatically generate menu items for each view controller based on their title property.

    After initialization, you must add the pagingViewController as a child view controller and set up its layout constraints to fill the parent view.

    import Parchment
    
    class ViewController: UIViewController {
      override func viewDidLoad() {
        super.viewDidLoad()
        let firstViewController = UIViewController()
        let secondViewController = UIViewController()
    
        let pagingViewController = PagingViewController(viewControllers: [
          firstViewController,
          secondViewController
        ])
    
        addChild(pagingViewController)
        view.addSubview(pagingViewController.view)
        pagingViewController.didMove(toParent: self)
        pagingViewController.view.translatesAutoresizingMaskIntoConstraints = false
    
        NSLayoutConstraint.activate([
          pagingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
          pagingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
          pagingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
          pagingViewController.view.topAnchor.constraint(equalTo: view.topAnchor)
        ])
      }
    }
  8. Apply SwiftUI modifiers to PageView

    main

    Customize the appearance and behavior of PageView using various modifiers such as .menuItemSize, .menuPosition, .indicatorOptions, and .contentInteraction.

    PageView {
        Page("Title 1") {
            Text("Page 1")
        }
    }
    .menuItemSize(.fixed(width: 100, height: 60))
    .menuItemSpacing(20)
    .menuItemLabelSpacing(30)
    .menuBackgroundColor(.white)
    .menuInsets(.vertical, 20)
    .menuHorizontalAlignment(.center)
    .menuPosition(.bottom)
    .menuTransition(.scrollAlongside)
    .menuInteraction(.swipe)
    .contentInteraction(.scrolling)
    .contentNavigationOrientation(.vertical)
    .selectedScrollPosition(.preferCentered)
    .indicatorOptions(.visible(height: 4))
    .indicatorColor(.blue)
    .borderOptions(.visible(height: 4))
    .borderColor(.blue.opacity(0.2))
  9. Create dynamic pages in SwiftUI

    main

    To handle a dynamic collection of items, pass the collection and an ID to PageView, then map each item to a Page within the trailing closure.

    PageView(items, id: \.self) { item in
        Page("Title \(item)") {
            Text("Page \(item)")
        }
    }