Статьи

Сборка iPad Reader для Войны Миров

В этом руководстве будет использован проект с открытым исходным кодом Leaves для создания простого устройства чтения iPad для «Войны миров » Х.Г. Уэллса. Попутно мы кратко рассмотрим код, обеспечивающий реализацию проекта Leaves, обсудим детали реализации и рассмотрим несколько альтернативных вариантов достижения аналогичного эффекта.

Leaves — это компонент iOS с открытым исходным кодом, который имитирует переход между страницами просмотра при переворачивании страниц, как и в официальном приложении Apple iBooks. Первоначально проект был написан Томом Брауом и доступен на GitHub , где позднее он был разветвлен Оле Бегеманом для добавления поддержки многостраничных макетов.

Чтобы увидеть проект Leaves в действии, взгляните на следующую видео-демонстрацию проекта, который научит вас создавать этот учебник:

Конечно, продемонстрированный выше эффект скручивания страницы не уникален ни для этой демонстрации, ни для приложения iBooks. Другие родные приложения для iOS (например, приложение «Карты») также используют аналогичный эффект. Чтобы получить полную предысторию о том, как именно Apple достигает этого в своих приложениях и почему разработчикам iOS SDK нужно прибегнуть к такому компоненту, как Leaves, ознакомьтесь с «Анимациями скручивания страниц в App Store» от Ole Begemann и «Apple iBooks Dynamic Page Curl» от Стивен Трутон-Смит.

Позже мы вернемся к некоторой теории, лежащей в основе проекта Leaves, но давайте продолжим и перейдем к созданию нашего читателя « Войны миров», чтобы понять, как код выглядит в действии.

Загрузочный файл, прикрепленный к этому сообщению Mobiletuts +, содержит исходный код Leaves и несколько общедоступных ресурсов (включая текст « Войны миров» ), использованных в проекте.

Откройте Xcode и создайте новый проект, используя шаблон «View-based Application». Назовите проект «WOTW» и выберите «iPad» в раскрывающемся списке семейства устройств.

Перетащите следующие файлы в группу «Вспомогательные файлы» в XCode:

Все перечисленные выше файлы можно найти в папке «Ресурсы», прилагаемой к данному руководству.

Затем добавьте фактический код компонента Leaves. Создайте новую группу под названием «Листья» в папке «WOTW» в навигаторе проекта XCode, а затем перетащите следующие файлы в группу «Листья»:

  • Utilities.h
  • Utilities.m
  • LeavesCache.h
  • LeavesCache.m
  • LeavesView.h
  • LeavesView.m
  • LeavesViewController.h
  • LeavesViewController.m

ПРИМЕЧАНИЕ. Обязательно установите флажок «Копировать объекты в папку целевой группы» при добавлении вышеуказанных файлов.

Анимация, созданная Leaves, зависит от QuartzCore Framework. Следовательно, вам нужно будет связать эту структуру с вашим проектом, чтобы Leaves работали. Чтобы сделать это в Xcode 4, начните с выбора «WOTW» в Project Navigator. Затем выберите цель «WOTW», а затем вкладку «Фазы сборки». Затем разверните раскрывающийся список «Связать двоичные файлы с библиотеками» и щелкните значок «плюс», чтобы добавить новую платформу. Наконец, выберите «QuartzCore» из списка доступных платформ и нажмите «Добавить». Результат этого процесса визуально отображается ниже:

Связывание с платформой QuartzCore в Xcode

Для нашего проекта мы хотим, чтобы читатель немедленно запустил текст « Войны миров» без дополнительной страницы. Для этого нам нужно сделать так, WOTWViewController класс WOTWViewController наследовался от класса LeavesViewController а не от UIViewController . Сделайте это, открыв WOTWViewController.h и изменив объявление интерфейса следующим образом:

1
2
3
@interface WOTWViewController : LeavesViewController {
     
}

Строка 1 выше изменяет WOTWViewController для наследования от LeavesViewController а не напрямую от UIViewController . Поскольку сам LeavesViewController наследуется от UIViewController , вы можете думать о WOTWViewController как о UIViewController

Итак, что мы получаем за нашу проблему? LeavesViewController соответствует источнику данных Leaves и протоколам делегатов (обсуждается позже) и определяет пользовательские реализации -loadView и -viewDidLoad которые добавляют специальный вид UIView называемый LeavesView к текущей иерархии представления. Класс LeavesView отвечает за большую часть работы за анимацию скручивания страницы.

Чтобы класс WOTWViewController мог наследоваться от класса LeavesViewController , нам необходимо импортировать код LeavesViewController . Добавьте следующие две строки #import в WOTWViewController.h :

1
2
#import «Utilities.h»
#import «LeavesViewController.h»

Хотите знать, для чего предназначен этот оператор импорта Utilities.h ? Как мы увидим позже, проект Leaves опирается на умную функцию, объявленную в этом файле под названием aspectFit . Многие проекты Cocoa-Touch поддерживают класс Utilities для объявления и определения вспомогательного кода, используемого в приложении.

На данный момент нам нужно всего лишь объявить один элемент данных для нашего проекта. В файле WOTWViewController.h добавьте следующую строку кода:

1
CGPDFDocumentRef bookPDF;

Мы будем использовать «bookPDF» в качестве ссылки на файл WOTW.pdf позже. CGPDFDocumentRef переменной CGPDFDocumentRef будет широко использоваться в этом учебном пособии и заслуживает дальнейшего изучения в официальной документации Apple .

Когда WOTWViewController создан, нам нужно инициализировать bookPDF данных bookPDF объявленный выше. Как показано в примере проекта Leaves, вы можете сделать это с помощью следующих строк кода:

1
2
3
4
5
6
7
8
— (id)init {
    if (self = [super init]) {
        CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR(«WOTW.pdf»), NULL, NULL);
        bookPDF = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
        CFRelease(pdfURL);
    }
    return self;
}

Вы видите проблему с подходом выше? Помните, что мы собираемся загружать наш контроллер представления из Интерфейсного Разработчика.

Пример проекта Leaves предполагает, что вы вручную LeavesViewController экземпляр LeavesViewController программным способом, вызвав метод -(id)init , например:

1
WOTWViewController *viewController = [[[WOTWViewController alloc] init];

Однако в нашем проекте мы хотим разархивировать WOTWViewController из нашего интерфейса Interface Builder, поэтому только что реализованная нами функция -(id)init никогда не будет вызываться. Вместо этого нам нужно предоставить пользовательскую реализацию для -(id)initWithCoder: метод, чтобы подключиться к процессу разархивирования NIB и выполнить пользовательскую инициализацию. Хорошо оставить init на месте, чтобы у вас была возможность создать этот контроллер представления вручную, но мы должны создать новый метод для инициализации PDF и вызвать этот метод из обоих -(id)init и -(id)initWithCoder:

Добавьте следующую строку кода в файл WOTWViewController.h :

Затем переключитесь на WOTWViewController.m и реализуйте метод следующим образом:

1
2
3
4
5
6
-(void)loadPDF
{
    CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR(«WOTW.pdf»), NULL, NULL);
    bookPDF = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
    CFRelease(pdfURL);
}

Наконец, вызовите метод loadPDF из инициализаторов класса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
— (id)init {
    self = [super init];
    if (self) {
        [self loadPDF];
    }
    return self;
}
 
-(id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self)
    {
        [self loadPDF];
    }
    return self;
}

Когда наш пользовательский контроллер представления разархивирован, будет вызван метод -(id)initWithCoder: и затем будет loadPDF метод loadPDF .

Есть еще одна вещь. Выделив память для ссылки на bookPDF , мы также должны освободить эту память, когда закончим с ней. Мы можем сделать это, добавив следующую строку кода в метод -(void)dealloc: ::

1
CGPDFDocumentRelease(bookPDF);

Напомним из шага 5, что класс LeavesViewController автоматически добавляет экземпляр класса LeavesView в иерархию представления. Класс LeavesView каким-то образом должен выяснить, какой контент должен отображаться в представлении, и это достигается путем вызова двух пользовательских методов источника данных: -(NSUInteger)numberOfPagesInLeavesView: и -(void)renderPageAtIndex:

Чтобы обеспечить пользовательскую реализацию для них, откройте WOTWViewController.m и добавьте следующие строки кода:

01
02
03
04
05
06
07
08
09
10
11
12
13
#pragma mark LeavesViewDataSource methods
 
— (NSUInteger) numberOfPagesInLeavesView:(LeavesView*)leavesView {
    return CGPDFDocumentGetNumberOfPages(bookPDF);
}
 
— (void) renderPageAtIndex:(NSUInteger)index inContext:(CGContextRef)ctx {
    CGPDFPageRef page = CGPDFDocumentGetPage(bookPDF, index + 1);
    CGAffineTransform transform = aspectFit(CGPDFPageGetBoxRect(page, kCGPDFMediaBox),
                                            CGContextGetClipBoundingBox(ctx));
    CGContextConcatCTM(ctx, transform);
    CGContextDrawPDFPage(ctx, page);
}

Метод -(NSUInteger)numberOfPagesInLeavesView: просто указывает, сколько страниц содержимого LeavesViewController отвечает за отображение. Если вы создавали контент для Leaves вручную или точно знали, сколько страниц в PDF, вы можете вернуть это число вручную здесь. Конечно, всегда лучше определить это число динамически, и именно это CGPDFDocumentGetNumberOfPages() функция CGPDFDocumentGetNumberOfPages() .

Метод -(void)renderPageAtIndex:inContext: отвечает за фактическое рисование CGContextRef после первого добавления содержимого PDF в контекст, переданный как ctx .

Постройте и запустите проект. Если все идет хорошо, теперь вы сможете увидеть обложку книги « Война миров» и пролистать страницы с анимацией скручивания страниц!

Окончательный файл WOTWViewController.h должен выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import «Utilities.h»
#import «LeavesViewController.h»
 
@interface WOTWViewController : LeavesViewController {
    CGPDFDocumentRef bookPDF;
}
 
-(void)loadPDF;
 
@end

И окончательный файл WOTWViewController.m должен выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#import «WOTWViewController.h»
 
@implementation WOTWViewController
 
#pragma mark — Initialization / Memory Management
 
— (id)init {
    self = [super init];
    if (self) {
        [self loadPDF];
    }
    return self;
}
 
-(id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self)
    {
        [self loadPDF];
    }
    return self;
}
 
-(void)loadPDF
{
    CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR(«WOTW.pdf»), NULL, NULL);
    bookPDF = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
    CFRelease(pdfURL);
}
 
— (void)dealloc
{
    CGPDFDocumentRelease(bookPDF);
    [super dealloc];
}
 
#pragma mark — LeavesViewDataSource methods
 
— (NSUInteger) numberOfPagesInLeavesView:(LeavesView*)leavesView {
    return CGPDFDocumentGetNumberOfPages(bookPDF);
}
 
— (void) renderPageAtIndex:(NSUInteger)index inContext:(CGContextRef)ctx {
    CGPDFPageRef page = CGPDFDocumentGetPage(bookPDF, index + 1);
    CGAffineTransform transform = aspectFit(CGPDFPageGetBoxRect(page, kCGPDFMediaBox),
                                            CGContextGetClipBoundingBox(ctx));
    CGContextConcatCTM(ctx, transform);
    CGContextDrawPDFPage(ctx, page);
}
 
@end

В своем исследовании Leaves я наткнулся на несколько дополнительных проектов с открытым исходным кодом, в которых добавлена ​​анимация перехода в виде страницы. Если вы нашли этот проект интересным, вам также следует проверить:

Оставьте комментарий ниже, если вы хотите увидеть учебник по одному из вышеперечисленных проектов!

Мы достигли определенного прогресса в этом уроке. Теперь мы можем взаимодействовать с « Войной миров» в формате PDF, а анимация скручивания страницы добавляет эстетическое чувство к электронной книге. Однако предстоит еще много работы, прежде чем это приложение будет готово к выпуску в App Store. Например, было бы замечательно, если бы пользователь автоматически возвращался к последней странице, к которой он обращался при запуске приложения, и было бы очень хорошо иметь UISlider или подобный компонент интерфейса для быстрого UISlider между разделами книги. Другие полезные функции могут включать поиск, выделение текста, оглавление или закладки.

Я хочу убедиться, что мои учебники по iOS SDK охватывают темы, в которых заинтересовано сообщество Mobiletuts +. Как вы думаете, мне следует создать полноценный eReader и поделиться своим исходным кодом? Или, возможно, вы хотели бы увидеть учебник по чему-то совершенно другому? Ответьте на следующий опрос и дайте мне знать!

ОБНОВЛЕНИЕ 7/7/2011: опрос закрыт. Более 60% респондентов выразили заинтересованность в том, чтобы увидеть больше постов о добавлении оглавления и / или UISlider, а также было опубликовано руководство с использованием UISlider (ссылка ниже).

Вы также можете отправить свой отзыв на мою учетную запись Twitter ( @markhammonds ), хотя я признаю, что обычно использую Twitter только тогда, когда не работаю над внештатными проектами или не пишу учебные пособия, поэтому вы можете просто вместо этого связаться со мной по электронной почте .