web-dev-qa-db-ja.com

複数のViewControllerを利用するUIPageViewControllerを実装する方法

UIPageViewControllerの詳細を学ぶために、簡単なテストアプリに取り組んでいます。私はそれを機能させていますが、私の実行が最良の方法であると確信していません。皆さんの何人かが私を正しい方向に向けてくれることを願っています。

基本的な理解を得るために、このチュートリアルを出発点として使用しました。 http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/

このチュートリアルでは、viewControllerによって表示されるページごとに1つのUIPageViewControllerを使用するアプリを作成します。ただし、UIPageViewControllerを使用して、レイアウトがまったく異なるページをスクロールする必要があります。したがって、チュートリアルをさらに進めるために、詳細ビューでUIPageViewControllerを使用して3つの異なるView Controllerを表示するマスター/詳細アプリケーションを作成しました。このテストアプリの画像とラベルを表示するだけで行き詰まりましたが、現在作成しているアプリには、tableview、imageView、textViews、またはtextFieldsを含む3つのviewControllerがあります。

これが私のテストアプリのストーリーボードです。

Storyboard

DetailViewControllerのデータソースとしてPageViewControllerを使用します。 DVCのviewDidLoadで、この方法で3つのコンテンツビューコントローラーfirstViewControllersecondViewController、およびthirdViewControllerで使用されるラベルと画像を設定します。

if ([[self.detailItem description] isEqualToString:@"F14's"]) {
    //Here the page titles and images arrays are created 
    _pageTitles = @[@"Grim Reapers", @"Breakin the Barrier!", @"Top Gun"];
    _pageImages = @[@"F14_Grim.jpg", @"F14boom.jpg", @"F14_topgun.jpg"];

    //Here I call a method to instantiate the viewControllers 
    FirstController *selectedController = [self viewControllerAtIndex:0];
    SecondController *nextController = [self viewControllerAtIndex:1];
    ThirdController *lastController = [self viewControllerAtIndex:2];
    [_vc addObject:selectedController];
    [_vc addObject:nextController];
    [_vc addObject:lastController];
    _vc1 = @[selectedController];


} else if ([[self.detailItem description] isEqualToString:@"F35's"]){
    //code is above is repeated

以下は、viewControllersをインスタンス化するメソッドです

- (UIViewController *)viewControllerAtIndex:(NSUInteger)index
{
    if (([self.pageTitles count] == 0) || (index >= [self.pageTitles count])) {
        return nil;
    }

    // Create a new view controller and pass suitable data.
    if (index == 0) {
        FirstController *fvc = [self.storyboard instantiateViewControllerWithIdentifier:@"FirstPageController"];
        fvc.imageFile = self.pageImages[index];
        fvc.titleText = self.pageTitles[index];
        fvc.pageIndex = index;
        if ([_vc count]) {
             //Here I have to replace the viewController each time it is recreated
             [_vc replaceObjectAtIndex:0 withObject:fvc];
        }
        return fvc;
    } else if (index == 1) {
//Code is repeated for remaining viewControllers

viewDidLoadのコードは、私が不必要な作業をしていると感じる領域の1つです。 DVCの読み込み時に3つすべてのView Controllerをインスタンス化する必要はないと思いますが、UIPageViewControllerDataSourceプロトコルメソッド(viewControllerBeforeViewControllerおよびviewControllerAfterViewController)に配列を提供する他の方法を知りませんでした。

viewControllerBefore.. 方法。

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{

    NSUInteger index = [_vc indexOfObject:viewController];

    if ((index == 0) || (index == NSNotFound)) {
        return nil;
    }

    index--;

    //notice here I call my instantiation method again essentially duplicating work I have already done!
    return [self viewControllerAtIndex:index];
}

要約すると、あるページから別のページにスワイプするたびに、不必要にView Controllerを再作成しているようです。これは、pageViewControllerがどのように機能するか、複雑なプロセスを乗り越える方法なのでしょうか。どんな入力でも素晴らしいでしょう!

[〜#〜] solution [〜#〜]

マットは、identifiersを使用する、信じられないほど簡単な解決策を提案しました。ストーリーボードで、既に実装されているストーリーボードの識別子を復元識別子として使用するボックスをオンにしました

RestorationId

次に、viewControllersの配列を作成するのではなく、viewDidLoadで、復元識別子に一致する文字列の配列を作成します。

if ([[self.detailItem description] isEqualToString:@"F14's"]) {
    _pageTitles = @[@"Grim Reapers", @"Breakin the Barrier!", @"Top Gun"];
    _pageImages = @[@"F14_Grim.jpg", @"F14boom.jpg", @"F14_topgun.jpg"];
    FirstController *selectedController = [self viewControllerAtIndex:0];
    [_vc addObject:@"FirstPageController"];
    [_vc addObject:@"SecondPageController"];
    [_vc addObject:@"ThirdPageController"];
    _vc1 = @[selectedController];

最後に、デリゲートメソッドのインデックスを決定するには、以前に行っていたことではなく、これを行います。

NSString * ident = viewController.restorationIdentifier;
NSUInteger index = [_vc indexOfObject:ident];

これで、View Controllerを不必要にインスタンス化することなく機能します。

最後に、誰かが私がここで持っているものを正確に使用している場合、次のスニペットをviewControllerAtIndex: 方法。

if ([_vc count]) {
     //Here I have to replace the viewController each time it is recreated
     [_vc replaceObjectAtIndex:0 withObject:fvc];
}
34
Ben

まず第一に、UIPageViewControllerの「ページ」を構成するView Controllerが本質的に完全に異なる可能性があることは絶対に正しいことです。 同じ View Controllerクラスのインスタンスでなければならないということは何もありません。

さて、実際の問題に取り掛かりましょう。これは、現在のView Controllerが与えられたときに、次または前のView Controllerを提供する方法が非常に賢明に必要だということです。実際、Page View Controllerを使用する際の主な問題です。

View Controllerの配列を保持するのは本当にひどいことではありません。結局のところ、View Controllerは軽量オブジェクトです(重量オブジェクトであるのはビューです)。ただし、これを処理する方法が不器用に見えることも正しいです。

私の提案は、ストーリーボードにView Controllerのインスタンスを保持する場合、なぜそれらの配列identifiersを保持しないのですか?これで、3つの文字列の配列ができました。どのくらい簡単に取得できますか?また、どの識別子がcurrentページとして使用されるビューを持つView Controllerに対応するかを追跡する単一のインスタンス変数が必要になります(これにより、どちらが「次」か「前");これは、配列にインデックスを付ける整数にすることができます。

この場合、ユーザーが「ページをめくる」たびにView Controllerをインスタンス化しても、まったく問題はありません。それがあなたが想定であるということです。そして、識別子によってこれを簡単に行うことができます。

最後に、Page View Controllerのスクロールスタイルを使用する場合、Page View ControllerはView Controllerをキャッシュし、デリゲートメソッドの呼び出しを停止する(または少なくとも呼び出し回数を減らす)ため、それを行う必要さえありません。 。

40
matt

私はこの同じ問題の解決策を探していたときにこの質問に出くわしました-彼が提供したガイダンスのマットとソリューションの説明のベンに感謝します。

これを自分で理解するためのサンプルプロジェクトを作成し、サンプルコードを要求するコメントに気づいたので、これをGitHubにアップロードしました。私のソリューションは、Mattが提案したアプローチとBenが述べたソリューションを次のように模倣しています。

  • PageViewControllerの一部である各View ControllerのStoryboardで復元IDを設定します。
  • 上記のRestorationIDを含むNSStringオブジェクトのNSArrayを使用します。
  • この構成に基づいて適切なView Controllerをインスタンス化します。

さらに、私が解決しようとした実装の問題には、子View Controllerから前後にナビゲートする機能が必要であったため、このサンプルプロジェクトは、ルートView Controllerに前または次のページに移動するよう求めることでその機能もサポートします(これも可能です特定のページに移動するために適用されます)。

GitHubのサンプルコード

サイドノート:ストーリーボード内からすべてを接続してView Controllerの順序を指定することができるUITabBarControllerに似ていることを期待していましたが、残念ながらまだXcode 6/iOS 8.1)。ただし、このソリューションに必要なコードはごく最小限で簡単です。

8
Derek Lee

基本的に、XCode 6.4(ページベースのアプリケーション)メソッドによって提供されるテンプレートと他の著者からの洞察( this 、他の回答からの@Benを含む)に基づいて、わずかに異なる方法を得ることができました:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.viewControllersArray = @[@"FirstViewController", @"SecondViewController"];
...
}

...

- (UIViewController *)viewControllerAtIndex:(NSUInteger)index {

    UIViewController *childViewController = [self.storyboard instantiateViewControllerWithIdentifier:[self.viewControllersArray objectAtIndex:index]];
    //childViewController.index = index;
    return childViewController;

}

- (NSUInteger)indexOfViewController:(UIViewController *)viewController {
    NSString *restorationId = viewController.restorationIdentifier;
    return [self.viewControllersArray indexOfObject:restorationId];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {

    NSUInteger index = [self indexOfViewController:(UIViewController *)viewController];
    if ((index == 0) || (index == NSNotFound)) {
        return nil;
    }

    index--;
    return [self viewControllerAtIndex:index];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {

    NSUInteger index = [self indexOfViewController:(UIViewController *)viewController];
    if (index == NSNotFound) {
        return nil;
    }

    index++;
    if (index == [self.viewControllersArray count]) {
        return nil;
    }
    return [self viewControllerAtIndex:index];
}
1
@interface ViewController ()
{
    NSMutableArray * StoryboardIds;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    StoryboardIds = [[NSMutableArray alloc]init];
    [StoryboardIds addObject:@"vc1"];
    [StoryboardIds addObject:@"vc2"];

    UIViewController *selectedController = [self viewControllerAtIndex:0];

    self.ProfilePageViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"PageControl"];
    self.ProfilePageViewController.dataSource = self;

    _viewcontrollers = [NSMutableArray new];

    [_viewcontrollers addObject:selectedController];

    [self.ProfilePageViewController setViewControllers:_viewcontrollers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];

    // Change the size of page view controller
    self.ProfilePageViewController.view.frame = CGRectMake(0, 0, self.bodypage.frame.size.width, self.bodypage.frame.size.height);

    [self addChildViewController:self.ProfilePageViewController];
    [self.ProfilePageViewController.view setFrame:self.bodypage.bounds];
    [self.bodypage addSubview:self.ProfilePageViewController.view];
    [self.ProfilePageViewController didMoveToParentViewController:self];

}
- (UIViewController *)viewControllerAtIndex:(NSUInteger)index
{
    if (([StoryboardIds count] == 0) || (index >= [StoryboardIds count])) {
        return nil;
    }

    if (index == 0) {
        vc1 *fvc = [self.storyboard instantiateViewControllerWithIdentifier:@"vc1"];
        fvc.Pageindex = index;
        if ([_viewcontrollers count]) {
            [_viewcontrollers replaceObjectAtIndex:0 withObject:fvc];
        }
        return fvc;
    }
    else
    {
        vc2 *fvc = [self.storyboard instantiateViewControllerWithIdentifier:@"vc2"];
        fvc.Pageindex = index;
        if ([_viewcontrollers count]) {
            [_viewcontrollers replaceObjectAtIndex:0 withObject:fvc];
        }
        return fvc;
    }

}

-(NSUInteger)indexofViewController
{
    UIViewController *currentView = [self.ProfilePageViewController.viewControllers objectAtIndex:0];

    if ([currentView isKindOfClass:[vc2 class]]) {
        return 1;
    }
    else{
        return 0;
    }

}


- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {

    NSUInteger index = [self indexofViewController];

    if ((index == 0) || (index == NSNotFound)) {
        return nil;
    }

    index--;
    return [self viewControllerAtIndex:index];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {

    NSUInteger index = [self indexofViewController];

    if (index == NSNotFound) {
        return nil;
    }

    index++;

    if (index == [StoryboardIds count]) {
        return nil;
    }
    return [self viewControllerAtIndex:index];
}
0
Raja Bhuma