0
votes

I am building a pdf viewer app. my skills are at medium LOL voodoo level now... I've got real far on my own with this app but I'm pretty stuck ether on my theory or on my code. My app has a paging scroll view that makes its self the length of the entire pdf document, then it shows the current page in another scrollview (ImageScrollView) which is its own class. ImageScrollView makes a UIView which does the CATiledLayer usual stuff and it all work fine! :)

My ImageScrollView shows the on-screen page (when you scroll to the next page ImageScrollView loads again CATiledLayer on-screen and you can see the tiles). I've been researching about how to get the pages left and right of the current page to preload (as I don't think having a pdf load as tiles on-screen is good for the user experience) but Im not to sure if I'm thinking about it correctly.

Maybe I should be making a left and right UIView that sit next to the onscreen UIView in ImageScrollView?

or maybe it has to do with recycling no-longer-visible pages as seen below (but I think I would still need views to the left/right and even still wont I need to recycle the views also??)

- (void)tilePages {
// Calculate which pages are visible
CGRect visibleBounds = pagingScrollView.bounds;//CGRect visibleBounds =     CGRectMake(0.0f, 0.0f, 320.0f * [self pdfPageCount], 435.0f);
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex  = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));


firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex  = MIN(lastNeededPageIndex, [self pdfPageCount] - 1);

// Recycle no-longer-visible pages 
for (ImageScrollView *page in visiblePages) {

    if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
        [recycledPages addObject:page];
        [page removeFromSuperview];}}
[visiblePages minusSet:recycledPages];

// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
    if (![self isDisplayingPageForIndex:index]) {
        ImageScrollView *page = [self dequeueRecycledPage];
        if (page == nil) {
            page = [[[ImageScrollView alloc] init] autorelease];}
        [self configurePage:page forIndex:index];
        [pagingScrollView addSubview:page];
        [visiblePages addObject:page];}}}

I changed the code to see what happened below (not sure) also I get error: initialization makes pointer from integer without a cast

- (void)tilePages:(NSUInteger) index {                
// Calculate which pages are visible
CGRect visibleBounds = pagingScrollView.bounds;
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex  = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex  = MIN(lastNeededPageIndex, [self pdfPageCount] - 1);
// Recycle no-longer-visible pages 
for (ImageScrollView *page in visiblePages) {
    if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
// visible N/Ppages *start*
if (index == 1) {
ImageScrollView *Npage = Npage.index +1;
Npage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Npage forIndex:index +1];
[pagingScrollView addSubview:Npage];
[visiblePages addObject:Npage];}
if (index < 2 || index > [self pdfPageCount] -2) {
ImageScrollView *Ppage = Ppage.index -1;
ImageScrollView *Npage = Npage.index +1;
Ppage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Ppage forIndex:index -1];
[pagingScrollView addSubview:Ppage];
[visiblePages addObject:Ppage];
Npage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Npage forIndex:index +1];
[pagingScrollView addSubview:Npage];
[visiblePages addObject:Npage];}
if (index == [self pdfPageCount] -1) {
ImageScrollView *Ppage = Ppage.index -1;
Ppage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Ppage forIndex:index -1];
[pagingScrollView addSubview:Ppage];
[visiblePages addObject:Ppage];}
// visible N/Ppages *end*
        [recycledPages addObject:page];
        [page removeFromSuperview];}}
[visiblePages minusSet:recycledPages];
// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
// recycled N/Ppages *start*
if (index == firstNeededPageIndex +1) {
ImageScrollView *Npage = Npage.index +1;
[recycledPages addObject:Npage];
[Npage removeFromSuperview];}
if (index < firstNeededPageIndex +2 || index > lastNeededPageIndex -2) {
ImageScrollView *Ppage = Ppage.index -1;
ImageScrollView *Npage = Npage.index +1;
[recycledPages addObject:Ppage];
[Ppage removeFromSuperview];
[recycledPages addObject:Npage];
[Npage removeFromSuperview];}
if (index == lastNeededPageIndex -1) {
ImageScrollView *Ppage = Ppage.index -1;
[recycledPages addObject:Ppage];
[Ppage removeFromSuperview];}
// recycled N/Ppages *end*
    if (![self isDisplayingPageForIndex:index]) {
        ImageScrollView *page = [self dequeueRecycledPage];
        if (page == nil) {
            page = [[[ImageScrollView alloc] init] autorelease];}
        [self configurePage:page forIndex:index];
        [pagingScrollView addSubview:page];
        [visiblePages addObject:page];}}}

Could I store 3 pdf pages in an NSIndex? eg: previousPageToRecycle, previousPage, currentPage, nextPage, nextPageToRecycle

I'm unsure of how to do this.

1

1 Answers

0
votes
ImageScrollView *Ppage = Ppage.index -1;

Ppage.index -1 is an integer. You're assigning it to a variable of type ImageScrollView*, which is supposed to magically turn it into a pointer. This is probably a mistake.

The first bit of code is much more readable, and looks like it's vaguely on the right track.

The second bit of code is completely unreadable due to lack of sane indenting. There are also some problems:

  • There are far too many special cases. Simplify your logic; it makess your code easier to get right and easier to maintain.
  • There is a lot of duplicated code. This is related to the special-casing.
  • I can't for the life of me figure out why firstNeededPageIndex+1 and lastNeededPageIndex-1 are special.
  • I would not use an NSSet for so few pages. I don't think being able to use minusSet: is a significant advantage over just using removeObject: (and if you're worried about performance, just use removeObjectIdenticalTo:)

I'm not sure what you mean by "storing pages in an NSIndex"; do you mean CFIndex or NSInteger?


And now, an overall answer:

In general, you're on the right track, but you need to load up to five pages:

  • The current page, i.
  • Pages i-1 and i+1. The user can start scrolling at any time; you don't want the display to lag while you render some tiles.
  • Pages i-2 and i+2. Bouncy-scrolling means if the user scrolls from i to i+1, it will show a few pixels of i+2. You don't want to load pages during the scroll animation, or it'll appear to lag (unless you can make it load in a background thread somehow). Note that if the user scrolls from i to i+1 to i+2, it'll "bounce" at i+3. You can load more, but I consider this uncommon enough that a little lag is justified.

I implemented this using a ring-buffer-like array of size 8; page i is stored in ring[i%8] if it is loaded. Since I only need to have 5 pages at a time, 8 is plenty; admittedly there's a largeish pile of ring-buffer-management code that you need to get right (mostly it just takes a while to code).

I also just used a "current page", loading i±1 during a scroll (hopefully they're already loaded) and i±2 at viewDidAppear: and at the end of a scroll (scrollViewDidEndDragging:willDecelerate: with decelerate=NO, and scrollViewDidEndDecelerating:).

You might also want to hold on to views a little longer (preload i±2 but keep i±3 if they happen to be loaded), otherwise views will be needlessly reloaded if the user moves back and forth during a scroll (laaaaaag).

If you show a page number, you might also want to add some hysteresis. I only changed pages when the user scrolled 2/3 of the way into the next page (that means to toggle between two pages, the user has to move back and forth by 1/3 page). If you implement hysteresis, the previous paragraph isn't such a big deal.