Parchment allows you to page between view controllers while showing menu items that scrolls along with the content. It’s build to be very customizable, it’s well-tested and written fully in Swift.
The easiest way to use Parchment is to use FixedPagingViewController
. Just pass in an array of view controllers and it will set up everything for you.
let firstViewController = UIViewController()
let secondViewController = UIViewController()
let pagingViewController = FixedPagingViewController(viewControllers: [
firstViewController,
secondViewController
])
Then add the paging view controller to you view controller:
addChildViewController(pagingViewController)
view.addSubview(pagingViewController.view)
pagingViewController.didMoveToParentViewController(self)
Parchment will then generate menu items for each view controller using their title property. You can customize how the menu items will look, or even create your completely custom subclass. See Customization.
Check out ViewController.swift
in the Example target for more details.
Parchment supports adding your own custom data sources. This allows you to allocate view controllers only when they are needed, and can even be used to create infinitly scrolling data sources ✨
To add your own data source, you need to conform to the PagingViewControllerDataSource
protocol:
public protocol PagingViewControllerDataSource: class {
func pagingViewController<T>(pagingViewController: PagingViewController<T>,
viewControllerForPagingItem: T) -> UIViewController
func pagingViewController<T>(pagingViewController: PagingViewController<T>,
pagingItemBeforePagingItem: T) -> T?
func pagingViewController<T>(pagingViewController: PagingViewController<T>,
pagingItemAfterPagingItem: T) -> T?
}
If you've ever used UIPageViewController this should seem familiar. Instead of returning view controllers directly, you return a object conforming to PagingItem
. PagingItem
is used to generate menu items for all the view controllers, without having to actually allocate them before they are needed.
Let’s take a look at an example of how you can create your own calendar data source. This is what we want to achieve:
First thing we need to do is create our own PagingItem
that will hold our date. We also need to make sure it conforms to Equatable
.
struct CalendarItem: PagingItem, Equatable {
let date: NSDate
}
func ==(lhs: CalendarItem, rhs: CalendarItem) -> Bool {
return lhs.date == rhs.date
}
We need to conform to PagingViewControllerDataSource
in order to implement our custom data source. Every time pagingItemBeforePagingItem:
or pagingItemAfterPagingItem:
is called, we either subtract or append the time interval equal to one day. This means our paging view controller will show one menu item for each day.
extension ViewController: PagingViewControllerDataSource {
func pagingViewController<T>(pagingViewController: PagingViewController<T>,
viewControllerForPagingItem pagingItem: T) -> UIViewController {
let calendarItem = pagingItem as! CalendarItem
return CalendarViewController(date: calendarItem.date)
}
func pagingViewController<T>(pagingViewController: PagingViewController<T>,
pagingItemBeforePagingItem pagingItem: T) -> T? {
let calendarItem = pagingItem as! CalendarItem
return CalendarItem(date: calendarItem.date.dateByAddingTimeInterval(-86400)) as? T
}
func pagingViewController<T>(pagingViewController: PagingViewController<T>,
pagingItemAfterPagingItem pagingItem: T) -> T? {
let calendarItem = pagingItem as! CalendarItem
return CalendarItem(date: calendarItem.date.dateByAddingTimeInterval(86400)) as? T
}
}
Then we simply need to create our PagingViewController
and specify our custom PagingItem
:
let pagingViewController = PagingViewController<CalendarItem>()
pagingViewController.dataSource = self
That’s all you need to create your own infinitely paging calendar ✨🚀
Check out the Calendar Example target for more details.
You can use the PagingViewControllerDelegate
to manually control the width of your menu items. Parchment does not support self-sizing cells at the moment, so you have to use this if you have a custom cell that you want to size based on its content.
protocol PagingViewControllerDelegate: class {
func pagingViewController<T>(pagingViewController: PagingViewController<T>,
widthForPagingItem pagingItem: T) -> CGFloat
}
Parchment is build to be very flexible. All customization is handled by the PagingOptions
protocol. Just create your own struct that conforms to this protocol, and override the values you want.
protocol PagingOptions {
var menuItemSize: PagingMenuItemSize { get }
var menuItemClass: PagingCell.Type { get }
var menuItemSpacing: CGFloat { get }
var menuInsets: UIEdgeInsets { get }
var menuHorizontalAlignment: PagingMenuHorizontalAlignment { get }
var selectedScrollPosition: PagingSelectedScrollPosition { get }
var indicatorOptions: PagingIndicatorOptions { get }
var borderOptions: PagingBorderOptions { get }
var theme: PagingTheme { get }
}
If you have any requests for addional customizations, issues and pull-requests are very much welcome 🙌.
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)
The class type for the menu item. Override this if you want your own custom menu items.
Default: PagingTitleCell.self
The spacing between the menu items.
Default: 0
The insets around all of the menu items.
Default: UIEdgeInsets()
public 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
Determine the transition behaviour of menu items while scrolling the content.
public 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
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
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, insets: UIEdgeInsets)
}
Default:
.Visible(
height: 4,
zIndex: Int.max,
insets: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8))
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))
The visual theme of the paging view controller.
protocol PagingTheme {
var font: UIFont { get }
var textColor: UIColor { get }
var selectedTextColor: UIColor { get }
var backgroundColor: UIColor { get }
var headerBackgroundColor: UIColor { get }
var borderColor: UIColor { get }
var indicatorColor: UIColor { get }
}
Default:
extension PagingTheme {
var font: UIFont {
return UIFont.systemFontOfSize(15, weight: UIFontWeightMedium)
}
var textColor: UIColor {
return UIColor.blackColor()
}
var selectedTextColor: UIColor {
return UIColor(red: 3/255, green: 125/255, blue: 233/255, alpha: 1)
}
var backgroundColor: UIColor {
return UIColor.whiteColor()
}
var headerBackgroundColor: UIColor {
return UIColor.whiteColor()
}
var indicatorColor: UIColor {
return UIColor(red: 3/255, green: 125/255, blue: 233/255, alpha: 1)
}
var borderColor: UIColor {
return UIColor(white: 0.9, alpha: 1)
}
}
Parchment will be compatible with the lastest public release of Swift. Older releases will be available, but bug fixes won’t be issued.
- Add
github "rechsteiner/Parchment"
to yourCartfile
- Run
carthage update
- Link
Parchment.framework
with you target - Add
$(SRCROOT)/Carthage/Build/iOS/Parchment.framework
to yourcopy-frameworks
script phase
- Add
pod "Parchment"
to yourPodfile
- Make sure
use_frameworks!
is included. Adding this means your dependencies will be included as a dynamic framework (this is necessary since Swift cannot be included as a static library). - Run
pod install
A big thanks to @emalyak for the EMPageViewController
library 🙌
Parchment is released under the MIT license. See LICENSE for details.