|
| 1 | +# Navigator |
| 2 | + |
| 3 | +You can use a Readium Navigator to present the publication to the user. The `Navigator` renders resources on the screen and offers APIs and user interactions for navigating the contents. |
| 4 | + |
| 5 | +:warning: Navigators do not have user interfaces besides the view that displays the publication. Applications are responsible for providing a user interface with bookmark buttons, a progress bar, etc. |
| 6 | + |
| 7 | +## Default implementations |
| 8 | + |
| 9 | +The Readium toolkit comes with several `Navigator` implementations for different publication profiles. Some are `UIViewController`s, designed to be added to your view hierarchy, while others are chromeless and can be used in the background. |
| 10 | + |
| 11 | +| Navigator | Profile | Formats | |
| 12 | +|-------------------------------|-------------|-----------------------------------------------------------------------| |
| 13 | +| `EPUBNavigatorViewController` | `epub` | EPUB (`.epub`), Readium Web Publication (`.webpub`) | |
| 14 | +| `PDFNavigatorViewController` | `pdf` | PDF (`.pdf`), LCP-protected PDF (`.lcpdf`) | |
| 15 | +| `CBZNavigatorViewController` | `divina` | Zipped Comic Book (`cbz`), Readium Divina (`.divina`) | |
| 16 | +| `AudioNavigator` | `audiobook` | Zipped Audio Book (`.zab`), Readium Audiobook (`.audiobook`, `.lcpa`) | |
| 17 | + |
| 18 | +To find out which Navigator is compatible with a publication, refer to its [profile](https://readium.org/webpub-manifest/profiles/). Use `publication.conformsTo()` to identify the publication's profile. |
| 19 | + |
| 20 | +```swift |
| 21 | +if publication.conformsTo(.epub) { |
| 22 | + let navigator = try EPUBNavigatorViewController( |
| 23 | + publication: publication, |
| 24 | + initialLocation: lastReadLocation, |
| 25 | + httpServer: GCDHTTPServer.shared |
| 26 | + ) |
| 27 | + |
| 28 | + hostViewController.present(navigator, animated: true) |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +## Navigator APIs |
| 33 | + |
| 34 | +Navigators implement a set of shared interfaces to help reuse the reading logic across publication profiles. For example, instead of using specific implementations like `EPUBNavigatorViewController`, use the `Navigator` interface to create a location history manager compatible with all Navigator types. |
| 35 | + |
| 36 | +You can create custom Navigators and easily integrate them into your app with minimal modifications by implementing these interfaces. |
| 37 | + |
| 38 | +### `Navigator` interface |
| 39 | + |
| 40 | +All Navigators implement the `Navigator` interface, which provides the foundation for navigating resources in a `Publication`. You can use it to move through the publication's content or to find the current position. |
| 41 | + |
| 42 | +Note that this interface does not specify how the content is presented to the user. |
| 43 | + |
| 44 | +### `VisualNavigator` interface |
| 45 | + |
| 46 | +Navigators rendering the content visually on the screen implement the `VisualNavigator` interface. This interface offers details about the presentation style (e.g., scrolled, right-to-left, etc.) and allows monitoring input events like taps or keyboard strokes. |
| 47 | + |
| 48 | +### `SelectableNavigator` interface |
| 49 | + |
| 50 | +Navigators enabling users to select parts of the content implement `SelectableNavigator`. You can use it to extract the `Locator` and content of the selected portion. |
| 51 | + |
| 52 | +### `DecorableNavigator` interface |
| 53 | + |
| 54 | +A Decorable Navigator is able to render decorations over a publication, such as highlights or margin icons. |
| 55 | + |
| 56 | +[See the corresponding proposal for more information](https://readium.org/architecture/proposals/008-decorator-api.html). |
| 57 | + |
| 58 | +## Instantiating a Navigator |
| 59 | + |
| 60 | +### Visual Navigators |
| 61 | + |
| 62 | +The Visual Navigators are implemented as `UIViewController` and must be added to your iOS view hierarchy to render the publication contents. |
| 63 | + |
| 64 | +```swift |
| 65 | +let navigator = try EPUBNavigatorViewController( |
| 66 | + publication: publication, |
| 67 | + initialLocation: lastReadLocation, |
| 68 | + httpServer: GCDHTTPServer.shared |
| 69 | +) |
| 70 | + |
| 71 | +hostViewController.present(navigator, animated: true) |
| 72 | +``` |
| 73 | + |
| 74 | +:point_up: The HTTP server is used to serve the publication resources to the Navigator. You may use your own implementation, or the recommended `GCDHTTPServer` which is part of the `ReadiumAdapterGCDWebServer` package. |
| 75 | + |
| 76 | +### Audio Navigator |
| 77 | + |
| 78 | +The `AudioNavigator` is chromeless and does not provide any user interface, allowing you to create your own custom UI. |
| 79 | + |
| 80 | +```swift |
| 81 | +let navigator = AudioNavigator( |
| 82 | + publication: publication, |
| 83 | + initialLocation: lastReadLocation |
| 84 | +) |
| 85 | + |
| 86 | +navigator.play() |
| 87 | +``` |
| 88 | + |
| 89 | +## Navigating the contents of the publication |
| 90 | + |
| 91 | +The `Navigator` interface offers various `go` APIs for navigating the publication. For instance: |
| 92 | + |
| 93 | +* to the previous or next pages: `navigator.goForward()` or `navigator.goBackward()` |
| 94 | +* to a link from the `publication.tableOfContents` or `publication.readingOrder`: `navigator.go(to: link)` |
| 95 | +* to a locator from a search result: `navigator.go(to: locator)` |
| 96 | + |
| 97 | +## Reading progression |
| 98 | + |
| 99 | +### Saving and restoring the last read location |
| 100 | + |
| 101 | +Navigators don't store any data permanently. Therefore, it is your responsibility to save the last read location in your database and restore it when creating a new Navigator. |
| 102 | + |
| 103 | +You can observe the current position in the publication by implementing a `NavigatorDelegate`. |
| 104 | + |
| 105 | +```swift |
| 106 | +navigator.delegate = MyNavigatorDelegate() |
| 107 | + |
| 108 | +class MyNavigatorDelegate: NavigatorDelegate { |
| 109 | + |
| 110 | + override func navigator(_ navigator: Navigator, locationDidChange locator: Locator) { |
| 111 | + if let position = locator.locations.position { |
| 112 | + print("At position \(position) on \(publication.positions.count)") |
| 113 | + } |
| 114 | + if let progression = locator.locations.progression { |
| 115 | + return "Progression in the current resource: \(progression)%" |
| 116 | + } |
| 117 | + if let totalProgression = locator.locations.totalProgression { |
| 118 | + return "Total progression in the publication: \(progression)%" |
| 119 | + } |
| 120 | + |
| 121 | + // Save the position in your bookshelf database |
| 122 | + database.saveLastReadLocation(locator.jsonString) |
| 123 | + } |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +The `Locator` object may be serialized to JSON in your database, and deserialized to set the initial location when creating the navigator. |
| 128 | + |
| 129 | +```swift |
| 130 | +let lastReadLocation = Locator(jsonString: dabase.lastReadLocation()) |
| 131 | + |
| 132 | +let navigator = try EPUBNavigatorViewController( |
| 133 | + publication: publication, |
| 134 | + initialLocation: lastReadLocation, |
| 135 | + httpServer: GCDHTTPServer.shared |
| 136 | +) |
| 137 | +``` |
| 138 | + |
| 139 | +### Bookmarking the current location |
| 140 | + |
| 141 | +Use a Navigator's `currentLocation` property to persists the current position, for instance as a bookmark. |
| 142 | + |
| 143 | +After the user selects a bookmark from your user interface, navigate to it using `navigator.go(bookmark.locator)`. |
| 144 | + |
| 145 | +### Displaying a progression slider |
| 146 | + |
| 147 | +To display a percentage-based progression slider, use the `locations.totalProgression` property of the `currentLocation`. This property holds the total progression across an entire publication. |
| 148 | + |
| 149 | +Given a progression from 0 to 1, you can obtain a `Locator` object from the `Publication`. This can be used to navigate to a specific percentage within the publication. |
| 150 | + |
| 151 | +```swift |
| 152 | +if let locator = publication.locate(progression: 0.5) { |
| 153 | + navigator.go(to: locator) |
| 154 | +} |
| 155 | +``` |
| 156 | + |
| 157 | +### Displaying the number of positions |
| 158 | + |
| 159 | +:warning: Readium does not have the concept of pages, as they are not useful when dealing with reflowable publications across different screen sizes. Instead, we use [**positions**](https://readium.org/architecture/models/locators/positions/) which remain stable even when the user changes the font size or device. |
| 160 | + |
| 161 | +Not all Navigators provide positions, but most `VisualNavigator` implementations do. Verify if `publication.positions` is not empty to determine if it is supported. |
| 162 | + |
| 163 | +To find the total positions in the publication, use `publication.positions.count`. You can get the current position with `navigator.currentLocation?.locations.position`. |
| 164 | + |
| 165 | +## Navigating with edge taps and keyboard arrows |
| 166 | + |
| 167 | +Readium provides a `DirectionalNavigationAdapter` helper to turn pages using arrow and space keys or screen taps. |
| 168 | + |
| 169 | +You can use it from your `VisualNavigatorDelegate` implementation: |
| 170 | + |
| 171 | +```swift |
| 172 | +extension MyReader: VisualNavigatorDelegate { |
| 173 | + |
| 174 | + func navigator(_ navigator: VisualNavigator, didTapAt point: CGPoint) { |
| 175 | + // Turn pages when tapping the edge of the screen. |
| 176 | + guard !DirectionalNavigationAdapter(navigator: navigator).didTap(at: point) else { |
| 177 | + return |
| 178 | + } |
| 179 | + |
| 180 | + toggleNavigationBar() |
| 181 | + } |
| 182 | + |
| 183 | + func navigator(_ navigator: VisualNavigator, didPressKey event: KeyEvent) { |
| 184 | + // Turn pages when pressing the arrow keys. |
| 185 | + DirectionalNavigationAdapter(navigator: navigator).didPressKey(event: event) |
| 186 | + } |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +`DirectionalNavigationAdapter` offers a lot of customization options. Take a look at its API. |
| 191 | + |
| 192 | +## User preferences |
| 193 | + |
| 194 | +Readium Navigators support user preferences, such as font size or background color. Take a look at [the Preferences API guide](Preferences.md) for more information. |
0 commit comments