Convert Figma logo to code with AI

rechsteiner logoParchment

A paging view with a highly customizable menu ✨

3,366
419
3,366
23

Top Related Projects

19,955

A Swift Autolayout DSL for iOS & OS X

40,982

Elegant HTTP Networking in Swift

24,358

Reactive Programming in Swift

A lightweight, pure-Swift library for downloading and caching images from the web.

The better way to deal with JSON data in Swift.

Promises for Swift & ObjC.

Quick Overview

Parchment is a paging view controller with a highly customizable menu. It's designed for iOS applications and allows developers to create smooth, interactive paging interfaces with customizable menu items. Parchment is built with Swift and provides a flexible solution for implementing paginated content with a top or side menu.

Pros

  • Highly customizable menu appearance and behavior
  • Smooth scrolling and page transitions
  • Easy integration with existing UIViewController-based content
  • Supports both horizontal and vertical layouts

Cons

  • Limited to iOS platform
  • May require additional setup for complex use cases
  • Learning curve for advanced customizations
  • Potential performance issues with a large number of pages

Code Examples

  1. Basic setup with an array of view controllers:
let pagingViewController = PagingViewController()
pagingViewController.dataSource = self
pagingViewController.delegate = self

override func viewDidLoad() {
    super.viewDidLoad()
    addChild(pagingViewController)
    view.addSubview(pagingViewController.view)
    pagingViewController.didMove(toParent: self)
}
  1. Implementing the data source:
extension ViewController: PagingViewControllerDataSource {
    func numberOfViewControllers(in pagingViewController: PagingViewController) -> Int {
        return viewControllers.count
    }

    func pagingViewController(_ pagingViewController: PagingViewController, viewControllerAt index: Int) -> UIViewController {
        return viewControllers[index]
    }

    func pagingViewController(_ pagingViewController: PagingViewController, pagingItemAt index: Int) -> PagingItem {
        return PagingIndexItem(index: index, title: titles[index])
    }
}
  1. Customizing the menu appearance:
let pagingViewController = PagingViewController()
pagingViewController.menuItemSize = .fixed(width: 100, height: 40)
pagingViewController.menuItemSpacing = 20
pagingViewController.indicatorOptions = .visible(
    height: 4,
    zIndex: Int.max,
    spacing: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8),
    insets: .zero
)

Getting Started

To use Parchment in your project:

  1. Add Parchment to your project using Swift Package Manager, CocoaPods, or Carthage.
  2. Import Parchment in your Swift file:
import Parchment
  1. Create a PagingViewController and set it up in your view controller:
let pagingViewController = PagingViewController()
pagingViewController.dataSource = self
pagingViewController.delegate = self

addChild(pagingViewController)
view.addSubview(pagingViewController.view)
pagingViewController.didMove(toParent: self)
  1. Implement the PagingViewControllerDataSource protocol to provide the necessary data for the paging view controller.

Competitor Comparisons

19,955

A Swift Autolayout DSL for iOS & OS X

Pros of SnapKit

  • More versatile, used for general UI layout and constraints
  • Larger community and more frequent updates
  • Extensive documentation and examples available

Cons of SnapKit

  • Steeper learning curve for beginners
  • May be overkill for simple UI layouts
  • Requires more code for basic setups compared to Parchment

Code Comparison

SnapKit example:

view.addSubview(button)
button.snp.makeConstraints { make in
    make.center.equalToSuperview()
    make.width.height.equalTo(100)
}

Parchment example:

let pagingViewController = PagingViewController()
pagingViewController.dataSource = self
pagingViewController.delegate = self
addChild(pagingViewController)
view.addSubview(pagingViewController.view)

Summary

SnapKit is a powerful Auto Layout DSL for iOS and macOS, offering flexibility for complex UI layouts. Parchment, on the other hand, is specifically designed for creating paging view controllers with customizable menu items. While SnapKit provides more general-purpose layout capabilities, Parchment offers a simpler, more focused solution for implementing paginated interfaces. The choice between the two depends on the specific requirements of your project and the complexity of your UI needs.

40,982

Elegant HTTP Networking in Swift

Pros of Alamofire

  • Comprehensive networking library with extensive features
  • Large, active community and frequent updates
  • Well-documented with extensive examples and tutorials

Cons of Alamofire

  • Larger footprint and potential overhead for simple networking tasks
  • Steeper learning curve for beginners due to its extensive API

Code Comparison

Alamofire:

AF.request("https://api.example.com/data").responseJSON { response in
    switch response.result {
    case .success(let value):
        print("JSON: \(value)")
    case .failure(let error):
        print("Error: \(error)")
    }
}

Parchment:

let pagingViewController = PagingViewController()
pagingViewController.dataSource = self
pagingViewController.delegate = self

Summary

Alamofire is a powerful networking library for iOS and macOS, offering a wide range of features for HTTP networking. It's well-suited for complex networking tasks and has a large community backing it. However, it may be overkill for simple projects and can have a steeper learning curve.

Parchment, on the other hand, is a library focused on creating paging view controllers. It's more specialized and doesn't directly compete with Alamofire in terms of functionality. Parchment is ideal for projects requiring custom paging interfaces, while Alamofire excels in handling network requests and responses.

The choice between these libraries depends on the specific needs of your project. If you require robust networking capabilities, Alamofire is an excellent choice. For creating paging interfaces, Parchment offers a tailored solution.

24,358

Reactive Programming in Swift

Pros of RxSwift

  • Comprehensive reactive programming framework for Swift
  • Large community and extensive documentation
  • Supports multiple platforms (iOS, macOS, tvOS, watchOS)

Cons of RxSwift

  • Steeper learning curve for developers new to reactive programming
  • Can lead to complex code if not used carefully
  • Larger codebase and potential performance overhead

Code Comparison

Parchment (Page view controller):

let pagingViewController = PagingViewController()
pagingViewController.dataSource = self
pagingViewController.delegate = self

RxSwift (Observable sequence):

Observable.from([1, 2, 3, 4, 5])
    .filter { $0 % 2 == 0 }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

Summary

Parchment is a lightweight library focused on creating paging view controllers, while RxSwift is a comprehensive reactive programming framework. Parchment is easier to integrate for specific UI tasks, whereas RxSwift offers powerful tools for managing asynchronous events and data streams across an entire application. The choice between them depends on the project's scope and requirements.

A lightweight, pure-Swift library for downloading and caching images from the web.

Pros of Kingfisher

  • Extensive image downloading and caching functionality
  • Supports multiple image formats and processing options
  • Large community and frequent updates

Cons of Kingfisher

  • Larger library size due to more features
  • May be overkill for simple image loading tasks
  • Steeper learning curve for advanced features

Code Comparison

Kingfisher:

let url = URL(string: "https://example.com/image.png")
imageView.kf.setImage(with: url)

Parchment:

let pagingViewController = PagingViewController()
pagingViewController.dataSource = self
pagingViewController.delegate = self

Summary

Kingfisher is a powerful image loading and caching library, while Parchment focuses on creating paging view controllers. They serve different purposes, making a direct comparison challenging. Kingfisher excels in image handling tasks, offering a wide range of features for downloading, caching, and processing images. Parchment, on the other hand, provides a customizable solution for implementing paging interfaces in iOS applications.

Choose Kingfisher for robust image management needs, and Parchment for creating smooth, paginated user interfaces. The decision between the two depends on the specific requirements of your project.

The better way to deal with JSON data in Swift.

Pros of SwiftyJSON

  • Specialized for JSON parsing and manipulation in Swift
  • Extensive documentation and community support
  • Simplifies working with complex JSON structures

Cons of SwiftyJSON

  • Limited to JSON data handling only
  • May introduce unnecessary overhead for simple JSON operations
  • Requires additional dependency in projects

Code Comparison

SwiftyJSON:

let json = JSON(data: dataFromNetworking)
if let name = json["user"]["name"].string {
    // Do something with name
}

Parchment:

let pagingViewController = PagingViewController()
pagingViewController.dataSource = self
pagingViewController.delegate = self

Key Differences

SwiftyJSON is a library for handling JSON data in Swift, while Parchment is a paging view controller for iOS. They serve entirely different purposes and are not directly comparable in terms of functionality.

SwiftyJSON focuses on simplifying JSON parsing and manipulation, making it easier to work with complex JSON structures. It provides a more convenient syntax for accessing JSON data compared to Swift's native JSONDecoder.

Parchment, on the other hand, is designed for creating customizable paging interfaces in iOS applications. It offers features like infinite scrolling, customizable menu items, and various transition styles.

The choice between these libraries depends on the specific needs of your project. If you're working extensively with JSON data, SwiftyJSON could be beneficial. For creating paging interfaces, Parchment would be the appropriate choice.

Promises for Swift & ObjC.

Pros of PromiseKit

  • Widely adopted and mature library for asynchronous programming
  • Extensive documentation and community support
  • Supports multiple programming languages beyond Swift

Cons of PromiseKit

  • Larger codebase and potential overhead for simpler projects
  • Steeper learning curve for developers new to promises

Code Comparison

PromiseKit:

firstly {
    fetchUser()
}.then { user in
    fetchAvatar(user)
}.done { avatar in
    self.imageView.image = avatar
}.catch { error in
    print("Error: \(error)")
}

Parchment:

let pagingViewController = PagingViewController()
pagingViewController.dataSource = self
pagingViewController.delegate = self
addChild(pagingViewController)
view.addSubview(pagingViewController.view)

Summary

PromiseKit is a comprehensive solution for handling asynchronous operations, offering broad language support and extensive documentation. However, it may be overkill for simpler projects and requires a deeper understanding of promises.

Parchment, on the other hand, is a specialized library for creating paging view controllers in iOS. It offers a simpler API for its specific use case but lacks the versatility of PromiseKit for general asynchronous programming.

The code comparison illustrates the different focus areas of these libraries, with PromiseKit demonstrating asynchronous chaining and error handling, while Parchment showcases its straightforward setup for paging view controllers.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Getting Started | Customization | Installation


Cities Example Unsplash Example Calendar Example

Features

Parchment lets you page between view controllers while showing any type of generic indicator that scrolls along with the content. Here are some benefits of using Parchment:

  • Highly customizable
    The menu items are built using UICollectionView, which means you can display pretty much whatever you want. You can even subclass the layout to create completely custom behaviours.

  • Memory-efficient:
    Parchment only allocates view controllers when they’re needed, meaning if you have a lot of view controllers you don’t have to initialize them all up-front.

  • Infinite scrolling:
    Because view controllers are only allocated as you are scrolling, you can create data sources that are infinitely large. This is perfect for things like calendars.

Table of contents

Getting started

Using UIKit? Go to UIKit documentation.

SwiftUI

Basic usage

Create a PageView instance with the pages you want to show. Each Page takes a title and a content view, which can be any SwiftUI view.

PageView {
    Page("Title 0") {
        Text("Page 0")
    }
    Page("Title 1") {
        Text("Page 1")
    }
}

By default, the menu items are displayed as titles, but you can also pass in any SwiftUI view as the menu item. The state parameter allows you to customize the menu item based on the selected state and scroll position of the view. For instance, you could show an icon that rotates based on its progress like this:

PageView {
    Page { state in
        Image(systemName: "star.fill")
            .rotationEffect(Angle(degrees: 90 * state.progress))
    } content: {
        Text("Page 1")
    }
}

Dynamic pages

To create a PageView with a dynamic number of pages, you can pass in a collection of items where each item is mapped to a Page:

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

Update selection

To select specific items, you can pass a binding into PageView with the index of the currently selected item. When updating the binding, Parchment will scroll to the new index.

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

Modifiers

You can customize the PageView using the following modifiers. See Options for more details on each option.

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))
UIKit

Basic usage with UIKit

Parchment is built around the PagingViewController class. You can initialize it with an array of view controllers and it will display menu items for each view controller using their title property.

let firstViewController = UIViewController()
let secondViewController = UIViewController()

let pagingViewController = PagingViewController(viewControllers: [
  firstViewController,
  secondViewController
])

See more: Basic usage

Data source

Initializing PagingViewController with an array of view controllers is fine in most cases, but if you have more than a few view controllers you probably don't want to allocate them all up-front. If you're going to display a fixed number of view controllers, you can setup your own data source by implementing PagingViewControllerDataSource:

extension ViewController: PagingViewControllerDataSource {
    func numberOfViewControllers(in pagingViewController: PagingViewController) -> Int {
        return 10
    }

    func pagingViewController(_ pagingViewController: PagingViewController, viewControllerAt index: Int) -> UIViewController {
        return ChildViewController(index: index)
    }

    func pagingViewController(_: PagingViewController, pagingItemAt index: Int) -> PagingItem {
        return PagingIndexItem(title: "View \(index)", index: index)
    }
}

Then you need to set the dataSource property and select the initial item:

let pagingViewController = PagingViewController()
pagingViewController.dataSource = self
pagingViewController.select(index: 0)

Using the data source means Parchment will only allocate view controllers for the currently selected item and any of its siblings. This is a lot more memory efficient than using PagingViewController(viewControllers:) if you have many view controllers.

Read more: Using the data source

Infinite data source

Using PagingViewControllerDataSource means you need to know how many view controllers to display. If you’re creating something like a calendar, the number of view controllers can be infinitely large. In that case you can use the PagingViewControllerInfiniteDataSource protocol:

extension ViewController: PagingViewControllerInfiniteDataSource {
    func pagingViewController(_: PagingViewController, viewControllerFor pagingItem: PagingItem) -> UIViewController {
        return ItemViewController(item: pagingItem)
    }

    func pagingViewController(_: PagingViewController, itemBefore pagingItem: PagingItem) -> PagingItem? {
        guard let item = pagingItem as? Item else { return nil }
        return Item(index: item.index - 1)
    }

    func pagingViewController(_ : PagingViewController, itemAfter pagingItem: PagingItem) -> PagingItem? {
        guard let item = pagingItem as? Item else { return nil }
        return Item(index: item.index + 1)
    }
}

Then set the infiniteDataSource property and select the initial item:

let pagingViewController = PagingViewController()
pagingViewController.infiniteDataSource = self
pagingViewController.select(pagingItem: Item(index: 0))

This pattern is very similar to the UIPageViewControllerDataSource protocol. The main difference is that instead of returning view controllers directly, you have to return an instance conforming to the PagingItem protocol. Parchment will recursively call these methods for the selected PagingItem until the available space is filled up.

Read more: Using the infinite data source

Selecting items

You can select items programmatically using:

func select(pagingItem: PagingItem, animated: Bool = false)

Let’s say you want to select the first item:

override func viewDidLoad() {
  super.viewDidLoad()
  if let first = pagingViewController.children.first as? PagingItem {
    pagingViewController.select(pagingItem: first)
  }
}

Or if you have set the dateSource property, you can select items based on their index:

func select(index: Int, animated: Bool = false)

Reloading data

You can reload data using this method:

func reloadData()

This will keep the previously selected item if it's still part of the updated data. If not, it will select the first item in the list. It will also reload the view controllers displayed in the page view controller. If you only want to reload the menu items, you can use this method:

func reloadMenu()

Calling reloadData() will not work when using PagingViewControllerInfiniteDataSource, as we then need to know what the initial item should be. In that case you should use this method:

func reloadData(around: PagingItem)

This will mark the given paging item as selected and generate new items around it.

Delegate

Parchment provides delegate methods for every step of the transition process through the PagingViewControllerDelegate protocol.

protocol PagingViewControllerDelegate: class {

    func pagingViewController(
        _: PagingViewController,
        isScrollingFromItem currentPagingItem: PagingItem,
        toItem upcomingPagingItem: PagingItem?,
        startingViewController: UIViewController,
        destinationViewController: UIViewController?,
        progress: CGFloat)

    func pagingViewController(
        _: PagingViewController,
        willScrollToItem pagingItem: PagingItem,
        startingViewController: UIViewController,
        destinationViewController: UIViewController)

    func pagingViewController(
        _ pagingViewController: PagingViewController,
        didScrollToItem pagingItem: PagingItem,
        startingViewController: UIViewController?,
        destinationViewController: UIViewController,
        transitionSuccessful: Bool)

    func pagingViewController(
        _ pagingViewController: PagingViewController,
        didSelectItem pagingItem: PagingItem)
}

Size delegate

By default, the size of the menu items is controlled by the menuItemSize property. If you need to control width of each menu item individually you can use the PagingControllerSizeDelegate protocol:

protocol PagingViewControllerSizeDelegate: class {
    func pagingViewController(
        _: PagingViewController,
        widthForPagingItem pagingItem: PagingItem,
        isSelected: Bool) -> CGFloat
}

Then set the sizeDelegate on the PagingViewController:

let pagingViewController = PagingViewController()
pagingViewController.sizeDelegate = self

Customization

Parchment is built to be very flexible. The menu items are displayed using UICollectionView, so they can display pretty much whatever you want. If you need any further customization you can even subclass the collection view layout. All customization is handled by the properties listed below.

Custom cells

To use custom cells you need to subclass PagingCell and register the cell type for a given PagingItem:

let pagingViewController = PagingViewController()
pagingViewController.register(CalendarPagingCell.self, for: CalendarItem.self)

Parchment will then dequeue your custom cell when you return the given PagingItem in your data source. You can register multiple cell types for different PagingItems.

Properties

All customization properties are set on PagingViewController:

let pagingViewController = PagingViewController()
pagingViewController.menuItemSize = .fixed(width: 40, height: 40)
pagingViewController.menuItemSpacing = 10

See Options for all customization options.

Options

menuItemSize

The size of the menu items. When using sizeDelegate the width will be ignored.

enum PagingMenuItemSize {
  case fixed(width: CGFloat, height: CGFloat)

  // Automatically calculate the size of the menu items based on the
  // cells intrinsic content size. Try to come up with an estimated
  // width that's similar to the expected width of the cells.
  case selfSizing(estimatedWidth: CGFloat, height: CGFloat)

  // Tries to fit all menu items inside the bounds of the screen.
  // If the items can't fit, the items will scroll as normal and
  // set the menu items width to `minWidth`.
  case sizeToFit(minWidth: CGFloat, height: CGFloat)
}

Default: .sizeToFit(minWidth: 150, height: 40)

menuItemSpacing

The spacing between the menu items.

Default: 0

menuItemLabelSpacing

The horizontal constraints of menu item label.

Default: 20

menuInsets

The insets around all of the menu items.

Default: UIEdgeInsets()

menuHorizontalAlignment

enum PagingMenuHorizontalAlignment {
  case `default`

  // Allows all paging items to be centered within the paging menu
  // when PagingMenuItemSize is .fixed and the sum of the widths
  // of all the paging items are less than the paging menu
  case center
}

Default: .default

menuTransition

Determine the transition behaviour of menu items while scrolling the content.

enum PagingMenuTransition {
  // Update scroll offset based on how much the content has
  // scrolled. Makes the menu items transition smoothly as you scroll.
  case scrollAlongside

  // Animate the menu item position after a transition has completed.
  case animateAfter
}

Default: .scrollAlongside

menuInteraction

Determine how users can interact with the menu items.

enum PagingMenuInteraction {
  case scrolling
  case swipe
  case none
}

Default: .scrolling

menuLayoutClass

The class type for collection view layout. Override this if you want to use your own subclass of the layout. Setting this property will initialize the new layout type and update the collection view.

Default: PagingCollectionViewLayout.Type

selectedScrollPosition

Determine how the selected menu item should be aligned when it is selected. Effectively the same as the UICollectionViewScrollPosition.

enum PagingSelectedScrollPosition {
  case left
  case right

  // Centers the selected menu item where possible. If the item is
  // to the far left or right, it will not update the scroll position.
  // Effectivly the same as .centeredHorizontally on UIScrollView.
  case preferCentered
}

Default: .preferCentered

indicatorOptions

Add an indicator view to the selected menu item. The indicator width will be equal to the selected menu items width. Insets only apply horizontally.

enum PagingIndicatorOptions {
  case hidden
  case visible(
    height: CGFloat,
    zIndex: Int,
    spacing: UIEdgeInsets,
    insets: UIEdgeInsets)
}

Default:

.visible(
  height: 4,
  zIndex: Int.max,
  spacing: UIEdgeInsets.zero,
  insets: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8))

indicatorClass

The class type for the indicator view. Override this if you want your use your own subclass of PagingIndicatorView.

Default: PagingIndicatorView.self

indicatorColor

The background color for the indicator view.

Default: UIColor(red: 3/255, green: 125/255, blue: 233/255, alpha: 1)

borderOptions

Add a border at the bottom of the menu items. The border will be as wide as all the menu items. Insets only apply horizontally.

enum PagingBorderOptions {
  case hidden
  case visible(
    height: CGFloat,
    zIndex: Int,
    insets: UIEdgeInsets)
}

Default:

.visible(
  height: 1,
  zIndex: Int.max - 1,
  insets: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8))

borderClass

The class type for the border view. Override this if you want your use your own subclass of PagingBorderView.

Default: PagingBorderView.self

borderColor

The background color for the border view.

Default: UIColor(white: 0.9, alpha: 1)

includeSafeAreaInsets

Updates the content inset for the menu items based on the .safeAreaInsets property.

Default: true

font

The font used for title label on the menu items.

Default: UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)

selectedFont

The font used for title label on the currently selected menu item.

Default: UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)

textColor

The color of the title label on the menu items.

Default: UIColor.black

selectedTextColor

The text color for the currently selected menu item.

Default: UIColor(red: 3/255, green: 125/255, blue: 233/255, alpha: 1)

backgroundColor

The background color for the menu items.

Default: UIColor.white

selectedBackgroundColor

The background color for the selected menu item.

Default: UIColor.clear

menuBackgroundColor

The background color for the view behind the menu items.

Default: UIColor.white

Installation

Parchment will be compatible with the lastest public release of Swift.

Requirements

  • iOS 12.0+
  • Xcode 14.0+

CocoaPods

Parchment is available through CocoaPods. To install it, add the following to your Podfile:

pod 'Parchment', '~> 4.0'

Swift Package Manager

Parchment is available through Swift Package Manager. Add Parchment as a dependency to your Package.swift:

.package(url: "https://github.com/rechsteiner/Parchment", from: "4.0.0")

Carthage

Parchment also supports Carthage. To install it, add the following to your Cartfile:

github "rechsteiner/Parchment" ~> 4.0

See this guide for more details on using Carthage.

Changelog

This can be found in the CHANGELOG file.

Licence

Parchment is released under the MIT license. See LICENSE for details.