The Document View Hierarchy

Getting Started

This guide discusses the view hierarchy used by PSPDFKit to display document and the API that can be used to customize the presentation of documents.

Controlling the Document

There are a few simple convenience methods available for you on PSPDFViewController. These cover the most common tasks while hiding the underlying complexity of documents and how they are visualized onscreen. For basic controls such as changing a page, these APIs might be all you need. However, if you want to have more fine-grained control, PSPDFDocumentViewController offers the full set of APIs and can be accessed via PSPDFViewController’s documentViewController property.

Pages vs. Spreads

The most fundamental difference between the methods on PSPDFViewController and PSPDFDocumentViewController is that the main view controller deals with pages and page indexes whereas the document view controller deals with spreads and spread indexes. A spread is a collection of pages that are always viewed together. If you have the most basic layout, a layout with its spread mode set to single, every spread corresponds to a single page in the document, which means the number of spreads is equal to the number of pages. However you can also have a double-paged layout or a book layout. PSPDFDocumentViewLayout provides methods to convert between pages and spreads: spreadIndexForPageAtIndex: and pageRangeForSpreadAtIndex:. To make your code independent of the layout you use, use these methods for conversion and, in case you want to implement your own spreading algorithms, override these methods and make them do the proper conversion, otherwise your layout may not work correctly in all cases.

Because a spread can have multiple pages, there is not always a single page presented to the user. Imagine a book, where the reader is not looking at a single page but at two pages at a time. Therefore using methods like -[PSPDFViewController pageIndex] doesn't give you the full picture. While this is very convenient and works for many scenarios, keep in mind that the returned page might only be one of multiple pages. If you need to get the full picture, use -[PSPDFDocumentViewController spreadIndex].

The number of pages per spread can vary. If you are using PSPDFDocumentViewLayoutSpreadModeBook, the first page will be treated as a cover and therefore will always be the only page in the first spread, while the following pages will be grouped together in groups of two. Also when using PSPDFDocumentViewLayoutSpreadModeBook or PSPDFDocumentViewLayoutSpreadModeDouble, the last spread might only contain one page depending on the number of pages in the document.

Customizing the Layout

There are two sets of APIs to support customizing the layout of a document. The high-level one is modeled by PSPDFConfiguration and contains the very basic customization options. This should be enough for most projects. However, if you want to have more control over the layout, PSPDFDocumentViewController is available on PSPDFViewController. This is the go-to API for making detailed changes to how the layout works.

These two concepts can not be mixed and matched. Either you alter the layout through PSPDFConfiguration or you set your own layout instance on PSPDFDocumentViewController. The options in the configuration only have an effect if the document view controller derives the layout from the configuration object. As soon as you set your own layout, the document view controller stops monitoring the configuration for layout related properties. This is also true the other way around: while a layout is derived from the configuration, the document view controller might refresh this layout and update it at any given time, so you should not change properties on that layout as these changes might be overridden at any time. As a general rule, only modify properties of layout objects that you created yourself.

For configuring your layout through PSPDFConfiguration, please check out the documentation of that class. This guide will mostly deal with how to implement your own layouts.

Subclassing

PSPDFDocumentViewLayout is an abstract base class meant for subclassing. You can subclass either PSPDFContinuousScrollingLayout or PSPDFScrollPerSpreadLayout if you want a layout similar to these two with only a few tweaks. If you want to have more control but your layout still follows the general idea of a layout that scrolls in a single direction — either vertically or horizontally — PSPDFStackViewLayout, the superclass of the two aforementioned layouts, gives you a lot of control while covering the basics. It also provides some convenience methods that make your life easier. This should be enough control for almost all designs. However, if your design is very specific, we also give you the same base class all of our own layouts use: PSPDFDocumentViewLayout. With this layout, you need to do everything on your own but there are almost no limitations on what to do with it.

Aside from customizing the layout, you can also customize other parts of the view hierarchy. The document view controller, the spread view and the page view all allow subclassing which can be used to customize the experience even further. For example you could customize PSPDFSpreadView and precisely control the frame of each page view inside it.

UICollectionViewLayout

A document view layout is based on UICollectionViewLayout and this is what is used to calculate the position of a spread on the screen. PSPDFDocumentViewLayout and especially its subclass PSPDFStackViewLayout offer you convenience methods that hide a lot of the complexity of collection views but depending on how the layout you are building should look, keep in mind that all the collection view layout methods can be used as well.

While the collection view layout works with index paths as its item identifying object, the document view layout does not need multiple levels of indexes; each layout only needs to deal with one section. The important identifier for a document view layout is the spread index of the item it is representing. Therefore, all the methods a PSPDFDocumentViewLayout and its subclasses offer refer to the spread index instead of an index path. For most layouts, you will not come into contact with any of the index path-based methods, but in case you do need them, PSPDFKit offers two new methods on NSIndexPath for easily converting between index paths and spread indexes: +[NSIndexPath pspdf_indexPathForSpreadAtIndex:] and -[NSIndexPath pspdf_spreadIndex]. You should always use these methods to convert back and forth between spread indexes and index paths and not make any assumptions about how an index path maps to a spread index.

Additional APIs

Aside from the collection view layout, a document view layout also offers a few additional things that the document view controller and its views use to determine other behaviors, such as how zooming behaves (spreadBasedZooming), how spreads map to pages, and how the actual view hierarchy is positioned in relation to the view controller's view (scrollViewFrameInsets).

Using the methods PSPDFDocumentViewLayout and its subclasses offer is recommended over overriding the collection view layout methods. If you override one of the collection view layout methods, it is up to you to make sure that other methods such as spreadIndexForPageAtIndex:, pageRangeForSpreadAtIndex:, continuousSpreadIndexForContentOffset:, or contentOffsetForContinuousSpreadIndex: don't return conflicting values or otherwise you might get unexpected results.

Scrolling and Zooming

This is what the view hierarchy looks like:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 ╔═══════════════════════════╗         ┌───────────────────────────┐
 ║  DocumentViewController   ║         │  PSPDFDocumentViewLayout  │
 ║                           ║────────▶│                           │
 ╚═══════════════════════════╝         └───────────────────────────┘
 ┌───────────────────────────┐
 │       UIScrollView        │
 │   (scrolling & zooming)   │◀───┐      If spreadBasedZooming is
 └───────────────────────────┘    │      YES the bottom of these 
 ┌───────────────────────────┐    ├────  scroll views is responsible
 │       UIScrollView        │    │      for zooming the content,
 │         (zooming)         │◀───┘      otherwise the top one is.
 └───────────────────────────┘
 ╔═══════════════════════════╗
 ║        SpreadView         ║
 ║                           ║
 ╚═══════════════════════════╝
 ╔═══════════════════════════╗
 ║         PageView          ║
 ║                           ║
 ╚═══════════════════════════╝

 ┌─────────────────────────────────────────────────────────────────┐
 │               ─── Public, ═══ Public & Subclassable             │
 └─────────────────────────────────────────────────────────────────┘

There are two levels of scroll views. The outer scroll view is always the one that is responsible for scrolling (i.e. changing between pages). If the layout's spreadBasedZooming is NO, the outer scroll view also is responsible for zooming, otherwise this is taken over by the inner scroll view. The difference is that each spread view is contained in its own inner scroll view. Switching spreadBasedZooming therefore controls if the user zooms the full document all the time (this behavior can be seen in the continuous scrolling layout) or if the user only zooms a single spread, leaving the zoom level of the other pages unchanged (this behavior can be seen in the scroll per spread layout).

All the positions that PSPDFDocumentViewLayout calculates are the positions that the inner scroll views (and therefore the spread views) have in the outer scroll view. Just think about the layout as the one that calculates which spread goes where, just that every spread is then wrapped with a scroll view (the inner scroll view) to make it zoomable in spread based zooming mode.

Insets

When used in the right combination, changing insets can be a very simple and powerful way to customize your layout. scrollViewFrameInsets determines the insets the outer scroll view's frame gets relative to the document view controller's view. PSPDFStackViewLayout also offers contentInsets which is used to inset the positions of the actual items inside the outer scroll view. These two are what makes it possible for the scroll per spread layout to make the outer scroll view paginated but still show a gap between two spreads. It uses the scrollViewFrameInsets to expand the scroll view's frame to make the pagination size bigger and then uses the contentInsets to move the actual spread views into the right place.