web-dev-qa-db-ja.com

iOS8のUISplitViewControllerのマスタービューにUINavigationControllerがある

私のUISplitViewControllerでは、マスタービューはUINavigationControllerを含むUITableViewControllerです。時々、ユーザーがテーブル内のアイテムを選択すると、マスタービューの既存のテーブルの上に別のUITableViewControllerをプッシュする必要があります。

IOS 7では、最初のUITableViewController内で

[self.navigationController pushViewController:otherTableVC animated:YES];

IOS 8の場合:

分割ビューを折りたたむと、otherTableVCが詳細ビューになります。次に、デバイスを回転させた後、2つのテーブルが並んで表示されます...

さらに悪いことに、デバイスに2つのペインが表示されている場合、コードは正常に機能し、2番目のテーブルがマスタービューの最初のテーブルの上にプッシュされます。しかし、2回転した後、2つのテーブルは再び並んでいます。 UISplitViewControllerの折りたたまれたモードが自分のナビゲーションコントローラーに干渉しているようです…

マスタービューで自分のUINavigationControllerを管理するにはどうすればよいですか?

ありがとうございました

編集済み:

プライマリビューと詳細ビューの両方にナビゲーションコントローラーがあります。そして、私の問題を解決するために、折りたたまれたモードで、追加のナビゲーションコントローラーを作成し、それをプライマリナビゲーションコントローラーにプッシュする必要があることを発見しました。

UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:otherTableVC];
[self.navigationController pushViewController:navController animated:YES];

だから私は、ナビゲーションコントローラーを別のナビゲーションコントローラー内にプッシュできることを発見しました。

12
PatrickV

簡単に言うと、UISplitViewControllerDelegateメソッドを使用してこの動作を制御できます。

splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:
splitViewController:separateSecondaryViewControllerFromPrimaryViewController:

あなたが本当にやりたいことは、iOS 8 UISplitViewControllerベースのアプリがあり、プライマリビューと詳細ビューの両方がUINavigationControllersであり、(これらのナビゲーションコントローラー内に)表示したいviewControllersがいくつかある状況に対処することだと思います分割ビューのプライマリ側または詳細側。以下の答えはこれを扱っています。また、詳細ナビゲーションコントローラのビューをプッシュするのではなく、ビューを置き換えたい場合もあります。

小さな注意点:以下のコードは、考えられるすべてのケースを扱っているわけではなく、いくつかの仮定があります。

  • 分割ビューが折りたたまれ、それらのビューがその上の詳細ビューによって隠されている場合、詳細ナビゲーションコントローラースタックで何も変更されることはないと予想されます。
  • UIViewControllerサブクラスにはすべてshouldDisplayInDetailedViewプロパティとshouldReplaceDetailedViewプロパティがあります
  • ShouldDisplayInDetailedViewプロパティが設定されている詳細ナビゲーションコントローラーにのみビューをプッシュすると想定します。
  • ビューコントローラは、詳細ビュー内のビューのnavigationControllerプロパティのsplitViewController:showDetailViewController:またはpushViewController:animated:を介して詳細側に追加されます(展開状態または折りたたみ状態)。
  • replace詳細ナビゲーションコントローラーのビューコントローラーは、splitViewController:showDetailViewController:を介してのみ追加され、プライマリビューコントローラーのビューとの対話からのみ追加されます。つまり、これはプライマリビューの場合にのみ発生します。折りたたまれた状態のときにコントローラーが隠されることはありません。
  • 分割ビューコントローラが展開されたときに詳細ビューに表示するBlankViewControllerがありますが、プライマリ側にとどまる必要があるビューコントローラしかありません。

SplitViewController:collapseSecondaryViewController:ontoPrimaryViewController:/ splitViewController:separateSecondaryViewControllerFromPrimaryViewController:ロジックの片側だけを実装し、もう一方の側のデフォルトの実装に応じて実装することはお勧めしません。 Apple UINavigationViewControllerを詳細側からプライマリナビゲーションコントローラースタックのviewControllerの1つとしてプライマリ側に配置し、他のビューコントローラーをその上にプッシュするなどの奇妙なことを行います。完全に理解しても、自分のコードから複製することはできません。したがって、プロセスの両側を自分で処理するのが最善です。

これは私が使用するものです:

#pragma mark -
#pragma mark Split View Controller delegate.

- (BOOL)splitViewController:(UISplitViewController *)splitViewController showViewController:(UIViewController *)vc sender:(id)sender
{
    //Standard behaviour.  This won't get called in our case when the split view is collapsed and the primary view controllers are obscured.
    return NO;
}

// Since we treat warnings as errors, silence warning about unknown selector below on UIViewController subclasses.
#pragma GCC diagnostic ignored "-Wundeclared-selector"


- (BOOL)splitViewController:(UISplitViewController *)splitViewController showDetailViewController:(UIViewController *)vc sender:(id)sender
{
    if (splitViewController.collapsed == NO)
    {
        // The navigation controller we'll be adding the view controller vc to.
        UINavigationController *navController = splitViewController.viewControllers[1];

        UIViewController *topDetailViewController = [navController.viewControllers lastObject];
        if ([topDetailViewController isKindOfClass:[BlankViewController class]] ||
           ([vc respondsToSelector:@selector(shouldReplaceDetailedView)] && [vc performSelector:@selector(shouldReplaceDetailedView)]))
        {
            // Replace the (expanded) detail view with this new view controller.
            [navController setViewControllers:@[vc] animated:NO];
        }
        else
        {
            // Otherwise, just Push.
            [navController pushViewController:vc animated:YES];
        }
    }
    else
    {
        // Collapsed.  Just Push onto the conbined primary and detailed navigation controller.
        UINavigationController *navController = splitViewController.viewControllers[0];
        [navController pushViewController:vc animated:YES];
    }

    // We've handled this ourselves.
    return YES;
}

- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController
{
    UINavigationController *primaryNavController = (UINavigationController *)primaryViewController;
    UINavigationController *secondaryNavController = (UINavigationController *)secondaryViewController;
    UIViewController *bottomSecondaryView = [secondaryNavController.viewControllers firstObject];
    if ([bottomSecondaryView isKindOfClass:[BlankViewController class]])
    {
        NSAssert([secondaryNavController.viewControllers count] == 1, @"BlankViewController is not only detail view controller");
        // If our secondary controller is blank, do the collapse ourself by doing nothing.
        return YES;
    }

    // We need to shift these view controllers ourselves.
    // This should be the primary views and then the detailed views on top.
    // Otherwise the UISplitViewController does wacky things like embedding a UINavigationController inside another UINavigation Controller, which causes problems for us later.
    NSMutableArray *newPrimaryViewControllers = [NSMutableArray arrayWithArray:primaryNavController.viewControllers];
    [newPrimaryViewControllers addObjectsFromArray:secondaryNavController.viewControllers];
    primaryNavController.viewControllers = newPrimaryViewControllers;

    return YES;
}

- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController
{
    UINavigationController *primaryNavController = (UINavigationController *)primaryViewController;

    // Split up the combined primary and detail navigation controller in their component primary and detail view controller lists, but with same ordering.
    NSMutableArray *newPrimaryViewControllers = [NSMutableArray array];
    NSMutableArray *newDetailViewControllers = [NSMutableArray array];
    for (UIViewController *controller in primaryNavController.viewControllers)
    {
        if ([controller respondsToSelector:@selector(shouldDisplayInDetailedView)] && [controller performSelector:@selector(shouldDisplayInDetailedView)])
        {
            [newDetailViewControllers addObject:controller];
        }
        else
        {
            [newPrimaryViewControllers addObject:controller];
        }
    }

    if (newDetailViewControllers.count == 0)
    {
        // If there's no detailed views on the top of the navigation stack, return a blank view  (in navigation controller) for detailed side.
        UINavigationController *blankDetailNavController = [[UINavigationController alloc] initWithRootViewController:[[BlankViewController alloc] init]];
        return blankDetailNavController;
    }

    // Set the new primary views.
    primaryNavController.viewControllers = newPrimaryViewControllers;

    // Return the new detail navigation controller and views.
    UINavigationController *detailNavController = [[UINavigationController alloc] init];
    detailNavController.viewControllers = newDetailViewControllers;
    return detailNavController;
}
24
Michael Wybrow

私のコードで動作するようにマイナーな変更を加えたSwift4バージョン:

func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
    if !isCollapsed {
        // in expanded mode set new VC as top view controller of the detail nav controller
        if let detailNavigationController = viewControllers[1] as? UINavigationController {
           detailNavigationController.setViewControllers([vc], animated: false)
        }
    } else {
        // in collapsed mode Push the new view controller on the master nav controller
        if let masterNavigationController = viewControllers[0] as? UINavigationController {
            masterNavigationController.pushViewController(vc, animated: true)
        }
    }
    return true
}

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {

    let masterNavigationController = primaryViewController as? UINavigationController
    let detailNavigationController = secondaryViewController as? UINavigationController
    let episodeDetailViewController = detailNavigationController?.viewControllers.first as? EpisodeDetailTableViewController
    if episodeDetailViewController?.episode == nil {
        // detail view is blank. We do not need to Push this onto the master
        return true
    }

    guard var newMasterViewControllers = masterNavigationController?.viewControllers else { return false }
    newMasterViewControllers.append(contentsOf: detailNavigationController?.viewControllers ?? [])
    masterNavigationController?.setViewControllers(newMasterViewControllers, animated: false)
    return true
}


func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? {
    let masterNavigationViewController = primaryViewController as? UINavigationController

    var newMasterViewControllers = [UIViewController]()
    var newDetailViewControllers = [UIViewController]()

    for vc in masterNavigationViewController?.viewControllers ?? [] {
        if vc is PodcastsTableViewController || vc is EpisodesTableViewController {
            newMasterViewControllers.append(vc)
        } else {
            newDetailViewControllers.append(vc)
        }
    }

    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let detailNavigationController = storyboard.instantiateViewController(withIdentifier: "splitViewDetailViewController") as! UINavigationController

    if newDetailViewControllers.count == 0 {
        let emptyEpisodeDetailViewController = storyboard.instantiateViewController(withIdentifier: "episodeDetail")
        newDetailViewControllers.append(emptyEpisodeDetailViewController)
    }

    masterNavigationViewController?.setViewControllers(newMasterViewControllers, animated: false)
    detailNavigationController.setViewControllers(newDetailViewControllers, animated: false)
    return detailNavigationController
}
0
funkenstrahlen