A paging view controller with a highly customizable menu
Clone or download
Latest commit 1ac102a Nov 18, 2018
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci Update Xcode version for CircleCI Aug 11, 2018
CalendarExample Upgrade to Swift 4.2 Aug 11, 2018
DelegateExample Upgrade to Swift 4.2 Aug 11, 2018
Documentation Update README.md Jan 29, 2018
Example Upgrade to Swift 4.2 Aug 11, 2018
HeaderExample Add header view scroll example Nov 18, 2018
IconsExample Upgrade to Swift 4.2 Aug 11, 2018
LargeTitlesExample Add example of usage with UINavigationBar's large titles Aug 22, 2018
NavigationBarExample Upgrade to Swift 4.2 Aug 11, 2018
Parchment.xcodeproj Add header view scroll example Nov 18, 2018
Parchment Include vertical insets for indicator view Nov 3, 2018
ParchmentTests Fix issue with width delegate not being called Aug 24, 2018
ScrollExample Add scrolling menu example Aug 23, 2018
StoryboardExample Upgrade to Swift 4.2 Aug 11, 2018
UnplashExample Add header view scroll example Nov 18, 2018
.gitignore Initial commit Mar 27, 2016
.swift-version Upgrade to Swift 4.2 Aug 11, 2018
CHANGELOG.md Update CHANGELOG.md Nov 18, 2018
Cartfile Remove Cartography dependency Mar 28, 2016
Cartfile.private Upgrade Quick and Nimble to latest versions Aug 11, 2018
Cartfile.resolved Upgrade Quick and Nimble to latest versions Aug 11, 2018
Gemfile Use bundler to specify a newer version of scan Sep 30, 2016
Gemfile.lock Update scan to lastest version Sep 29, 2018
LICENSE Add MIT license May 22, 2016
Parchment.podspec.json Release v1.5.0 Nov 18, 2018
README.md [README] Add `selectedFont` option Oct 22, 2018

README.md

Getting Started | Customization | Installation


Features

Parchment is a very flexible paging view controller. It let’s 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:

  • 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.

  • 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.

Getting Started

Basic usage

The easiest way to use Parchment is to use the FixedPagingViewController subclass. Just pass in an array of view controllers and Parchment will generate menu items for each view controller using their title property:

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

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

See: Using FixedPagingViewController

Data source

Using FixedPagingViewController 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<T>(in pagingViewController: PagingViewController<T>) -> Int {
        return 10
    }

    func pagingViewController<T>(_ pagingViewController: PagingViewController<T>, viewControllerForIndex index: Int) -> UIViewController {
        return ItemViewController(index: index)
    }

    func pagingViewController<T>(_ pagingViewController: PagingViewController<T>, pagingItemForIndex index: Int) -> PagingItem {
        return PagingIndexItem(index: index, title: "View \(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 FixedPagingViewController if you have many view controllers.

See: Using custom data source

Infinite data source

Using the PagingViewControllerDataSource protocol 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<T>(_ pagingViewController: PagingViewController<T>, viewControllerForPagingItem pagingItem: T) -> UIViewController {
    return ItemViewController(item: pagingItem)
  }

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

  func pagingViewController<T>(_ pagingViewController: PagingViewController<T>, pagingItemAfterPagingItem pagingItem: T) -> T? {
    guard let item = pagingItem as? IndexedPagingItem 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.

See: Using infinite data source

Selecting items

You can select items programatically using:

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

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

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

Reload 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.

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. You can also use this to manually control the width of your menu items.

protocol PagingViewControllerDelegate: class {

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

  func pagingViewController<T>(
    _ pagingViewController: PagingViewController<T>,
    willScrollToItem pagingItem: T,
    startingViewController: UIViewController,
    destinationViewController: UIViewController)

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

  func pagingViewController<T>(
    _ pagingViewController: PagingViewController<T>,
    widthForPagingItem pagingItem: T,
    isSelected: Bool) -> CGFloat?
}

Customization

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

menuItemSize

The size for each of the menu items.

enum PagingMenuItemSize {
  case fixed(width: 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)

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

menuItemClass

The class type for the menu item. Override this if you want your own custom menu items.

Default: PagingTitleCell.self

menuItemSpacing

The spacing between the menu items.

Default: 0

menuInsets

The insets around all of the menu items.

Default: UIEdgeInsets()

selectedScrollPosition

The scroll position of the selected menu item:

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 a 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

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.

CocoaPods

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

pod 'Parchment'

Carthage

Parchment also supports Carthage. To install it, you need to do the following steps:

  1. Add github "rechsteiner/Parchment" to your Cartfile
  2. Run carthage update
  3. Link Parchment.framework with you target
  4. Add $(SRCROOT)/Carthage/Build/iOS/Parchment.framework to your copy-frameworks script phase

See this guide for more details on using Carthage.

Requirements

  • iOS 8.2+
  • Xcode 8.0+

Acknowledgements

Changelog

This can be found in the CHANGELOG file.

Licence

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