しかし、TransitionStyleの初期値は"Page Curl"となっており、動作もこれに最適化されているようです。
"Scroll"も用意されていますが、この場合はちょっと思うようにいかない、カスタマイズが面倒、ということがあったのでまとめておきます。
1.UIPageViewContollerの部品構成
UIPageViewContoller
┗ _UIPageViewControllerContentView
┗ _UIQueuingScrollView
┗ UIPageControl
UIViewControllerのviewのサブビューにUIScrollViewとUIPageControlがある。(実際はその間にUIViewが配置されている。)
UIPageControlは、TransitionStyleが”Scroll”で、UIPageViewControllerDataSourceの次の二つのメソッドが実装されている時に追加される。
TransitionStyleの初期値は"Page Curl"なので注意。
TransitionStyleの初期値は"Page Curl"なので注意。
- presentationCountForPageViewController:
- presentationIndexForPageViewController:
UIPageControlを使用しない場合はUIScrollViewは画面サイズと同じ。NavigationBarがある場合も画面サイズとなり、NavigationBarの下に隠れる部分ができる。
UIPageControlを使用する場合は画面下にUIPageControlのエリアが取られ、UIScrollViewはそれを除いたサイズになる。
2.最初のページの表示
UIPageViewContollerはスクロールに応じて前後の画面を要求するメソッドを呼び出すが、最初の画面はこれとは別に設定する必要がある。
- (void)viewDidLoad
{
[super viewDidLoad];
[self setViewControllers:@[someViewController]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
}
3.ページ切替
各ページに異なるUIViewContollerのサブクラスを割り当てても良いが、例えば画像だけを入れ替えるような場合は同じ構成のUIViewContollerのサブクラスの複数インスタンスを割り当てたい。
AppleのPageContolサンプルで使われている方法を応用して、次のような実装をしてみた。
@interface MyPageViewController : UIPageViewController
<UIPageViewControllerDataSource>
//ViewControllerをキャッシュするArray
@property (nonatomic, strong) NSMutableArray *viewControllersCache;
@property (nonatomic) int numPages;
@end
@implementation MyPageViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.dataSource = self;
//NSMutableArrayを作り、ページ数分のMyContentViewControllerオブジェクトをセットする。実際の画像セットはMyContentViewController表示時に行う。
self.viewControllersCache = [[NSMutableArray alloc] init];
//Photoライブラリの画像をセットするなど
for (int n = 0; n < 3; n++) {
self.numPages++;
//storyboardでUIViewContollerを追加し、classをMyContentViewController、Storuboard IDを"pageContent"に設定しておく。
MyContentViewController *vc = [[self storyboard] instantiateViewControllerWithIdentifier:@"pageContent"];
vc.pageNumber = n;
[self.viewControllersCache addObject:vc];
}
[self setViewControllers:@[self.viewControllersCache.firstObject]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
}
//今表示されているviewControllerの次のviewControllerを返す。
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(NLInfoContentViewController *)viewController
{
if (viewController.pageNumber >= self,numPages - 1) return nil;
return [self.viewControllersCache objectAtIndex:viewController.pageNumber + 1];
}
//今表示されているviewControllerの前のviewControllerを返す。
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(NLInfoContentViewController *)viewController
{
if (viewController.pageNumber <= 0) return nil;
return [self.viewControllersCache objectAtIndex:viewController.pageNumber - 1];
}
@end
//UIImageViewを表示するViewController。
@interface MyContentViewController : UIViewController
//MyPageViewContrllerでインスタンス作成時にページ番号をセット
@property (nonatomic) int pageNumber;
//InterfaceBuilderでMyContentViewController内にUIImageを追加、コネクトする。
@property (nonatomic, weak) IBOutlet UIImageView * imageView;
@end
@implementation MyContentViewController
//viewWillAppearでUIImageを作る。
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];if (self.imageView.image == nil) {
[self setImage];
}
}
- (void)setImage
{
NSString *imgName = [NSString stringWithFormat:@"img%d", self.pageNumber];
self.imageView.image = [UIImage imageNamed:imgName];
//UIImageがself.imageViewのframeにリサイズされるので、必要に応じてself.imageViewのframeを変更する。
//最初のページのサイズ調整を行うのはviewDidLayoutSubviewsのタイミングが良い。
}
4.背景色設定
各ページの内容を表示するUIViewControllerのviewが1ページ分のエリアを覆うので、この背景色が優先される。これはInterfaceBuilderで設定可能。デフォルトはwhiteColor。
UIViewControllerの背景色をclearColorにした時はUIPageViewControllerのviewの背景色が有効になる。これはデフォルトでは未設定(透明)なので、見た目は黒になる。
この間にUIScrollViewがあるが、この背景色も未設定(透明)。
UIPageViewControllerのviewの属性はInterfaceBuilderでは設定できないので、コーディングで行う。
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
}
5.UIPageControlの表示
TransitionStyleを"Scroll"に設定し、次の二つのメソッドを実装する。
//実際のページ数(以上)の数を返す。
TransitionStyleを"Scroll"に設定し、次の二つのメソッドを実装する。
//実際のページ数(以上)の数を返す。
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
{
return self.numPages;
}
//初期表示ページに対応するindexを返す。
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
return 0;
}
UIPageControlは画面下に表示され、各ページの表示領域はこれを除いた部分になる。
UIPageControlの背景色はclearColorで、その下のUIPageViewControllerの背景色(透明=黒)が表示される。
6.UIPageControlの属性変更(位置変更以外)
UIPageViewControllerが表示するUIPageControlの属性はInterfaceBuilderでは変更できない。
コーディングで変更する場合は次の要領で行う。
- (void)viewDidLoad
{
[super viewDidLoad];
[UIPageControl appearance].pageIndicatorTintColor = [UIColor grayColor];
[UIPageControl appearance].currentPageIndicatorTintColor = [UIColor greenColor];
[UIPageControl appearance].backgroundColor = [UIColor blueColor];
}
viewDidLoad以外にviewWillAppear, viewWillLayoutSubviewsなどでも有効だが、viewDidLayoutSubviewsでは遅すぎる。
7.UIScrollView、UIPageControlのframe変更
UIPageControlを表示するとUIScrollViewのサイズが小さくなる。フルサイズ表示したい場合はリサイズが必要になる。
いささか無理やりだが、UIPageViewControllerのサブビュー階層をたどってUIScrollViewオブジェクトを探し、そのframeを変更することでリサイズすることができる。
UIPageControlの位置変更も同じ要領で可能。
リサイズが有効なのはviewDidLayoutSubviewsのタイミング。それ以前にframeを設定しても、サブビューのレイアウト時にUIPageControlが再計算してしまう。
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
UIScrollView *sv = [self findViewOfClass:[UIScrollView class] fromView:self.view];
sv.frame = self.view.frame;
UIPageControl *pc = [self findViewOfClass:[UIPageControl class] fromView:self.view];
pc.frame = CGRectMake(0, 100, pc.frame.size.width, pc.frame.size.height);
}
特定のクラス(またはそのサブクラス)のインスタンスを探すメソッド
- (id)findViewOfClass:(Class)class fromView:(UIView *)view
{
if ([view isKindOfClass:class]) return view;
for(UIView *subview in view.subviews) {
if ([subview isKindOfClass:class]) {
return subview;
}
id v = [self findViewOfClass:class fromView:subview];
if (v) return v;
}
return nil;
}
8.UIPageViewContollerの縦または横固定
UIPageViewContollerDelegateに次のメソッドがあるが、これを実装しただけでは縦・横固定にならない。
- pageViewControllerPreferredInterfaceOrientationForPresentation:
- pageViewControllerSupportedInterfaceOrientations:
UIPageViewContollerを表示するUINavigationControlに次のようなメソッドを追加する。
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
UINavigationControlがいくつかのViewControllerを扱う場合は、次のようにするとViewController別に縦・横の扱いを変えることができる。
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return [self.visibleViewController supportedInterfaceOrientations];
}
8.UIPageViewContollerの縦または横固定
UIPageViewContollerDelegateに次のメソッドがあるが、これを実装しただけでは縦・横固定にならない。
- pageViewControllerPreferredInterfaceOrientationForPresentation:
- pageViewControllerSupportedInterfaceOrientations:
UIPageViewContollerを表示するUINavigationControlに次のようなメソッドを追加する。
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
UINavigationControlがいくつかのViewControllerを扱う場合は、次のようにするとViewController別に縦・横の扱いを変えることができる。
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return [self.visibleViewController supportedInterfaceOrientations];
}