Viewer 源码阅读心得3——初始化分析

版权所有, 转载请著明出处,保留链接。
Viewer 源码阅读心得3——初始化分析

##初始化概括
初始化分为数据的初始化和UI的初始化,数据的初始化就是构建CoreData体系,这已经在上篇对象存储模型中讨论过,主要通过类CoreDataManager(单例)进行,而UI的初始化是通过一组UIViewController类进行,逐视图层次初始化下去,并在UI初始化的过程中通过CoreData加载DB数据展现在视图上。由此可见,必须先进行数据的初始化,再进行UI视图的初始化。

初始化的嵌入点是ViewerAppDelegate类,是NSApplication的代理类,主要在如下方法中进行,其逻辑过程如下:

- (BOOL)application:(UIApplication *)application :(NSDictionary *)launchOptions
  1. 初始化APP配置参数, 向NSUserDefaults登记APP的默认配置:版本号,隐藏状态栏标志;
  2. 初始化CoreData对象存储模型体系, 如果CoreData中不存在Documents/Recent,则向CoreData中插入;
  3. 如果APP是通过其他APP传递NSURL打开的,则在NSUserDefault删除kReaderSettingsCurrentDocument键的值;
  4. 使用类DirectoryWatcher对文件夹Documents进行监视,如果Documents文件下的文件有变动,则调用委托方法:

    • (void)directoryDidChange:(DirectoryWatcher *)folderWatcher;

      在委托方法中,如果定时器有效, 则使其失效,然后创建一个4.8m的定时器,当时间到时,则调用:

    • (void)watcherTimerFired:(NSTimer *)timer

      在此方法法中,首先使directoryWatcherTimer失效,并置为nil,然后调用DocumentsUpdate单例的queueDocumentsUpdate方,创建一个NSOperation加入队列,异步处理PDF文档有变动.

  5. 最后进行UI的初始化,实例化LibraryViewController,并作为根视图赋值给主窗口;

ViewerAppDelegate的类中还有一个方法,如下:

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
    return [[DocumentsUpdate sharedInstance] handleOpenURL:url];
}

这个方法是供给系统调用的,当其他APP调用本APP打开本APP支持的文件时,系统会调用这个文件。那么怎么说明本App支持的文件类型呢。说明在*.plist文件中,如下:

document type

Viewer程序打开的UI初始化脉络如下:
类层次结构:

LibraryViewController --- LibraryDirectoryView --- UIXToolbarView
                      |                            |--- ReaderThumbsView --- LibraryDirectoryCell
                      |
                      |--- LibraryDocumentsView ---UIXToolbarView
                                                  |--- ReaderThumbsView --- LibraryDocumentsCell

函数调用层次结构:

LibraryViewController:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
    |
    |
- (void)viewDidLoad          
    |
    |
- (void)viewDidAppear:(BOOL)animated
    |
    |------------------ Library***View:
    |                    - (id)initWithFrame:(CGRect)frame
    |                    |
    |                    |-----------------ReaderThumbsView
    |                                       - (id)initWithFrame:(CGRect)frame
    |------------------ Library***View:
                         ***View reload***
                         |
                         |----------------ReaderThumbsView:
                                           reloadThumbsContentOffset:
                                            |
                                            |
                                           layoutSubviews
                                            |
                                            |
                                            -(id)thumbsView:thumbCellWithFrame:
                                                        |
                                                        |-----------------------Library**Cell:
                                                        |                       - (id)initWithFrame:(CGRect)frame
                                                        |
                                                        -(void)thumbsView:updateThumbCell:forIndex:
                                                        |
                                                        |-----------------------Library**Cell 配置相关属性

Viewer打开显示PDF页面的UI初始化脉络如下:
类层次结构:

ReaderViewController  ---- ReaderMainToolbar
                      |---- ReaderMainPagebar --- ReaderPagebarThumb
                      |                          |--- ReaderTrackControl ----- ReaderPagebarThumb
                      |
                      |---- ReaderContentView --- ReaderContentThumb
                                                |--- ReaderContentPage

函数调用层次结构:

ReaderViewController:
- (instancetype)initWithReaderDocument:(ReaderDocument *)object
   |
   |
- (void)viewDidLoad
   |
   |------------------ ReaderMainPagebar:
   |                        - (instancetype)initWithFrame:(CGRect)frame document:(ReaderDocument *)object;
   |                        |
   |                        |---------------------ReaderTrackControl:
   |                        |
   |                    - (void)layoutSubviews
   |                        |
   |                           |---------------------ReaderPagebarThumb:
   |                                               - (instancetype)initWithFrame:(CGRect)frame small:(BOOL)small;
   - (void)showDocument                                 
   |
   - (void)showDocumentPage:(NSInteger)page
   |
   |
   - (void)layoutContentViews:(UIScrollView *)scrollView
   |
   |----ReaderContentView:
           - (instancetype)initWithFrame:(CGRect)frame fileURL:(NSURL *)fileURL page:(NSUInteger)page password:(NSString *)phrase
           |
           |----ReaderContentPage:
                 - (instancetype)initWithURL:(NSURL *)fileURL page:(NSInteger)page password:(NSString *)phrase;

Viewer的主要视图页面如下:

viewer

##LibraryViewController控制器的初始化

LibraryViewController是一个控制器, 在MVC模式中,控制器是数据和UI的协调者,而它的初始化主要是UI的初始化,在视图的加载和显示中调用CoreData的数据,在视图上呈现。LibraryViewController管理着文件夹视图directoryView和PDF文档视图documentsView,显示PDF文档的控制器。它的初始化先调用:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

再在视图加载时调用:

- (void)viewDidLoad

再在视图将显示调用:

- (void)viewWillAppear:(BOOL)animated

再调用视图已经显示时调用:

- (void)viewDidAppear:(BOOL)animated

LibraryViewController的实现文件中封装了以下字段:

LibraryViewController
    --UIScrollView *theScrollView                   //滚动视图(左右滚动),放directoryView/documentView
    --LibraryDirectoryView *directoryView;          //文件夹视图
    --LibraryDocumentsView *documentsView;          //PDF文档视图
    --ReaderViewController *readerViewController;//PDF文档阅读控制器
    --LibraryUpdatingView *updatingView;          //文档更新提示视图
    --NSMutableArray *contentViews;                  //存放directoryView,documentsView
    --NSInteger visibleViewTag;                      //标志当前显示的视图
    --CGSize lastAppearSize;                      //视图控制视图最近显示的大小
    --BOOL isVisible;

LibraryViewController中视图层次结构如下:

view---updateView
    |---theScrollView----directoryView
                      |----documentsView

LibraryViewController控制器中的重写方法,先看如下方法,如下方法随视图控制的初始化,视图的加载,实现的显示先后调用。其中initWithNibName…和viewDidLoad一个视图控制器,在视图的生命周期中只调用了一次。而其他方法随视图的显示和不显示会相应的调用。

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
- (void)viewDidLoad
- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated
- (void)viewDidUnload //已经废弃
  1. (id)initWithNibName:(NSString )nibNameOrNil bundle:(NSBundle )nibBundleOrNil,是最先被调用的函数,是视图控制器初始化方法,在这里一般做预备工作, 如数据的初始化, 在这里做了向通知中心注册DocumentsUpdateOpenNotification,DocumentsUpdateBeganNotification,DocumentsUpdateEndedNotification通知的调用方法,并对Viewer的缓存做清空。

    DocumentsUpdateOpenNotification  --->   openNewDocument 告诉视图控制器打开了新的PDF文档
    DocumentsUpdateBeganNotification --->   showUpdatingView 告诉视图控制器显示更新提示视图
    DocumentsUpdateEndedNotification --->   hideUpdatingView 告诉视图控制器隐藏更新提示视图
    
  2. (void)viewDidLoad, 当视图控制器的视图加载完成后调用, 主要初始化子视图,并添加到视图中。在这里初始化了theScrollView和updateView视图,并添加为视图控制器view的子视图。

  3. (void)viewWillAppear:(BOOL)animated, 当视图控制器的视图将要显示的时候,调用该方法,调整子视图的layout;
  4. (void)viewDidAppear:(BOOL)animated,当视图控制的视图显示后调用,主要的共用是加载子视图的数据,其逻辑过程如下:

    1. 当contentViews的数组没有视图时,初始化一个diretoryView和documentsView,并添加为theScrollView的子视图,并设置加载子视图的标志为真;
    2. 当theScrollView的contentSize为0时, 调用内部方法updateScrollViewContentSize,更新滚动视图的内容大小,设置滚动视图内容区域为directoryView和documentsView,它们的宽度之和,高度为自身bounds大小的高度;
    3. 如果加载了子视图(reload=YES),则加载子视图的数据,如下:

      1. 首先目录视图(directoryView)重新加载目录, [directoryView reloadDirectory];
      2. 获取当前的文件夹; 首先从NSUserDefaults获取键kReaderSettingsCurrentFolder的值, 如果该值为空,则设置当前文件夹为Documents文件夹;
      3. 文档视图(documentsView)重新加载文档, [documentsView reloadDocumentsWithFolder:folder];
      4. 如果NSUserDefaults中的当前文档键值不为空,则从CoreData中读取该文档ReaderDocument,并显示该文档 [self showReaderDocument:document]; 此中的寓意一是:打开上次APP打开的PDF文档,二是其他APP调用该APP打开当前文档。

那么现在来看一下- (void)showReaderDocument:(ReaderDocument *)document方法,该方法主要是显示readerViewController,即PDF阅读控制器, 从LibraryViewController视图控制中present, 逻辑如下:

  1. 判断document是否存在的, 并且PDF文档的的密码是正确的;
  2. 设置NSApplication的状态栏,显示或隐藏;
  3. 置readerViewController为nil,并以参数document重新分配一个readerViewController,并在view中显示;

在来看下- (void)openNewDocument:(NSNotification *)notification方法,该方法是打开一个新的PDF文档,是收到DocumentsUpdateOpenNotification消息后调用的,那么打开哪个PDF文档呢,PDF文档的documentURL是通过NSUserDefaults的键kReaderSettingsCurrentDocument值传递过来的,其逻辑如下:

  1. 从NSUserDefault中获取kReaderSettingsCurrentDocument键的值,当前PDF文档的URL;
  2. 检查documentURL所在的PDF文档是否存在,并从CoreData中查询返回该ReaderDocument;
  3. 调用 [self showReaderDocument:document] 显示该PDF文档;

##LibraryDirectoryView视图的初始化

LibraryDirectoryView作为视图, 管理着文件夹视图的显示和交互, 并担当了一部分控制器的功能, 其直接与数据层进行交互,而与用户的交互, 自己能处理自己处理掉,自己不能处理的, 会把这些信息通过协议传递给委托(LibraryViewController)来处理。

LibraryDirectoryView实现文件中封装了以下字段:

HelpViewController *helpViewController; //帮组视图控制器
UIPopoverController *popoverController; //iPad中的帮组提示
NSArray *directories;                     //存放DocumentsFolder对象数组
NSMutableSet *selected;                     //存放选中的DocumentsFolder对象可变数组
UIXToolbarView *theToolbar;                 //工具栏视图
ReaderThumbsView *theThumbsView;         //文件夹图标视图

UIXTextEntry *theTextEntry;                
UIAlertView *theAlertView;    

UIButton *theCheckButton;                //工具栏视图上的check按钮(在编辑模式和非编辑模式间转化)
UIButton *thePlusButton;                //工具栏视图上的创建新文件夹按钮
UIButton *theEditButton;                //工具栏视图上的使LibraryDirectoryView进入编辑模式的按钮
UILabel *theTitleLabel;                    //工具栏视图上的标题
BOOL editMode;                            //编辑模式标志

LibraryDirectoryView的视图层次结构如下:

view----theToolBar ---infoButton
    |               |---theCheckButton
    |               |---thePlusButton
    |               |---theEditButton
    |               |---theTitleLabel
    |---theThumbsView

此处关键是ReaderThumbsView视图, 它显示的是文件夹图标, 而LibraryDirectoryView是作为其的代理delegate, 相当于数据源, 相当于UITableViewControl和UITableView之间的关系, UITableView需要知道UITableViewCell的信息,才能显示出UITableViewCell,并且会把一些交互的信息传递到UITableView的委托中(delegate),让委托更新数据,同理ReaderThumbsView同UITableView一样也实现了ReaderThumbView(UITableViewCell)的缓存,详细的介绍请移步ReaderThumbsView。

LibraryDirectoryView的视图的初始化通过- (id)initWithFrame:(CGRect)frame进行

  1. 首先设置视图的一些特性,例如backgroundColor,autoresizingMask等;
  2. 构建theToolbar视图及其子视图;
  3. 构建theThumbsView视图;
  4. 向通知中心注册UIApplicationWillResignActiveNotification通知的处理方法willResignActive;
  5. 分配一个可变数组给selected;

LibraryDirectoryView视图的对外接口主要是:- (void)reloadDirectory消息,其主要逻辑重新加载数据(directoies),重新加载显示数据的视图theThumbsView。

##LibraryDocumentsView视图的初始化

LibraryDocumentsView作为视图, 管理着PDF文档视图的显示和交互, 并担当了一部分控制器的功能…

LibrararyDocumentsView的实现文件中封装了以下字段:

FoldersViewController *foldersViewController;  //移动文档时显示的文件夹视图
UIPopoverController *popoverController;        //iPad中显示的文件中视图
NSArray *documents;                                //存放ReaderDocument的对象的数组
NSMutableSet *selected;                            //存放选中的ReaderDocument的对象可变数组
DocumentFolder *inFolder;                           //文件夹对象
UIXToolbarView *theToolbar;                        //工具栏视图
ReaderThumbsView *theThumbsView;               //显示PDF文档图标的视图
ReaderDocument *openDocument;                  //打开的PDF文档,记录打开的PDF文档

UIXTextEntry *theTextEntry;
UIAlertView *theAlertView;

UIButton *theFolderButton;                        //工具栏中的移动文档到文件夹按钮
UIButton *theCheckButton;                        //工具栏中check按钮在编辑和非编辑模式中转换
UIButton *theMinusButton;                        //工具栏中删除按钮
UIButton *theEditButton;                        //工具栏中编辑按钮
UILabel *theTitleLabel;                        //工具栏中标题label

BOOL editMode;                                    //标记模式标志

LibrarayDocumentsView的视图层次结构如下:

view---theToolBar --- theFolderButton
    |              |--- theCheckButton
    |              |--- theMinusButton
    |              |--- theEditButton
    |              |--- theTitleLabel
    |---theThumbsView

LibrarayDocumentsView也是遵守了LibraryDirectoryView的设计思路,其初始化也是通过- (id)initWithFrame:(CGRect)frame进行

  1. 首先设置视图的一些特性,例如backgroundColor,autoresizingMask等;
  2. 构建theToolbar视图及其子视图;
  3. 构建theThumbsView视图;
  4. 向通知中心注册各种文档操作的通知的处理方法, 通过这些通知位置状态数据的一致性,并通过视图表现出来;

    DocumentsUpdateNotification ---- documentsDidUpdate
    DocumentsUpdateOpenNotification --- openedNewDocument
    UIApplicationWillResignActiveNotification --- willResignActive
    DocumentFoldersDeletedNotification --- foldersWhereDeleted
    DocumentFolderDeletedNotification --- folderWasDeleted
    DocumentFolderRenamedNotification --- folderWasRenamed 
    
  5. 分配一个可变数组给selected;

LibraryDocumentsView视图的对外接口有:

- (void)reloadDocumentsWithFolder:(DocumentFolder *)folder; //重新加载documents数据,重新加载theThumbsView
- (void)refreshRecentDocuments; //如果当前的文档在recent文件夹下,重新加载其数据和视图

##ReaderThumbsView视图初始化

ReaderThumbsView继承自UIScrollView, 是ReaderThumbView的集合视图, 管理图标视图的交互与显示, 请通过协议委托(delegate), 一方面向委托请求显示的数据, 另一方面也向委托传递与用户交互的信息。

其实现文件中封装了以下字段:

CGPoint lastContentOffset;                    //最近一次scrollView内容的偏离位置
ReaderThumbView *touchedCell;                //记录最经一次触碰的ReaderThumbView
NSMutableArray *thumbCellsQueue;            //存放ReaderThumbView的可变数组
NSMutableArray *thumbCellsVisible;            //存放用户可见的ReaderThumbView的可变数组

NSInteger _thumbsX,                         //x方向thumbs的个数
          _thumbsY,                         //y方向thumbs的个数
          _thumbX;                            //x方向thumb间的间距

CGSize _thumbSize,                            //thumb的大小, 通过消息setThumbSize:(CGSize)thumbSize设置            _lastViewSize;                        //ReaderThumbsView视图的大小

NSUInteger _thumbCount;                        //thumb的个数

BOOL canUpdate;

ReaderThumbsView的视图层次结构如下:

view --- UIScrollView --- ReaderThumbView
                      |--- ReaderThumbView
                      |--- ReaderThumbView
                      .......

ReaderThumbsView的初始化通过重载父类的方法- (instancetype)initWithFrame:(CGRect)frame实现:

  1. 设置视图属性, 例如backgroundColor, autoresizingMask等;
  2. 初始化thumbCellsQueue,thumbCellsVisible可变数组, 初始化lastContentOffset;
  3. 添加触摸手势和按长按手势操作;

    UITapGestureRecognizer --- handleTapGesture:
    UILongPressGestureRecognizer --- handlePressGesture:
    

initWithFrame是在ReaderThumbsView初始化的时候被调用, 当其要被显示时,控制子视图的布局: - (void)layoutSubviews会被调用, 其逻辑如下:

  1. 如果_lastViewSize未被设置,则设置为self.bounds.size;
  2. 如果_lastViewSize已被设置了, 则根据_thumbCount(图标个数)更新ReaderThumbsView的contentsize;
  3. 获取可见区域所有ReaderThumbView的索引数组:visibleIndexSetForContentOffset,根据cotentOffset,_thumbSize, _thumbsX等参数构建索引数组;
  4. 对每一个在thumbCellsVisible中的ReaderThumbView, 判断其索引是否在visibleIndexSet, 如果在说明其不需要重新画了,把其索引从visibleIndexSet中删除, 如果不在,则把其加入入队数组, 回收资源,从thubmCellsVisible中删除, 加入thumbCellsQueue中;
  5. 对每一个可见索引,

    1. 首先为该索引计算矩形[self thumbCellFrameForIndex:index];
    2. 再次出队一个ReaderThumbView,[self dequeueThumbCellWithFrame:thumbRect],如果thumbCellsQueue中有,则直接取一个,如果没有, 则需要向委托(delegate)请求分配一个ReaderThumbView:[delegate thumbsView:self thumbCellWithFrame:frame];
    3. 向委托(delegate)请求更新此ReaderThumbView的数据:[delegate thumbsView:self updateThumbCell:tvCell forIndex:index];

下面再来看下LibraryDirectoryView,LibraryDocumentsView重新加载时都会调用的方法:

- (void)reloadThumbsContentOffset:(CGPoint)newContentOffset;
  1. 检查delegate, _thumbSize是否正确设置;
  2. 确保UIScrollView处于停止状态, [self setContentOffset:self.contentOffset animated:NO];
  3. 重置lastContentOffset, 回收所有的ReaderThumbView进thumbCellsQueue;
  4. 向委托(delegate)请求图标的数量:NSUInteger thumbCount = [delegate numberOfThumbsInThumbsView:self];
  5. 更新ReaderThumbsView的区域: contentOffset, 根据thumbCount;
  6. 设置UIScrollView的contentOffset;
  7. 调用[self flashScrollIndicators], 促使调用- (void)layoutSubviews方法;

##ReaderThumbView,LibiraryDirectoryCell,LibraryDocumentsCell视图的初始化
LibraryDirectoryCell是文件夹Thumb, 而LibraryDocumentsCells是PDF文档Thumb, 两者都继承于ReaderThumbView,而ReaderThumbView继承于UIView,ReaderThumbView用来描述单个Thumb(图标)。LibraryDirectoryCell和LibraryDocumentsCell的初始化分别通过各自的ReaderThumbsView的委托,向其请求初始化,返回一个ReaderThumbView:

- (id)thumbsView:(ReaderThumbsView *)thumbsView thumbCellWithFrame:(CGRect)frame;

###ReaderThumbView视图的初始化
ReaderThumbView的头文件声明如下:

@interface ReaderThumbView : UIView
{
@protected // Instance variables
    UIImageView *imageView;        //图像视图,给子类使用
}

@property (atomic, strong, readwrite) NSOperation *operation;  //记录获取thumb图像的任务operation
@property (nonatomic, assign, readwrite) NSUInteger targetTag; //ReaderThumbView在集合视图中的索引

- (void)showImage:(UIImage *)image;   //在图像视图中显示图像
- (void)showTouched:(BOOL)touched;    //
- (void)reuse;                            //复用, 重置, 清除内容
@end

ReaderThumbView视图的层次结构如下:

UIView ---- imageView

ReaderThumbView的初始化也是通过- (instancetype)initWithFrame:(CGRect)frame 进行.

###LibraryDirectoryCell视图的初始化
再来看看LibraryDirectoryCell, 其在实现文件中封装了如下字段:

UILabel *textLabel;            //显示目录的名称
UIImageView *checkIcon;        //选中标志视图
CGRect defaultRect;            //默认区域

LibraryDirectoryCell视图层次结构

UIView --- checkIcon
      |--- textLabel
      |--- imageView

在-(id)initWithFrame:(CGRect)frame初始化方法中初始化上述视图,并设置相应的视图属性。

在LibraryDirectoryView, ReaderThumbsView的委托中,直接实例化一个LibraryDirectoryCell返回:

- (id)thumbsView:(ReaderThumbsView *)thumbsView thumbCellWithFrame:(CGRect)frame
{
    return [[LibraryDirectoryCell alloc] initWithFrame:frame];
}

###LibraryDocumentsCell视图的初始化
LibraryDocumentsCell的实现文件中封装了如下字段:

UIView *backView;           //背景视图
UIView *maskView;           //掩盖视图
UIView *titleView;           //文档标题视图
UILabel *titleLabel;       //文档标题
UIImageView *checkIcon;   //选中标识视图
CGSize maximumSize;
CGRect defaultRect;

LibraryDocumentsCell的视图层次结构

UIView --- imageView --- checkIcon
       |             |--- maskView
       |                 
       |--- titleView --- titleLabel
       |--- backView

在-(id)initWithFrame:(CGRect)frame初始化方法中初始化上述视图,并设置相应的视图属性。

在LibraryDocumentsView, ReaderThumbsView的委托中, 实现如下,直接实例化一个LibraryDocuemntsCell返回

- (id)thumbsView:(ReaderThumbsView *)thumbsView thumbCellWithFrame:(CGRect)frame
{
    return [[LibraryDocumentsCell alloc] initWithFrame:frame];
}

可以看出, 无论LibraryDocumentsView,还是LibraryDirectoryView,其委托初始化时,只是简单的调用initWithFrame:放回一个实例, 那么的它的个性化数据,图片,标题等什么时候加载呢?显然也是通过向委托请求, 即先实例化, 在配置参数,协议方法如下:

- (void)thumbsView:(ReaderThumbsView *)thumbsView updateThumbCell:(id)thumbCell forIndex:(NSInteger)index;

在LibraryDirectoryView中实现如下,只是简单的设置了LibraryDirectoryCell的名称, 图片是默认图片

- (void)thumbsView:(ReaderThumbsView *)thumbsView updateThumbCell:(LibraryDirectoryCell *)thumbCell forIndex:(NSInteger)index
{
    DocumentFolder *folder = [directories objectAtIndex:index];

    if (folder.isDeleted == NO) // Object must not be deleted
    {
        BOOL checked = folder.isChecked; [thumbCell showCheck:checked];

        NSString *name = folder.name; [thumbCell showText:name];
    }
}

在LibraryDocumentsView中的实现如下,由于其显示的图片是来自PDF文档的第一页, 通过Viewer的存储体系来读取这样图片,如果图片未在内存中,则是一个异步操作。
这里的实现, Viewer中把数据读取存储与UI结合在了一起。

- (void)thumbsView:(ReaderThumbsView *)thumbsView updateThumbCell:(LibraryDocumentsCell *)thumbCell forIndex:(NSInteger)index
{
    ReaderDocument *document = [documents objectAtIndex:index];

    if (document.isDeleted == NO) // Document object must not be deleted
    {
        [thumbCell showText:[document.fileName stringByDeletingPathExtension]];

        CGSize size = [thumbCell maximumContentSize]; // Get the cell's maximum content size

        NSURL *fileURL = document.fileURL; NSString *guid = document.guid; NSString *phrase = document.password; // Document

        ReaderThumbRequest *thumbRequest = [ReaderThumbRequest newForView:thumbCell fileURL:fileURL password:phrase guid:guid page:1 size:size];

        UIImage *image = [[ReaderThumbCache sharedInstance] thumbRequest:thumbRequest priority:NO]; // Request the thumbnail

        if ([image isKindOfClass:[UIImage class]]) [thumbCell showImage:image]; // Show image from cache

        BOOL checked = document.isChecked; [thumbCell showCheck:checked]; // Show checked status
    }
}

Viewer中所有的关于thumb图标都继承于ReaderThumbView,除了LibraryDirectoryCell, LibraryDocumentsCell, 还有ReaderPagebarThumb,ReaderContentThumb,ThumbsPageThumb,其中ReaderPagebarThumb是在ReaderMainPagebar中使用的,标识滚动页的thumb,ReaderContentThumb是在ReaderContentView中使用,标识PDF页面的Thumb,而ThumbsPageThumb是在ThumbsViewController中使用,标识缩略页, 而ThumbsViewController同LibraryDocumentsView和LibraryDirectoryView的实现方式是一样的。

UIView
  |
  |
ReaderThumbView 
  |
  |
LibraryDirectoryCell, LibraryDocumentsCell,ThumbsPageThumb,ReaderPagebarThumb, ReaderContentThumb

说到底ReaderThumbView是对一张图片的抽象,将这张图片显示在视图上, 而在不同的环境下, 视图的属性是不同的, 所以需要子类的不同实现,应用在不同的地方。

至此Viewer的UI的初始化基本上算完毕,下面就等待用户的操作了, 随用户的交互, Viewer做出不同的反应, 但是在这里我把ReaderViewController的初始化也放在这里, 当打开PDF文档时,会进入该控制器。

##ReaderViewController的初始化
ReaderViewController是一个控制器, 其数据是document,一个ReaderDocument对象, 而其UI有控制阅读进度的ReaderMainPagebar, 有对页面进行操作的mainToolbar, 还有显示PDF页面的theScrollView,控制器协调用户交互, 数据呈现与数据实体,使其有机的结合在一起。它是阅读的中心, 控制着PDF的阅读,一页一页的翻页。

ReaderViewController的实现文件中封装了以下字段:

ReaderDocument *document;                               //PDF文档对象
UIScrollView *theScrollView;                           //滚动视图
ReaderMainToolbar *mainToolbar;                           //工具栏
ReaderMainPagebar *mainPagebar;                       //页面快速导航
NSMutableDictionary *contentViews;                    //内容视图,存放可见区域的ReaderContentView,缓冲的作用
UIUserInterfaceIdiom userInterfaceIdiom;
NSInteger currentPage,                                    //当前页面
          minimumPage,                                //最小页面
          maximumPage;                                //最大页面
UIDocumentInteractionController *documentInteraction; //
UIPrintInteractionController *printInteraction;       //打印控制器
CGFloat scrollViewOutset;                             //滑动视图的内边距
CGSize lastAppearSize;                                   //记录最近一次出现的大小,用来跟踪视图控制器视图的大小, 从而更新contentViews
NSDate *lastHideTime;                                 //最近隐藏的事件
BOOL ignoreDidScroll;                                 //是否滑动的标志

ReaderViewController中视图的层次结构如下:

UIView  ---- mainToolbar
        |---- mainPagebar
        |---- theScrollView ---- ReaderContentView1
                           |---- ReaderContentView2
                           |---- ReaderContentView3
                           ....

ReaderViewController的初始化方法是:- (instancetype)initWithReaderDocument:(ReaderDocument *)object,以要打开的ReaderDocument来初始化。其逻辑过程如下,主要是做些预准备工作。

  1. 调用父类的 initWithNibName:bundle:, 检查传递进来的参数object的合法性;
  2. 向通知中心注册通知的处理方法:

    UIApplicationWillTerminateNotification  --- applicationWillResign
    UIApplicationWillResignActiveNotification --- applicationWillResign
    
  3. 设置scrollViewOutset, document, 并且更新ReaderDocument的属性,包括页数, 文件修改事件, 文件大小属性;

  4. 使用ReaderThumbCache创建该文档的存储文件夹/Library/Caches/guid/

ReaderViewController的viewDidLoad的处理逻辑如下,主要是创建子视图, 并初始化一些字段

  1. 创建theScrollView对象, 并设置相关属性
  2. 创建mainToolbar对象,并设置相关属性
  3. 创建ReaderMainPagebar对象, 并设置相关属性
  4. 设置手势处理方法:

    singleTapOne ----- handleSingleTap:
    doubleTapOne ----- handleDoubleTap:
    doubleTapTwo ----- handleDoubleTap:
    
  5. 为contentViews分配数组, 设置lastHideTime, minimumPage = 1, maximumPage为文档的页数;

ReaderViewController的- (void)viewWillAppear:(BOOL)animated处理逻辑如下,主要功用是重新布局;

  1. 调用父类的viewWillAppear:
  2. 如果lastAppearSize不等于CGSizeZero,并且判断也不等于self.view.bounds.size,则更新内容视图

    - (void)updateContentViews:(UIScrollView *)scrollView
    
    1. 更新scrollView的内容区域;
    2. 对于contentViews中每一ReaderContentView,重新设置frame;
    3. 根据当前页,重新设置scrollView的contentOffset;
    4. 更新mainToolbar和mainPagebar; 
    
  3. 否则设置lastAppearSize = CGSizeZero;

ReaderViewController的- (void)viewDidAppear:(BOOL)animated处理逻辑如下,主要功用是加载显示的数据

  1. 调用父类的viewDidAppear
  2. 如果theScrollView.contentSize的大小为0,说明当前没有显示ReaderDocument文档,则调用showDocument方法;

ReaderViewController的- (void)showDocument处理方法:

  1. 更新滚动区域的内容大小,滚动大小的高设置为scrollView的bounds的高,宽要设置为bounds的宽乘以页数;
  2. 调用showDocumentPage:消息,显示document.pageNumber的PDF页;
  3. 更新document的最近一次打开事件;

ReaderViewController的- (void)showDocumentPage:(NSInteger)page处理方法:

  1. 检查传递进来参数page的正确性, 如果正确记录page为当前页面,计算该页面的偏移位置;
  2. 当theScrollView的偏移位置与当前页的偏移位置不相等时候, 调用layoutContentViews,重新布局内容视图
    1. 在layoutContentViews中, 首先计算显示区域的Page范围[pageA, pageB], page作为contentViews的键值
    2. 对于每个contentViews中的键,如果不再[pageA, pageB]的范围内,则从contentViews中删除,如果在,则从[pageA, pageB]中删除;
    3. 如果[pageA, pageB]集合中还有页码,如果页面数为2, 当最大页大于2且最后一页是最大页,则逆序遍历集合
    4. 当页数等于3, 则在contentViews增加中间页的视图ReaderContentView,并添加到theScrollView中,并调用:
      [contentView showPageThumb:fileURL page:page password:phrase guid:guid];异步显示该thumb
    5. 对于[pageA, pageB]中的每一页,调用[self addContentView:scrollView page:page] 加入到contentViews中;
  3. 对于contentViews中的每一个ReaderContentView,如果不是当前显示的ReaderContentView, 则调用zoomResetAnimated,重置缩放大小;
  4. 根据document的bookmarks书签集合是否包含当前页,更新mainToolbar, 并根据当前页, 更新mainPagebar。

##ReaderContentView视图的初始化
ReaderContentView视图显示PDF文档的一页,ReaderContentView控制着这一页的部分显示及缩放,其继承UIScrollView.

ReaderContentView的实现文件中,封装了以下字段:

UIView *theContainerView;                    //包含视图
UIUserInterfaceIdiom userInterfaceIdiom;
ReaderContentPage *theContentPage;            //当前内容页视图
ReaderContentThumb *theThumbView;             //当前页的thumb图片
CGFloat realMaximumZoom;                    //真实最大放大因子
CGFloat tempMaximumZoom;                    //临时放大因子
CGFloat bugFixWidthInset;
BOOL zoomBounced;                            //放大因子限制标志

ReaderContentView视图层次结构如下:

UIView ---- theContainerView ---- theThumbView
                             |---- theContentPage

ReaderContentView的初始化通过如下方法进行:

- (instancetype)initWithFrame:(CGRect)frame fileURL:(NSURL *)fileURL page:(NSUInteger)page password:(NSString *)phrase
  1. 设置视图的相关属性,例如scrollsToTop,clipsToBounds等;
  2. 实例化一个theContentPage, theContainerView, theThumbView, 并把theContentPage, theThumbView作为子视图添加到theContainerView中。
  3. 把theContainerView添加为self的子视图, 即UIScrollView的子视图;
  4. 更新缩放缩放因子,最小缩放因子是theContentPage视图的bounds与本UIScrollView视图的bounds之比, 最大缩放因子是最小缩放因子的16倍。
  5. 设置UIScrollView的缩放因子zoomScale为最小缩放因子;
  6. 对本视图的frame进行观察(登记):[self addObserver:self forKeyPath:@”frame” options:0 context:ReaderContentViewContext];

而如果UIScrollView的frame发生变化,将调用如下方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  1. 居中scrollView的内容区域;
  2. 更新最小最大缩放因子,在进行缩放因子计算时, 其实scrollView的的bounds的大小是不变的(320,568), theContentPage的bounds的大小为(492,620)
  3. 对scrollView的缩放因子进行控制,在合理的范围内。

ReaderContentView显示PDF页面的thumb的消息如下,其主要逻辑是构建一个ReaderThumbRequest请求,向ReaderThumbCache缓存单例请求该thumb图像,显示在theThumbView视图上。

- (void)showPageThumb:(NSURL *)fileURL page:(NSInteger)page password:(NSString *)phrase guid:(NSString *)guid

##ReaderContentPage视图的初始化

ReaderContentPage代表着从PDF文档数据获取的原始的PDF页视图,其主要作用是对PDF数据的封装。

ReaderContentPage的实现文件中,封装了以下字段:

NSMutableArray *_links;            //PDF文档中链接
CGPDFDocumentRef _PDFDocRef;    //PDF文档
CGPDFPageRef _PDFPageRef;      //PDF页
NSInteger _pageAngle;          //PDF页显示的角度
CGFloat _pageWidth;                //PDF页的宽度
CGFloat _pageHeight;             //PDF页的高度
CGFloat _pageOffsetX;            //PDF页X方向的偏移
CGFloat _pageOffsetY;            //PDF页Y方向的便宜

ReaderContentPage的初始化方法如下:

- (instancetype)initWithURL:(NSURL *)fileURL page:(NSInteger)page password:(NSString *)phrase
  1. 判断fileURL参数的正确性,根据fileURL, phrase打开对PDF文档的引用_PDFDocRef;
  2. 检查PDF文档的页数,并获取对打开页的引用_PDFPageRef;
  3. 从_PDFPageRef中获取页面的宽,高,及X,Y方向的偏移量;
  4. 依据获取的信息构建viewRect,构建ReaderContentPage;
  5. 构建引用链接数组, 并返回ReaderContentPage;

##ReaderMainPagebar视图的初始化
ReaderMainPagebar其实是一个阅读进度条,显示阅读信息,并支持跳转到具体的某PDF页。

ReaderMainPagebar的实现文件中封装了如下字段:

ReaderDocument *document;                //PDF文档
ReaderTrackControl *trackControl;        //阅读跟踪控件
NSMutableDictionary *miniThumbViews;    //存放每页的thumb,缓存的作用
ReaderPagebarThumb *pageThumbView;        //当前页的thumb

UILabel *pageNumberLabel;                //页码
UIView *pageNumberView;                    //页码视图
NSTimer *enableTimer;                    //使能定时器
NSTimer *trackTimer;                    //跟踪定时器

ReaderMainPagebar的视图层次结构如下:

UIView --- pageNumberView --- pageNumberLabel
                          |--- trackControl ------- pageThumbView
                                              |------- ReaderPagebarThumb1
                                              |------- ReaderPagebarThumb2
                                              |------- ....

ReaderMainPagebar的初始化方法如下,其以ReaderDocument为参数:

- (instancetype)initWithFrame:(CGRect)frame document:(ReaderDocument *)object
  1. 调用父类initWithFrame:, 设置视图属性, 构建阴影视图;
  2. 构建pageNumberView视图;
  3. 构建trackControl视图,并在控制器上设置控制器事件与消息的对应关系

    UIControlEventTouchDown --- trackViewTouchDown:
    UIControlEventValueChanged --- trackViewValueChanged:
    UIControlEventTouchUpOutside --- trackViewTouchUp:
    UIControlEventTouchUpInside --- trackViewTouchUp:     
    
  4. 设置document, 实例化miniThumbViews

下面再来看一下其重载的消息layoutSubview,对子视图进行重新布局的逻辑:

  1. 根据thumb的宽和thumb间的间距重新设置trackControl的frame;
  2. 如果pageThumbView为空,则构建一个实例,并添加为trackControl的子视图;
  3. 根据document的当前页更新pageThumbView,即调用:[self updatePageThumbView:[document.pageNumber integerValue]]
    1. 计算此pageThumbView的frame,根据document的总页数,并设置其位置;
    2. 构建一个ReaderThumbRequest请求, 向ReaderThumbCache请求thumb图片并显示;
  4. 对于阅读进度上的每一page, 判断其是否有ReaderPagebarThumb在miniThumbViews中,如果在, 则复用,重新设置其frame,并显示,如果没在,则构建ReaderThumbRequest请求,向ReaderThumbCache请求, 并添加到trackControl中;

版权所有, 转载请著明出处,保留链接。