Статьи

Использование ScrollStyle с UIPageViewController

Одним из изменений, представленных в iOS 6, является возможность использовать UIPageViewController для использования анимации прокрутки вместо стандартного стиля перелистывания страниц. Читайте дальше, чтобы узнать, как мы можем использовать это, чтобы легко создать эффект просмотра прокрутки с разбивкой по страницам!

Чтобы изучить эти изменения, мы создадим приложение для оценки изображений, в котором пользователи смогут пролистывать разные картинки и давать оценку каждому из них. Хотя предпосылка является базовой, она даст вам хороший шанс начать использовать UIPageControl с новым стилем.


Прежде чем мы начнем, нам нужно несколько фотографий. Перейдите в Википедию и загрузите 5 разных картинок, которые лицензируются под креативным достоянием. Создайте новый проект Xcode с пустым шаблоном. Назовите его «ImageRater» или что-то подобное. Также убедитесь, что целевое устройство установлено на iPad, сюжетная запись отключена, а ARC включен.

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


Далее нам нужно создать объект модели и контроллер представления, отвечающий за отображение изображения. Создайте новый класс с именем «ImageModel», наследующий от NSObject. Это будет простой класс модели, который содержит строку, представляющую имя файла изображения, и целое число, которое представляет «оценку» изображения:

imagemodel.h

1
2
3
4
5
6
7
8
9
@interface ImageModel : NSObject
 
— (id)initWithImageName:(NSString *)imageName;
 
@property (nonatomic, strong) NSString *imageName;
 
@property (nonatomic) NSInteger rating;
 
@end

Мы также создали собственный метод init, который облегчит создание их с именем изображения. В реализации файла .m метод init выглядит так:

ImageModel.m

01
02
03
04
05
06
07
08
09
10
11
12
13
@implementation ImageModel
 
— (id)initWithImageName:(NSString *)imageName
{
    self = [super init];
    if (self)
    {
        _imageName = imageName;
        _rating = 0;
    }
     
    return self;
}

Контроллер представления, который будет отображать изображение, также будет очень простым. Создайте новый класс с именем ImageViewController (наследующий UIViewController) и присвойте ему следующие свойства (а также импортируйте ImageModel.h):

ImageViewController.h

01
02
03
04
05
06
07
08
09
10
#import «ImageModel.h»
 
@interface ImageViewController : UIViewController
 
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UILabel *label;
 
@property (nonatomic, strong) ImageModel *model;
 
@end

И в файле .m добавьте следующий код:

ImageViewController.m

01
02
03
04
05
06
07
08
09
10
11
12
13
— (void)viewDidLoad
{
    [super viewDidLoad];
     
    [self.view setBackgroundColor:[UIColor whiteColor]];
     
    CGRect insetFrame = CGRectMake(20, 80, self.view.frame.size.width — 40, self.view.frame.size.height — 100);
    _imageView = [[UIImageView alloc] initWithFrame:insetFrame];
    _imageView.backgroundColor = [UIColor clearColor];
    [_imageView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
    [_imageView setImage:[UIImage imageNamed:_model.imageName]];
    [[self view] addSubview:_imageView];
}

Когда представление загружается, мы устанавливаем цвет фона представления и настраиваем imageView.


Создайте новый класс с именем «RootViewController» (наследующий от UIViewController). Здесь будет происходить действие. Он будет содержать PageViewController, метку, которая показывает имя и рейтинг изображения, а также шаговый механизм, который позволяет пользователю устанавливать оценку для изображения.

Создав класс, откройте файл заголовка делегата приложения, импортируйте класс RootViewController и установите его в качестве свойства:

AppDelegate.h

1
2
3
4
5
6
7
8
9
#import «RootViewController.h»
 
@interface AppDelegate : UIResponder <UIApplicationDelegate>
 
@property (strong, nonatomic) UIWindow *window;
 
@property (strong, nonatomic) RootViewController *viewController;
 
@end

И в файле реализации установите его в качестве корневого контроллера представления:

AppDelegate.m

01
02
03
04
05
06
07
08
09
10
11
— (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
     
    // Override point for customization after application launch.
    self.viewController = [[RootViewController alloc] init];
     
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

Откройте файл .h RootViewController и присвойте ему следующие свойства, чтобы он соответствовал методам UIPageViewControllerDataSource и Delegate:

RootViewController.h

01
02
03
04
05
06
07
08
09
10
11
@interface RootViewController : UIViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
 
@property (nonatomic, strong) UIPageViewController *pageViewController;
@property (nonatomic, strong) NSMutableArray *modelArray;
@property (nonatomic) NSInteger vcIndex;
@property (nonatomic, strong) UIStepper *rateStepper;
@property (nonatomic, strong) UILabel *imageLabel;
 
— (void)stepperValueChanged:(id)sender;
 
@end

PageViewController будет отвечать за отображение ImageViewControllers, а массив модели будет отвечать за хранение объектов imageModel. VcIndex будет отвечать за отслеживание текущего индекса страницы. RateStepper позволит пользователю оценить изображение вверх или вниз. ImageLabel будет отображать имя и рейтинг текущего изображения.

Метод stepperValueChanged обновит соответствующую модель изображения, установив ее рейтинг вверх или вниз.

В файле .m RootViewController импортируйте ImageViewController и настройте контроллеры представления для контроллера представления страницы:

RootViewController.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
#import «ImageViewController.h»
 
@implementation RootViewController
 
— (void)viewDidLoad
{
    [super viewDidLoad];
     
    [[self view] setBackgroundColor:[UIColor blackColor]];
     
    _modelArray = [NSMutableArray arrayWithObjects:[[ImageModel alloc] initWithImageName:@»cat.jpg»],
                   [[ImageModel alloc] initWithImageName:@»DawnFlight.jpeg»],
                   [[ImageModel alloc] initWithImageName:@»James.jpg»],
                   [[ImageModel alloc] initWithImageName:@»MOS_KIM.jpg»],
                   [[ImageModel alloc] initWithImageName:@»Pterophorus.jpg»],
                   nil];
     
     
    _pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
                                                          navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                                                                        options:[NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:50.0f] forKey:UIPageViewControllerOptionInterPageSpacingKey]];
     
    _pageViewController.delegate = self;
    _pageViewController.dataSource = self;

Здесь происходит несколько вещей. Сначала мы устанавливаем наш цвет фона на черный. Во-вторых, мы выделяем init нашего pageViewController с переходом scollStyle, горизонтальной ориентацией навигации и словарем для наших опций. Словарь содержит NSNumber, в котором говорится, что между каждым viewController должен быть интервал в 50 точек. Затем мы продолжаем устанавливать себя в качестве делегата и источника данных для pageViewController.

Далее нам нужно настроить начальный viewController для запуска с:

RootViewController.m

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
_pageViewController.delegate = self;
   _pageViewController.dataSource = self;
    
   ImageViewController *imageViewController = [[ImageViewController alloc] init];
   imageViewController.model = [_modelArray objectAtIndex:0];
   NSArray *viewControllers = [NSArray arrayWithObject:imageViewController];
 
   [self.pageViewController setViewControllers:viewControllers
                                 direction:UIPageViewControllerNavigationDirectionForward
                                  animated:NO
                                completion:nil];
    
   [self addChildViewController:_pageViewController];
   [self.view addSubview:_pageViewController.view];
    
   [_pageViewController didMoveToParentViewController:self];
    
   CGRect pageViewRect = self.view.bounds;
   pageViewRect = CGRectInset(pageViewRect, 40.0, 80.0f);
   self.pageViewController.view.frame = pageViewRect;
    
   self.view.gestureRecognizers = _pageViewController.gestureRecognizers;

Здесь мы создаем новый контроллер представления содержимого, добавляем его в массив, а затем устанавливаем этот массив в качестве свойства viewControllers для нашего pageController. Затем мы добавляем pageViewController как наш childViewController, а затем pageViewControllersView к нашему собственному. Затем мы даем ему некоторую вставку, чтобы он не занимал весь вид, и устанавливаем наши распознаватели жестов на идентификаторы pageViewControllers, чтобы все было синхронизировано.

Наконец, чтобы завершить просмотр, нам нужно добавить степпер и метку, которая описывает рисунок:

RootViewController.m

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
    self.view.gestureRecognizers = _pageViewController.gestureRecognizers;
     
    _rateStepper = [[UIStepper alloc] initWithFrame:CGRectMake(40, 680, 40, 30)];
    [_rateStepper addTarget:self action:@selector(stepperValueChanged:) forControlEvents:UIControlEventValueChanged];
    [_rateStepper setMinimumValue:0];
    [_rateStepper setMaximumValue:10];
    [_rateStepper setIncrementImage:[UIImage imageNamed:@»arrowup»] forState:UIControlStateNormal];
    [_rateStepper setDecrementImage:[UIImage imageNamed:@»arrowdown»] forState:UIControlStateNormal];
    [[self view] addSubview:_rateStepper];
     
    _imageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 40)];
    _imageLabel.backgroundColor = [UIColor clearColor];
    _imageLabel.textColor = [UIColor whiteColor];
    [_imageLabel setFont:[UIFont boldSystemFontOfSize:20]];
    [_imageLabel setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
    [_imageLabel setTextAlignment:NSTextAlignmentCenter];
    ImageModel *model = [_modelArray objectAtIndex:0];
    _imageLabel.text = [NSString stringWithFormat:@»%@ — Rating: %d», model.imageName, model.rating];
    [[self view] addSubview:_imageLabel];
}

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

Если мы сейчас создадим и запустим наше приложение, оно должно выглядеть примерно так:

Неполное приложение

Это хорошее начало, но изображения еще не прокручиваются, и степпер вылетает из приложения. Нам также не хватает индикаторов страницы. Давайте заполним эти пробелы:


Нам нужно реализовать методы источника данных, которые сообщают pageViewController, какие контроллеры представления должны быть загружены до и после текущего:

RootViewController.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
#pragma mark —
#pragma mark — UIPageViewControllerDelegate Method
 
— (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
    ImageViewController *contentVc = (ImageViewController *)viewController;
     
    NSUInteger currentIndex = [_modelArray indexOfObject:[contentVc model]];
    _vcIndex = currentIndex;
    [_rateStepper setValue:[[contentVc model] rating]];
    ImageModel *model = [_modelArray objectAtIndex:_vcIndex];
    [_imageLabel setText:[NSString stringWithFormat:@»%@ — Rating: %d», model.imageName, model.rating]];
     
    if (currentIndex == 0)
    {
        return nil;
    }
     
    ImageViewController *imageViewController = [[ImageViewController alloc] init];
    imageViewController.model = [_modelArray objectAtIndex:currentIndex — 1];
    return imageViewController;
}
 
— (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    ImageViewController *contentVc = (ImageViewController *)viewController;
     
    NSUInteger currentIndex = [_modelArray indexOfObject:[contentVc model]];
    _vcIndex = currentIndex;
    [_rateStepper setValue:[[contentVc model] rating]];
    ImageModel *model = [_modelArray objectAtIndex:_vcIndex];
    [_imageLabel setText:[NSString stringWithFormat:@»%@ — Rating: %d», model.imageName, model.rating]];
     
    if (currentIndex == _modelArray.count — 1)
    {
        return nil;
    }
     
    ImageViewController *imageViewController = [[ImageViewController alloc] init];
    imageViewController.model = [_modelArray objectAtIndex:currentIndex + 1];
    return imageViewController;
}

Оба эти метода довольно похожи. Сначала мы получаем соответствующий viewController до или после текущего (и приводим его к ImageViewController). Затем мы посмотрим, где находится индекс модели этого viewController по отношению к массиву нашей модели. Найдя его, мы устанавливаем текущее значение vcIndex на currentIndex. Мы также обязательно обновим текущее значение степпера и обновим строку метки для отображения имени текущего изображения и значения рейтинга.

Если индекс выходит за пределы (определяется по 0 или по количеству модельного массива — 1), то мы не возвращаем новый контроллер представления. Если есть модель для загрузки, мы создаем новый ImageViewController, устанавливаем его модель для соответствующей модели в массиве и возвращаем viewController.

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

RootViewController.m

01
02
03
04
05
06
07
08
09
10
11
12
#pragma mark —
#pragma mark — UIPageViewControllerDataSource Method
 
— (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
{
    return _modelArray.count;
}
 
— (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
    return 0;
}

Это так просто. Для метода count мы просто возвращаем счетчик модельного массива, а для текущего Index мы просто возвращаем то, с чего он должен начинаться, то есть 0.


Вся тяжелая работа была проделана! Чтобы упорядочить изображения, просто реализуйте наш метод «stepperValueChanged» и добавьте следующий код:

RootViewController.m

1
2
3
4
5
6
7
8
#pragma mark —
#pragma mark — Private Methods
— (void)stepperValueChanged:(id)sender
{
    ImageModel *model = [_modelArray objectAtIndex:_vcIndex];
    [model setRating:[_rateStepper value]];
    [_imageLabel setText:[NSString stringWithFormat:@»%@ — Rating: %d», model.imageName, model.rating]];
}

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

Создайте и запустите приложение сейчас, и оно должно выглядеть примерно так:

Готовое приложение

Мы рассмотрели, как настроить очень простой PageViewController с использованием нового стиля перехода прокрутки без использования XIB или раскадровки. Это должно дать вам четкое представление о том, как работает PageViewController, и позволит вам изменить его для работы в ваших собственных приложениях!