iOS SDK: Using a Slider to Scrub a PDF Reader

This tutorial is a continuation of the War of the Worlds iPad reader project and will demonstrate how to navigate a PDF with a UISlider when using the Leaves project. Along the way, we'll make a few aesthetic changes to make the interface a bit more immersive.

Where We Left Off

In last week's tutorial, I introduced you to the Leaves open source project and demonstrated how to setup a basic PDF reader. However, the basic Leaves implementation left a lot to be desired from a user experience standpoint. Specifically, I suggested the following enhancements:

  • Table of Contents
  • UISlider for Navigation
  • Bookmarks
  • Search
  • Highlights

60% of the poll participants voted to see additional tutorials on either adding a Table of Contents or a UISlider, so that's what we'll be accomplishing today. Another poll has been included in this post, so if you'd like to see additional features added to this project or would prefer that I move on to another iOS SDK topic, let me know!

Tutorial Preview

This is a video demo of what this tutorial will create:

At the end of this tutorial, you should have a good understanding of how the UISlider object works and a better understanding of the Leaves project internals.

Step 1: Add the Space Background

We will begin by preparing the interface to have a UISlider. I experimented with a few different approaches here, but in the end I decided to settle on a design I haven't seen anywhere else: I shrunk the book display, centered it in the middle of the screen, and then added a backdrop of some distant galaxy for a thematic effect. I then simply centered the UISlider underneath the book. I actually really like the way this turned out, but I willingly admit that this isn't the most practical approach. When building a reader application it makes sense for the text to cover the full screen as it did in our last project build, but the plus side to adding some padding around the book is that you can potentially build a more immersive UI. That's what I've tried to accomplish here.

To do the same, open the WOTWViewController.xib file. Drag a UIImage onto the iPad view, and size that image to fill the entire screen (make sure the view is set to Portrait mode). Next, select the Attributes Inspector for the UIImage and set the image field to "space.png" (you can find this file in the "Resources" folder of this post's download). We now have a much cooler background image for the project. This could very easily be customized for a genre other than Science Fiction.

Step 2: Add the Navigation Slider

Next, drag a UISlider object onto the view. With the UISlider selected, go to the "Size Inspector". Set the width of the object to 360, the X position to 204, and the Y position to 955. The UISlider should now be positioned near the bottom of the screen, and just underneath where the book will be displayed.

Step 3: Create an IBOutlet for the Slider

At this point we need to sync the UISlider in Interface Builder with a property in our WOTW view controller. With the XIB file still open, click the Assistant Editor tab in the Xcode toolbar. Doing so will open an editor window that should contain the WOTWViewController.h file (if it contains a different file, select the correct one from the "Related Files" icon in the top left of the editor pane). Now, CTRL+Click the UISlider in the XIB file and drag the line that appears over the editor window pane. Release when the pop over reads "Insert Outlet, Action, or Outlet Collection". A dialogue will appear prompting you for a name for the IBOutlet connection. Name the outlet pageSlider and click connect. Interface Builder will now add the code necessary for this outlet to be used in your project.

Step 4: Resize the Book Display

As mentioned in the first tutorial in this series, the LeavesViewController class contains a UIView called leavesView where the page drawing actually occurs. The frame of leavesView is set to mirror the LeavesViewController in the loadView method, as shown below:

LeavesViewController.m

In our case, we want the leavesView frame to only fill a subset of the view controller, not the whole screen.

We have a couple of options here. The easiest solution would seem to be simply changing the size of the leavesView frame manually on line 3 above in the LeavesViewController.m file. However, you'll recall that LeavesViewController is part of the official Leaves project code and all of our changes so far have been made in WOTWViewController, which is a subclass of LeavesViewController. This is generally a far more maintainable approach than the alternative: hacking around in the core project code for needs specific to your situation, and then continuously fighting with project updates by repetitively merging or rewriting community changes. In such a scenario, you will find yourself willfully neglecting the latest stable builds of the project. You don't want to find yourself stuck in this situation.

So, what's a better alternative? Because we have inherited the leavesView object in WOTWViewController, we can just make our changes in the -(void)viewDidLoad method.

In WOTWViewController.m, add the following lines of code:

On line 3 above we call the LeavesViewController implementation of loadView, and then we customize the leavesView frame on our own. Line 4 sets the frame to a width and height that I found suitable, and line 5 centers the frame in the middle of our WOTW view controller.

NOTE: Are you wondering why I'm using funky syntax to access the leavesView object? There seems to be a bug in GCC 4.2.1 that requires this. Comments with further insight much appreciated.

If you build and run the project now, you should see the WOTW reader in the center of the screen with a slider underneath for navigation. Of course, the slider doesn't work yet, so let's keep rolling!

Step 5: Initialize the Slider

When our application launches we want to set the minimum, maximum, and current slider values based on the PDF loaded for the app. We also need to specify what should happen when the slider value changes. We'll do this in the WOTWViewController.m file with the following lines of code:

Line 8 above sets a selector that should be called when the slider value changes. By default, the selector will be called continuously as the slider button moves. However, you can disable this by setting the slider's continuous value to "NO", which will cause the selector to only be called after the slider button is released.

Line 9 above sets the minimum value to 0. This is appropriate because the PDF in Leaves is referenced with a 0-based index.

Line 10 above calls the numberOfPagesInLeavesView: method to set the slider's maximum value, and adjusts for a 0 based index by subtracting 1 from the result.

Finally, line 11 sets the current value of the slider to the leavesView property currentPageIndex.

Step 6: Add Page Scrubbing

We'll now write the logic that should occur when the turnPageWithSlider: selector is called.

Add the following code into WOTW implementation file:

The value returned from a UISlider is of the float data type. On line 3 above we typecast this value to an integer and store it in the pageIndex variable.

On line 4, we do the reverse: we typecast pageIndex to a float and then update the value of the slider. What's the point? Isn't this redundant? No, because when we typecast the slider value to an integer we cut off the remainder. This is important because we don't want to turn, for example, to page 1.23 or 20.56, we want to turn to page 1 or 20. This step forces the user to traverse the PDF in what is likely the expected manner.

Line 5 above sets the leavesView property currentPage, which will also automatically force an update of the book display.

If you build and run the project now, you should be able to scrub through the book. However, there's one important detail missing: if you turn pages by manually dragging them, the slider value stays the same. For this we'll need to tap into the LeavesView delegate.

Step 7: Sync the Slider

The custom Leaves view provides several delegate methods that are called at key points in the animation. One of those is leavesView:didTurnToPageAtIndex:. Add the following logic to update the slider when this delegate method is called:

With the above code in place, our slider implementation should be complete!

Should I Continue This Series?

As I mentioned at the beginning of this tutorial, there are still many features that could be added to this project. If you'd like me to continue this series, vote for the feature you'd like to see next below. Otherwise, you can vote for me to move on to a completely different iOS SDK topic (feel free to suggest one in the comments section). The poll will close on Saturday morning, September 10th.



Read the Next Tutorial in this Series

Tags:

Comments

Related Articles