[iOS]電子書籍への道:PDF表示(その3) | Cocoa練習帳

[iOS]電子書籍への道:PDF表示(その3)

前回のサンプル・プログラムをリソースとして持っているPDF文書からページ数を取得して、頁に対応したページを表示するように拡張する。




Page-Based Applicationの雛形は、データを管理するクラスModelControllerが用意されている。また、iOSには複数の文書を扱う仕組みが用意されているので、いずれは、それへの対応という事になるが、今回は自分が単一文書を扱う場合に用意している独自のDocumentクラスでPDF書類を管理する事にした。




本当は、PDF文書の扱いはDocumentクラスで抽象化すべきと思うが、サンプル・コードという事で、単にプロパティとして持っている事で許して欲しい。




@interface Document : NSObject

@property (nonatomic, assign) CGPDFDocumentRef  pdf;

@end



そして、各コントローラクラスで、Documentクラスをプロパティとして参照するようにする。例えば、初期化をinitメソッドで行う場合は、以下のとおり。




#import "AppDelegate.h"

- (id)init
{
    self = [super init];
    if (self) {
        AppDelegate
        appl = (AppDelegate *)[[UIApplication sharedApplication] delegate];
        self.document = appl.document;
    }
    return self;
}



ページ・ビュー・コントローラの利用では、各クラスの関係や、モデルの初期化のタイミングは少し悩んだので、分かりやすいように掻い摘んで説明する。




ADCのサンプルZoomingPDFViewでは、決めうちで、リソースで持っているPDF文書の1頁だけを表示するに固定されているので、PDFScrollViewの初期時にPDF文書関連の初期化を行っていたが、それを止めて、外部から指定されたタイミングで指定された頁を表示する様に変更する。




@interface PDFScrollView : UIScrollView 

- (void)setIndexOfPDF:(NSUInteger)index;
- (NSUInteger)getIndexOfPDF;

@end
 
@implementation PDFScrollView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
    }
    return self;
}
 
- (void)awakeFromNib
{
    self.decelerationRate = UIScrollViewDecelerationRateFast;
    self.delegate = self;
 
    AppDelegate
    appl = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    self.document = appl.document;
}
 
- (void)setIndexOfPDF:(NSUInteger)index
{
    self.page = CGPDFDocumentGetPage(self.document.pdf, index + 1);
    CGPDFPageRetain(self.page);
    
    CGRect pageRect = CGPDFPageGetBoxRect(self.page, kCGPDFMediaBox);
    self.pdfScale = self.frame.size.width/pageRect.size.width;
    pageRect.size = CGSizeMake(pageRect.size.width * self.pdfScale, pageRect.size.height * self.pdfScale);
    
    UIGraphicsBeginImageContext(pageRect.size);
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
    CGContextFillRect(context,pageRect);
    
    CGContextSaveGState(context);
    CGContextTranslateCTM(context, 0.0, pageRect.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
     
    CGContextScaleCTM(context, self.pdfScale, self.pdfScale);
    CGContextDrawPDFPage(context, self.page);
    CGContextRestoreGState(context);
    
    UIImage *backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    self.backgroundImageView = [[UIImageView alloc] initWithImage:backgroundImage];
    self.backgroundImageView.frame = pageRect;
    self.backgroundImageView.contentMode = UIViewContentModeScaleAspectFit;
    [self addSubview:self.backgroundImageView];
    [self sendSubviewToBack:self.backgroundImageView];
    
    self.pdfView = [[TiledPDFView alloc] initWithFrame:pageRect andScale:self.pdfScale];
    [self.pdfView setPage:self.page];
    
    [self addSubview:self.pdfView];
}
 
- (NSUInteger)getIndexOfPDF
{
    return (NSUInteger)CGPDFPageGetPageNumber(self.page);
}

@end



雛形ではカレンダーのデータを保持していたDataViewControllerクラスを表示している頁番号とPDFScrollViewクラスを扱うように変更する。




@interface DataViewController : UIViewController

@property (strong, nonatomic) Document                  *document;
@property (nonatomic, assign) NSUInteger                index;

- (void)setIndexOfPDF:(NSUInteger)index;
- (NSUInteger)getIndexOfPDF;
@end
 
@implementation DataViewController

- (void)setIndexOfPDF:(NSUInteger)index
{
    self.index = index;
    PDFScrollView   *pdfScrollView = (PDFScrollView *)self.view;
    [pdfScrollView setIndexOfPDF:index];
}
 
- (NSUInteger)getIndexOfPDF
{
    return self.index;
}

@end



頁に対応するビュー・コントローラDataViewControllerは、モデル・コントーラで管理される?ということで、PDF表示に対応させる。




@implementation ModelController

- (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard
{   
    if (CGPDFDocumentGetNumberOfPages(self.document.pdf) < index)
        return nil;
     
    DataViewController *dataViewController = [storyboard instantiateViewControllerWithIdentifier:@"DataViewController"];
    [dataViewController setIndexOfPDF:index];
    return dataViewController;
}
 
- (NSUInteger)indexOfViewController:(DataViewController *)viewController
{   
    return viewController.index;
}
 
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
    NSUInteger index = [self indexOfViewController:(DataViewController *)viewController];
    if ((index == 0) || (index == NSNotFound)) {
        return nil;
    }
     
    index--;
    return [self viewControllerAtIndex:index storyboard:viewController.storyboard];
}
 
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    NSUInteger index = [self indexOfViewController:(DataViewController *)viewController];
    if (index == NSNotFound) {
        return nil;
    }
    
    index++;
    if (index == CGPDFDocumentGetNumberOfPages(self.document.pdf))
        return nil;
    return [self viewControllerAtIndex:index storyboard:viewController.storyboard];
}

@end



このサンプルをGitHubから取得して、実行すると、強制的に著者がCocoa勉強会で発表した文書を読まされる事になる。




Books





ソースコード

GitHubからどうぞ。

https://github.com/murakami/workbook/tree/master/ios/Books - GitHub




関連情報

第4回 出版プラットフォームとしてのiOS

Think IT 連載: iOSでつくる2011アプリ開発状況から。

Quartz 2d Programming Guide

Acroll View Programming Guide for iOS

ZoomingPEFViewer

Apple Developerサイトの情報。

Programming with Quartz: 2D and PDF Graphics in Mac OS X

OS XとiOSのCore Graphicsの解説書。