Статьи

iOS SDK: добавление оглавления в iPad Reader

Это третий выпуск из серии о создании iPad-ридера для книги «Война миров» . В сегодняшнем уроке я покажу, как добавить оглавление в проект. При этом я расскажу о работе с plist-файлами, наложением слоев, базовыми анимациями UIView и ручным созданием компонентов интерфейса.

В уроке , представленном на прошлой неделе , я продемонстрировал, как добавить в проект UISlider , который позволил бы пользователю быстро «пролистывать» вперед или назад PDF-файл « Войны миров» . Это важная функция для любого PDF-ридера, но она по-прежнему ложится тяжелым бременем на пользователя, если он хочет перейти к определенной главе или разделу. Следовательно, добавление оглавления (далее именуемого просто «оглавление») выглядело как следующий логический шаг, и в опросе, приложенном к последнему уроку, 68% наших читателей проголосовали за это. В сегодняшних шагах я покажу вам, как это сделать.

Важно отметить, что в тексте « Войны миров», который используется в этом руководстве, еще нет страницы оглавления в документе PDF. Следовательно, мы сосредоточимся на создании собственного TOC UIView .

Последнее замечание перед тем, как мы начнем: пока это руководство в первую очередь полагалось на Интерфейсный Разработчик для настройки и создания пользовательского интерфейса. В этом уроке я отойду от Interface Builder и создам несколько новых компонентов интерфейса UIKit программным способом в нашем главном контроллере представления. Иногда я предпочитаю управлять созданием пользовательского интерфейса таким образом, поэтому я решил показать его как альтернативный подход в образовательных целях. Просто поймите, что все компоненты UIKit, которые я создаю в коде, могли быть созданы в Интерфейсном Разработчике и распакованы из NIB.

Лучшие приложения для чтения на iPad предоставляют пользователям постоянную кнопку, которая будет переходить непосредственно к оглавлению книги. Мы сделаем то же самое здесь, добавив UIButton в верхнем правом углу дисплея книги. Мы могли бы легко сделать эту кнопку иконкой или нарисовать ее простым градиентом, но вместо этого мы сделаем все просто. Кнопка будет иметь прозрачный фон, синий текст и текст «Содержание» в качестве заголовка.

Для начала откройте WOTWViewController.h и добавьте следующий элемент данных:

1
2
3
4
5
@interface WOTWViewController : LeavesViewController {
    CGPDFDocumentRef bookPDF;
    UIButton *contentsButton;
    UISlider *pageSlider;
}

Пока вы находитесь в файле интерфейса, продолжайте и объявите метод, который будет отвечать за создание кнопки вручную:

Затем переключитесь на файл WOTWViewController.m и addContentsUIButton метод addContentsUIButton :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
— (void)addContentsUIButton
{
    if(!contentsButton)
    {
        // Basic Initialization
        contentsButton = [[UIButton alloc] initWithFrame:CGRectMake(460.0f, 5.0f, 100.0f, 45.0f)];
        [contentsButton setTitle:@»Contents» forState:UIControlStateNormal];
        [contentsButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
        [contentsButton setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted];
        contentsButton.backgroundColor = [UIColor clearColor];
 
        // When the button is tapped, trigger the «displayTableOfContents» method
        [contentsButton addTarget:self action:@selector(displayTableOfContents) forControlEvents:UIControlEventTouchUpInside];
         
        // Hide the button by default as it shouldn’t show on the book cover
        contentsButton.hidden = YES;
 
        // Add the button as a subview of LeavesView
        [self->leavesView addSubview:contentsButton];
    }
}

Поскольку одновременно должна существовать только одна кнопка содержимого, мы начинаем этот метод со строки 3, проверяя, что мы еще не создали и не добавили эту кнопку в представление «Листья». Если у нас есть, вызов метода просто не будет выполнять любой код создания кнопки.

Строки 5 — 10 — это несколько стандартных операторов создания кнопок. Стоит отметить, что в строке 6 вызывается метод -initWithFrame: который является назначенным инициализатором для UIButton , и настраивается для кнопки значение 460 по оси x, значение 5 по оси y, ширина 100 и высота 45. Стоит также отметить, что свойства title и titleColor UIButton зависят от состояния. Это позволяет визуально реагировать на такие события, как нажатие или отключение кнопки.

Если раньше вы только создавали кнопки с помощью Interface Builder, в строке 12 все становится немного интереснее. В этой строке показано, как вручную установить, какой селектор должен вызываться для одного из различных событий управления кнопками. Если вам интересно, как Interface Builder достиг этого, то теперь это должно быть немного яснее.

Далее скрытое свойство кнопки устанавливается на «ДА». Это происходит потому, что читатель изначально загружается с отображенной обложкой книги, и кажется неуместным показывать ссылку на оглавление, по крайней мере, до следующей страницы.

В строке 19 мы добавляем наш вновь созданный UIButton непосредственно в объект LeavesView в качестве подпредставления. Кажется логически логичным сделать эту кнопку частью LeavesView, и это не позволит нам вспомнить переключение как LeavesView, так и кнопки содержимого при отображении оглавления. Также можно интегрировать кнопку содержимого с анимацией оглавления, используя этот подход (подробнее об этом позже).

Конечно, мы еще не совсем закончили. Нам все еще нужно вызывать этот метод где-то в жизненном цикле программы, и нам также нужно отпустить выделенную нами кнопку.

-viewDidLoad методу -viewDidLoad , чтобы добавить кнопку с этой строкой кода:

— (void) viewDidLoad

1
[self addContentsUIButton];

Это кажется хорошим местом для добавления кнопки TOC, но вы задаетесь вопросом, почему я пытаюсь использовать вызов метода вместо простого написания кода в этом методе изначально? Когда я впервые создавал эту функцию, я сделал. Однако при первоначальном добавлении всего кода непосредственно в viewDidLoad все стало действительно грязно, и было сложнее получить общее представление о том, что происходило в течение жизненного цикла приложения. Разделение больших кусков кода на независимые методы решило эту проблему и сделало проект более понятным и поддерживаемым.

Поскольку мы выделили кнопку содержимого из viewDidLoad , мы должны также отпустить ее как в -viewDidUnload и в -dealloc .

— (void) viewDidUnload

1
[contentsButton release], contentsButton = nil;

— (недействительно) dealloc

1
[contentsButton release];

Вспомните сверху, что мы изначально установили contentsButton скрытым. Нам нужно добавить несколько строк кода в LeavesView делегата -leavesView:didTurnToPageAtIndex: чтобы показать кнопку содержимого после того, как пользователя больше нет на титульной странице. Это достаточно просто:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
— (void) leavesView:(LeavesView *)leavesView didTurnToPageAtIndex:(NSUInteger)pageIndex
{
    if((int) self.pageSlider.value != pageIndex)
    {
        self.pageSlider.value = (float) pageIndex;
    }
     
    if(pageIndex > 0)
    {
        contentsButton.hidden = NO;
    }
    else
    {
        contentsButton.hidden = YES;
    }
}

Если вы строите и запускаете проект на этом этапе, вы должны увидеть кнопку содержимого, наложенную в верхнем правом углу представления книги. Однако, если вы на самом деле нажмете кнопку, приложение displayTableOfContents потому что мы еще не реализовали селектор displayTableOfContents , который будет пытаться вызвать действие кнопки.

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

1
UIView *tableOfContentsView;

Затем объявите метод для обработки добавления оглавления в LeavesView:

1
— (void) addTableOfContentsUIView;

Теперь перейдите к WOTWViewController.m и добавьте код, необходимый для создания TOC UIView :

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
— (void) addTableOfContentsUIView
{
    if(!tableOfContentsView)
    {
        // Create the TOC UIView.
        tableOfContentsView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0, 563.0f, 845.0f)];
        tableOfContentsView.backgroundColor = [UIColor whiteColor];
     
        // Create the Book Title.
        UILabel *bookHeading = [[UILabel alloc] initWithFrame:CGRectMake(170.0f, 30.0f, 300.0f, 40.0f)];
        bookHeading.font = [UIFont fontWithName:@»Arial» size:24.0f];
        bookHeading.text = @»WAR OF THE WORLDS»;
        bookHeading.textColor = [UIColor blackColor];
        bookHeading.backgroundColor = [UIColor whiteColor];
        [bookHeading sizeToFit];
        [tableOfContentsView addSubview:bookHeading];
        [bookHeading release];
     
        // Add «BOOK I» subheader as left column header
        UILabel *bookOneSubtitle = [[UILabel alloc] initWithFrame:CGRectMake(105.0f, 70.0f, 150.0f, 40.0f)];
        bookOneSubtitle.font = [UIFont fontWithName:@»Arial» size:22.0f];
        bookOneSubtitle.text = @»BOOK I»;
        bookOneSubtitle.textColor = [UIColor blackColor];
        bookOneSubtitle.backgroundColor = [UIColor whiteColor];
        [bookOneSubtitle sizeToFit];
        [tableOfContentsView addSubview:bookOneSubtitle];
        [bookOneSubtitle release];
     
        // Add «BOOK II» subheader as right column header
        UILabel *bookTwoSubtitle = [[UILabel alloc] initWithFrame:CGRectMake(375.0f, 70.0f, 150.0f, 40.0f)];
        bookTwoSubtitle.font = [UIFont fontWithName:@»Arial» size:22.0f];
        bookTwoSubtitle.text = @»BOOK II»;
        bookTwoSubtitle.textColor = [UIColor blackColor];
        bookTwoSubtitle.backgroundColor = [UIColor whiteColor];
        [bookTwoSubtitle sizeToFit];
        [tableOfContentsView addSubview:bookTwoSubtitle];
        [bookTwoSubtitle release];
             
        // Add the TOC directly to the Leaves view, but hide it at «layer» 0
        [self->leavesView insertSubview:tableOfContentsView atIndex:0];
    }
}

Приведенный выше метод создает базовый шаблон для представления TOC. Размеры представления такие же, как у отображения книги / PDF, и я добавил три метки: заголовок книги, подзаголовок для «Книги 1» и подзаголовок для «Книги 2». Это может показаться немного странным: разве « Война миров» не является одной книгой? Сегодня он опубликован в виде одного тома, но первоначально был напечатан в виде серии журналов и принял формат «Книга 1» и «Книга 2». Потому что это формат, который использует PDF, это то, что я встроил и в оглавление.

Если вы понимаете, как мы обрабатывали создание кнопки содержимого на предыдущем шаге, то большая часть приведенного выше кода также будет иметь смысл для вас. Единственное дополнительное примечание, которое стоит сделать, это то, что я снова вставил оглавление в иерархию LeavesView, только на этот раз я «похоронил» представление оглавления, используя insertSubview:atIndex: чтобы поместить его под всеми другими leavesView в leavesView . Скрывая вид с индексом 0, будет легко выявить оглавление, когда пользователь нажмет кнопку, и это также позволит нам легко создать анимацию с переворотом.

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

В WOTWViewController.m добавьте следующие строки кода:

01
02
03
04
05
06
07
08
09
10
11
12
— (void)displayTableOfContents
{
    // Hide the page slider
    self.pageSlider.hidden = YES;
     
    // Animate the transition with a horizontal flip from right to left
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:0.5f];
    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self->leavesView cache:YES];
    [self->leavesView bringSubviewToFront:tableOfContentsView];
    [UIView commitAnimations];
}

В строке 4 мы устанавливаем скрытый pageSlider . Это предотвращает попытку пользователя перейти к другому месту в PDF, пока оглавление покрывает отображение книги.

Строки 6 — 11 обрабатывают анимацию переворачивания страницы. Строка 8 устанавливает длительность этой анимации UIViewAnimationTransitionFlipFromRight половине секунды, а строка 9 указывает тип анимации ( UIViewAnimationTransitionFlipFromRight ), а также представление, к которому должен применяться переход.

Конечно, ни один из приведенных выше кодов не принесет нам пользы, если он не выполняется. Добавьте еще одну строку в -viewDidLoad чтобы создать представление оглавления:

1
[self addTableOfContentsUIView];

Если вы создаете и запускаете проект сейчас, у вас должна быть кнопка с работающим содержимым, которая отображает простое представление TOC.

Следующим шагом является добавление заголовков глав в представление TOC. Однако, поскольку мы собираемся динамически создавать главу, нам нужно сначала где-то хранить информацию о главе и странице. Одним из вариантов будет использование NSDictionary и статическое создание информации о главе / странице в WOTWViewController реализации WOTWViewController . Это, безусловно, сработает, но при работе с этим типом информации мне часто легче работать с файлами списка свойств (plist). Мы создадим два plist-файла для этого проекта: один для хранения информации для левого столбца («Книга 1»), а второй для хранения информации для второго столбца («Книга 2»).

CTRL + щелчок (или щелчок правой кнопкой мыши) в группе «Файлы поддержки» в Навигаторе проекта XCode. Выберите «Добавить файлы в WOTW». В появившемся окне выберите категорию «Ресурсы», а затем выберите «Список свойств». Нажмите «Далее», а затем введите имя «BookOne» для файла.

Откройте недавно добавленный файл BookOne.plist и добавьте пары ключ / значение, показанные ниже:

Книга Первая Листинг

Используйте тот же метод для создания BookTwo.plist и измените пары ключ / значение, как показано:

Книга Вторая Листинг

Теперь, когда мы сохранили информацию о главе / странице для PDF, мы готовы программно создать список глав. Конечно, в дополнение к простому перечислению контента, мы также должны позволить пользователям нажимать на любую из глав, чтобы перейти к этой части PDF. По этой причине каждая глава фактически будет UIButton .

Подходящее место для добавления списка глав — когда мы впервые создаем представление TOC, поэтому перейдите к - (void) addTableOfContentsUIView и добавьте следующие строки кода:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
— (void) addTableOfContentsUIView
{
    if(!tableOfContentsView)
    {
         
        // Create the TOC UIView.
        tableOfContentsView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0, 563.0f, 845.0f)];
        tableOfContentsView.backgroundColor = [UIColor whiteColor];
     
        // Create the Book Title.
        UILabel *bookHeading = [[UILabel alloc] initWithFrame:CGRectMake(170.0f, 30.0f, 300.0f, 40.0f)];
        bookHeading.font = [UIFont fontWithName:@»Arial» size:24.0f];
        bookHeading.text = @»WAR OF THE WORLDS»;
        bookHeading.textColor = [UIColor blackColor];
        bookHeading.backgroundColor = [UIColor whiteColor];
        [bookHeading sizeToFit];
        [tableOfContentsView addSubview:bookHeading];
        [bookHeading release];
     
        // Add «BOOK I» subheader as left column header
        UILabel *bookOneSubtitle = [[UILabel alloc] initWithFrame:CGRectMake(105.0f, 70.0f, 150.0f, 40.0f)];
        bookOneSubtitle.font = [UIFont fontWithName:@»Arial» size:22.0f];
        bookOneSubtitle.text = @»BOOK I»;
        bookOneSubtitle.textColor = [UIColor blackColor];
        bookOneSubtitle.backgroundColor = [UIColor whiteColor];
        [bookOneSubtitle sizeToFit];
        [tableOfContentsView addSubview:bookOneSubtitle];
        [bookOneSubtitle release];
     
        // Add «BOOK II» subheader as right column header
        UILabel *bookTwoSubtitle = [[UILabel alloc] initWithFrame:CGRectMake(375.0f, 70.0f, 150.0f, 40.0f)];
        bookTwoSubtitle.font = [UIFont fontWithName:@»Arial» size:22.0f];
        bookTwoSubtitle.text = @»BOOK II»;
        bookTwoSubtitle.textColor = [UIColor blackColor];
        bookTwoSubtitle.backgroundColor = [UIColor whiteColor];
        [bookTwoSubtitle sizeToFit];
        [tableOfContentsView addSubview:bookTwoSubtitle];
        [bookTwoSubtitle release];
     
     
        // Create NSDictionary objects from column plist files
        NSString *bookOneFilepath = [[NSBundle mainBundle]
                                 pathForResource:@»BookOne» ofType:@»plist»];
        NSDictionary *bookOneTOC = [[NSDictionary alloc] initWithContentsOfFile:bookOneFilepath];
 
        NSString *bookTwoFilepath = [[NSBundle mainBundle]
                                 pathForResource:@»BookTwo» ofType:@»plist»];
        NSDictionary *bookTwoTOC = [[NSDictionary alloc] initWithContentsOfFile:bookTwoFilepath];
     
        // Iterate over reach NSDictionary object and add UIButtons
        float colXOffset = 20.0f;
        float colYOffset = 100.0f;
     
        NSArray *sortedKeys1 = [[bookOneTOC allKeys] sortedArrayUsingComparator:^(id obj1, id obj2) {
         
            if ([obj1 integerValue] < [obj2 integerValue] ) {
                return (NSComparisonResult)NSOrderedAscending;
            }
         
            if ([obj1 integerValue] > [obj2 integerValue] ) {
                return (NSComparisonResult)NSOrderedDescending;
            }
            return (NSComparisonResult)NSOrderedSame;
        }];
     
        for(NSString *key in sortedKeys1)
        {
            int pageIndex = [key intValue];
            NSString *sectionTitle = [bookOneTOC objectForKey:key];
         
            UIButton *sectionButton = [[UIButton alloc] initWithFrame:CGRectMake(colXOffset, colYOffset, 250.0f, 35.0f)];
            [sectionButton setTitle:sectionTitle forState:UIControlStateNormal];
            sectionButton.tag = pageIndex;
            [sectionButton addTarget:self action:@selector(contentsButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
            sectionButton.enabled = NO;
         
            [sectionButton setBackgroundColor:[UIColor lightGrayColor]];
 
            [sectionButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
            [tableOfContentsView addSubview:sectionButton];
            [sectionButton release];
         
            colYOffset += 40.0f;
        }
        [bookOneTOC release];
     
        colXOffset = 293.0f;
        colYOffset = 100.0f;
     
        NSArray *sortedKeys2 = [[bookTwoTOC allKeys] sortedArrayUsingComparator:^(id obj1, id obj2) {
         
            if ([obj1 integerValue] < [obj2 integerValue] ) {
                return (NSComparisonResult)NSOrderedAscending;
            }
         
            if ([obj1 integerValue] > [obj2 integerValue] ) {
                return (NSComparisonResult)NSOrderedDescending;
            }
            return (NSComparisonResult)NSOrderedSame;
        }];
     
     
        for(NSString *key in sortedKeys2)
        {
            int pageIndex = [key intValue];
            NSString *sectionTitle = [bookTwoTOC objectForKey:key];
         
            UIButton *sectionButton = [[UIButton alloc] initWithFrame:CGRectMake(colXOffset, colYOffset, 250.0f, 35.0f)];
            [sectionButton setTitle:sectionTitle forState:UIControlStateNormal];
            [sectionButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
            sectionButton.tag = pageIndex;
            sectionButton.enabled = NO;
            [sectionButton addTarget:self action:@selector(contentsButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
         
            [sectionButton setBackgroundColor:[UIColor lightGrayColor]];
                [tableOfContentsView addSubview:sectionButton];
                [sectionButton release];
         
                colYOffset += 40.0f;
        }
        [bookTwoTOC release];
     
        // Add the TOC directly to the Leaves view, but hide it at «layer» 0
        [self->leavesView insertSubview:tableOfContentsView atIndex:0];
    }
     
}

Большая часть этого кода должна быть достаточно понятной для промежуточного разработчика. Однако есть несколько частей, которые заслуживают дальнейшего обсуждения.

Синтаксис, используемый в разделе sortedArrayUsingComparator: будет выглядеть странно, если вы ранее не программировали «Блоки» Objective-C. Полное обсуждение блоков выходит за рамки данного руководства, но важно знать, что для этого приложения код блока просто отвечает за сортировку массива ключей plist по номеру страницы в порядке возрастания. Это гарантирует, что главы будут отображаться в правильном порядке. Если вы хотите узнать больше о блоках, ознакомьтесь с официальной документацией .

Еще одно важное соображение заключается в том, что я отключаю каждый из объектов UIButton созданных перед добавлением их в представление TOC. Это leavesView для того, чтобы не дать leavesView на leavesView проходить и запускать кнопки, когда оглавление не отображается. Нам нужно снова включить их в методе -displayTableOfContents . Давайте продолжим и сделаем это сейчас.

Измените метод -displayTableOfContents следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
— (void)displayTableOfContents
{
    // Hide the page slider
    self.pageSlider.hidden = YES;
    
    // Traverse subviews looking for buttons
    for (UIView *subview in tableOfContentsView.subviews)
    {
        if([subview isKindOfClass:[UIButton class]])
        {
            UIButton *button = (UIButton *) subview;
            button.enabled = YES;
        }
    }
     
    // Animate the transition with a horizontal flip from right to left
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:0.5f];
    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self->leavesView cache:YES];
    [self->leavesView bringSubviewToFront:tableOfContentsView];
    [UIView commitAnimations];
}

Строки кода, выделенные выше, будут проходить через tableOfContentsView в tableOfContentsView ища те, которые соответствуют классу UIButton . Когда он находит его, он переключает его с отключенного на включенный.

Если вы внимательно изучили код на шаге 5, вы, вероятно, заметили, что всем UIButton объектам UIButton был предоставлен селектор contentsButtonPressed: для события внутренней обработки. Этот шаг создаст этот метод.

В WOTWViewController.m добавьте следующие строки кода:

На данном этапе нашей серии ничто в приведенном выше коде не должно быть для вас новым, поэтому я не буду подробно останавливаться на том, что происходит. Общие шаги суммированы в комментариях к коду.

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

За последние два урока в этой серии я закончил опросом, спрашивающим, должен ли я продолжить серию и, если да, то, что я должен написать о следующем. На этот раз, пожалуйста, «голосовать», оставив комментарий ниже. Если вы хотите, чтобы я продолжал писать о программе для чтения PDF с листьями, дайте мне знать, какие функции или усовершенствования, по вашему мнению, должны стать темой следующего урока. Несколько хороших вариантов: адаптация дисплея для различных ориентаций устройства, добавление закладок или сохранение состояния страницы.

С другой стороны, если вы предпочитаете, чтобы мы полностью охватили другую тему iOS SDK, оставьте комментарий, в котором мне будет сказано, о каком аспекте разработки iOS SDK или связанных проектах с открытым исходным кодом вы хотели бы, чтобы я писал о нем.

Чтобы продолжить серию iPad Reader, по крайней мере 10 читателей должны будут прокомментировать продолжение серии до 23 сентября 2011 года.

ОБНОВЛЕНИЕ 17/17/2011: у нас была отличная обратная связь в разделе комментариев, и похоже, что все, кто писал, заинтересованы по крайней мере еще в одном учебнике. Были получены голоса за следующие функции: поддержка ориентации устройства (7 голосов), поддержка закладок (2 голоса), двухстраничное отображение в альбомной ориентации (2 голоса), сохранение состояния страницы (1 голос), аннотация (1 голос), левая перелистывание страниц вправо (1 голос) и динамические ссылки в формате PDF (1 голос). Ориентация на устройства была явным победителем, поэтому в ближайшие 2 недели я постараюсь сделать еще один пост из этой серии, чтобы показать вам, как это можно сделать!

Спасибо за прочтение!