iPhoneのUISplitViewControllerが縦長でマスターではなく詳細なVCを表示する


177

Xcode 6でUniversal Storyboardを使用していますが、iOS 7以降をターゲットにしています。UISplitViewControlleriOS 8を実行しているiPhoneでネイティブにサポートされるを実装しました。Xcodeは自動的にiOS 7にバックポートします。iOS8を実行している縦向きでiPhoneでアプリを起動した場合を除いて、分割ビューの詳細ビューは非常にうまく機能します。最初にマスタービューコントローラーが表示されるはずでしたが、コントローラーが表示されます。iOS 8でアプリを実行すると、マスタービューコントローラーが正しく表示されるため、これはiOS 8のバグだと思いました。しかし、iOS 8は現在GMであり、これはまだ発生しています。分割ビューコントローラーを折りたたむときに(画面に表示されるビューコントローラーは1つだけ)、分割ビューコントローラーが表示されるときにマスタービューコントローラーが詳細ではなく表示されるようにするにはどうすればよいですか?

この分割ビューコントローラーをInterface Builderで作成しました。分割ビューコントローラーは、タブバーコントローラー内の最初のビューコントローラーです。マスターVCと詳細VCはどちらも、内部に埋め込まれたテーブルビューコントローラーを備えたナビゲーションコントローラーです。

回答:


238

ああ、これは私に数日間頭痛を引き起こし、これを行う方法を理解できませんでした。最悪の部分は、マスター/詳細テンプレートを使用した新しいXcode iOSプロジェクトの作成がうまく機能したことです。幸いなことに、結局のところ、その小さな事実が私が解決策を見つけた方法でした。

私が見つけたいくつかの投稿がありますが、解決策はに新しいprimaryViewControllerForCollapsingSplitViewController:メソッドを実装することUISplitViewControllerDelegateです。私はそれを無駄にしようとした。Appleがマスター/ディテールテンプレートで機能しているように見えることは、新しい(このすべてを言うには深く息をする)splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:デリゲートメソッドを実装することです(ここでもUISplitViewControllerDelegate)。docsによると、このメソッドは:

デリゲートに、プライマリビューコントローラーを調整し、セカンダリビューコントローラーを折りたたまれたインターフェイスに組み込むように要求します。

より具体的な詳細については、そのメソッドの説明部分を必ずお読みください。

Appleがこれを処理する方法は次のとおりです。

- (BOOL)splitViewController:(UISplitViewController *)splitViewController
collapseSecondaryViewController:(UIViewController *)secondaryViewController
  ontoPrimaryViewController:(UIViewController *)primaryViewController {

    if ([secondaryViewController isKindOfClass:[UINavigationController class]]
        && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[DetailViewController class]]
        && ([(DetailViewController *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)) {

        // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return YES;

    } else {

        return NO;

    }
}

この実装は基本的に次のことを行います。

  1. 場合secondaryViewController我々は(期待しているものですUINavigationController()、そして我々が期待しているものを見せているDetailViewController-あなたのビューコントローラ)が、(何のモデルを持っていないdetailItem「その後、)Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
  2. それ以外の場合は、「返してNO、分割ビューコントローラーにセカンダリビューコントローラーのコンテンツを折りたたまれたインターフェースに組み込みます」

iPhoneの縦向きの結果は次のとおりです(縦向きで開始するか、縦向きに回転するか、より正確にはコンパクトサイズクラス)。

  1. あなたの見解が正しい場合
    • モデルがあり、詳細ビューコントローラーを表示します
    • モデルはありませんが、マスタービューコントローラーを表示します
  2. あなたの見方が正しくない場合
    • マスターView Controllerを表示する

泥としてクリア。


8
素晴らしい答え!私は単純にサブクラス化しUISplitViewController、常にYESそのメソッドから戻り、iPhoneのマスターを常に縦向きに表示したいので、ストーリーボードの分割ビュークラスを変更しました。:)
ジョーダンH

2
デフォルトの詳細ビューコントローラーが設定されているため、「iPhone」が「ポートレート」モードの場合、マスタービューコントローラーを非表示にしたいのですが。どうやってやるの。私のマスターとディテールはどちらもVCタイプです。特に私の詳細はMMDrawerControllerです。助けてください
Harshit Gupta 2014

3
Joeyのサブクラス化の提案を試してUISplitViewControllerみましたが、それが機能しないことがわかりましたsplitViewController:collapseSecondaryViewController:ontoPrimaryViewController:。代わりに、AppleのテンプレートをコピーしてAppDelagateに配置しました。これにより、UISplitViewControllerの作成にもいくつかの変更が必要になりましたapplication didFinishLaunchingWithOptions:(Appleのテンプレートもコピーしました)。
Nick 14年

7
@joeyのコメントは、self.delegate = selfの設定で機能します。viewdidloadで!そして、<UISplitViewControllerDelegate>を.h Thankyou!に追加します。
fellowworldcitizen 2014年

2
私はまったく同じ問題を抱えているので、これは私にとって正しい答えのようです。しかし、何らかの理由で私splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:が呼び出されることはありません。デリゲートがアプリデリゲートのapplicationDidFinishLaunchingWithOptions:メソッドを適切に設定しているようです。他の誰かがこの問題を見たことがありますか?この解決策は機能しませんでしたか?
Tim Dean

60

これがSwiftで受け入れられた答えです。このサブクラスを作成し、ストーリーボードのsplitViewControllerに割り当てます。

//GlobalSplitViewController.swift

import UIKit

class GlobalSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

  override func viewDidLoad() {
    super.viewDidLoad()

    self.delegate = self
  }

  func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController!, ontoPrimaryViewController primaryViewController: UIViewController!) -> Bool{
    return true
  }

}

3
これはとても役に立ちます。しかし、新たな問題が発生しました。マスターに戻るための戻るボタンが消えます(表示されません)。どうすれば元に戻すことができますか?編集:気にしないで、自分を考え出した:-)。他のユーザーの場合:detailViewでこれを追加します。self.navigationItem.leftBarButtonItem = self.splitViewController .displayModeButtonItem()self.navigationItem.leftItemsSupplementBackButton =本当?
トムTallak Solbu

3
今では何でもSwiftでfunc splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
Dan Rosenstark 2018

2
そのデリゲートメソッドは、クラスサイズがコンパクトな場合にのみ呼び出されるようです。iPhoneでは呼び出されますが、iPadの縦では呼び出されません。つまり、iPadの縦も折りたたみモードであるため、問題は解決しません。iOSの12.1でテスト
ダニエル

21

Mark Sの正解のSwiftバージョン

AppleのMaster-Detailテンプレートによって提供されます。

func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController:UIViewController, ontoPrimaryViewController primaryViewController:UIViewController) -> Bool {
    guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
    guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
    if topAsDetailController.detailItem == nil {
        // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return true
    }
    return false
}

明確化

(マークSの発言は少し混乱した)

このデリゲートメソッドはと呼ばれsplitViewController: collapseSecondaryViewController: ontoPrimaryViewController:ています。よりコンパクトな幅のサイズに変更する場合(たとえば、電話を横向きから縦向きに回転させる場合)、分割ビューコントローラーを折りたたみ、そのうちの1つだけにする必要があります。

この関数は、詳細を縮小してマスターを表示するかどうかを決定するブール値を返します。

したがって、ここでは、詳細が選択されているかどうかに基づいて決定します。詳細が選択されているかどうかをどのようにして知ることができますか?Appleのマスター/詳細テンプレートに従う場合、詳細ビューコントローラーには詳細情報を持つオプションの変数が必要です。そのため、nil(.None)の場合はまだ何も選択されておらず、ユーザーが何かを選択できるようにマスターを表示する必要があります。

それでおしまい。


@sschaleの編集からロールバックした理由を明確にするために。そのコードはからの引用でありApple's Master-Detail template、優れたものや簡潔なものではなく、事実に基づくものです。:)
NiñoScript

10

ドキュメントから、デリゲートを使用して、詳細ビューを「折りたたまれたインターフェース」(つまり、ケースでは「ポートレートモード」)に組み込まUISplitViewController ないように指示する必要があります。Swift 4では、そのために実装するデリゲートメソッドの名前が変更されました。

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

9
   #import <UIKit/UIKit.h>

    @interface SplitProductView : UISplitViewController<UISplitViewControllerDelegate>




    @end

.m:

#import "SplitProductView.h"
#import "PriceDetailTableView.h"

@interface SplitProductView ()

@end

@implementation SplitProductView

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.delegate = self;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/
- (BOOL)splitViewController:(UISplitViewController *)splitViewController
collapseSecondaryViewController:(UIViewController *)secondaryViewController
  ontoPrimaryViewController:(UIViewController *)primaryViewController {

    if ([secondaryViewController isKindOfClass:[UINavigationController class]]
        && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[PriceDetailTableView class]]

        //&& ([(PriceDetailTableView *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)

        ) {

        // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return YES;

    } else {

        return NO;

    }
}
@end

9

私のアプリはSwift 2.xで書かれていて、うまく実行できました。(XCodeコンバーターを使用して)Swift 3.0に変換した後、縦長モードのマスターではなく、最初に詳細を表示し始めます。問題は、splitViewController関数の名前がUISplitViewControllerDelegateの新しい名前と一致するように変更されていないことです。

その関数の名前を手動で変更した後、私のアプリは正しく動作するようになりました:

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
    guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
    guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
    if topAsDetailController.game == nil {
        // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return true
    }
    return false
}

私はあなたと同じ問題を抱えていますが、あなたの解決策がわかりません。ここに投稿したコードに変更はありません。もっと具体的に教えてください。おかげで
bibscy

多くのメソッドはわずかに名前が変更されています。
Dave

Tonyの回答は、@NiñoScriptの回答に対するSwift 3構文です(以前のSwiftバージョン用に記述されています)
Hellojeffy

2
Swift 3の場合はself.delegate = selfviewDidLoadメソッドを置くことを忘れないでください。
2017

7

詳細ビューコントローラーに表示するデフォルト値がない場合は、SplitViewControllerとストーリーボードの詳細UIViewControllerの間のデフォルトセグエを削除するだけで済みます。これにより、常に最初にマスタービューコントローラーに入ります。

これの副作用は、横長の2つのビューを表示する代わりに、マスタービューコントローラで詳細セグエを表示するまで、SplitViewControllerで1つのビューがフルサイズで表示されることです。


良いトリック。私のアプリは縦向きモードでしか実行できません。
Peacemoon 2015

これは、横向きではビューの空の右側部分がグレーで塗りつぶされている可能性があることを除いて、当てはまります。
vedrano

4

cs193pの金曜日のセクションを見つけることができなかったすべての人のために:

Swift 3.1.1では、UISplitViewControllerのサブクラスを作成し、そのデリゲートメソッドの1つを実装すると、魅力的に機能します。

class MainSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

override func viewDidLoad() {
    super.viewDidLoad()
    self.delegate = self
}

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

私のストーリーボード


@olitoが指摘したように、スウィフト4で、このための構文は、に変更されました: public func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool
Robuske

3

私の意見では、この問題をより一般的に解決する必要があります。UISplitViewControllerをサブクラス化して、埋め込まれたView Controllerにプロトコルを実装できます。

class MasterShowingSplitViewController: UISplitViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }
}

extension MasterShowingSplitViewController: UISplitViewControllerDelegate {
    func splitViewController(splitViewController: UISplitViewController,
                             collapseSecondaryViewController secondaryViewController: UIViewController,
                             ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
        guard let masterNavigationController = primaryViewController as? UINavigationController,
                  master = masterNavigationController.topViewController as? SplitViewControllerCollapseProtocol else {
            return true
        }
        return master.shouldShowMasterOnCollapse()

    }
}

protocol SplitViewControllerCollapseProtocol {
    func shouldShowMasterOnCollapse() -> Bool
}

UITableViewControllerの実装例:

extension SettingsTableViewController: SplitViewControllerCollapseProtocol {
    func shouldShowMasterOnCollapse() -> Bool {
        return tableView.indexPathForSelectedRow == nil
    }
}

それが役に立てば幸い。したがって、このクラスを再利用でき、プロトコルを実装するだけで済みます。


デリゲートメソッドが呼び出されることはありません!
K_Mohit 2017

iPadとiPhone 6/7/8 Plusでは呼び出されません。それはあなたの問題ですか?:見ていstackoverflow.com/questions/29767614/...
Maik639

2

マスターから開始する必要がある場合は、SplitViewコントローラーからDetailViewControllerを削除するだけです。

UISplitViewController *splitViewController = (UISplitViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"SETTINGS"];
splitViewController.delegate = self;
[self.navigationController presentViewController:splitViewController animated:YES completion:nil];
if (IPHONE) {
    NSMutableArray * cntlrs = [splitViewController.viewControllers mutableCopy];
    [cntlrs removeLastObject];
    splitViewController.viewControllers = cntlrs;
}

2

これはiOS-11とSwift 4で私にとってはうまくいきました。

//Following code in application didFinishLaunching (inside Application Delegate)
guard let splitViewController = window?.rootViewController as? UISplitViewController,
            let masterNavVC = splitViewController.viewControllers.first as? UINavigationController,
            let masterVC = masterNavVC.topViewController as? MasterViewController
else { fatalError() }
splitViewController.delegate = masterVC

//Following code in MasterViewController class
extension MasterViewController:UISplitViewControllerDelegate {
    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
        return true
    }
}

2

Swiftの新しいバージョンでは関数の名前が変更されているため、このコードはSwift 4で機能します。

import UIKit

class GlobalSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
    }

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

}

0

Xamarin / C#ソリューション

public partial class MainSplitViewController : UISplitViewController
{
    public MainSplitViewController(IntPtr handle) : base(handle)
    {
    }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        Delegate = new MainSplitViewControllerDelegate();
    }
}

public class MainSplitViewControllerDelegate : UISplitViewControllerDelegate
{
    public override bool CollapseSecondViewController(UISplitViewController splitViewController, UIViewController secondaryViewController, UIViewController primaryViewController)
    {
        return true;
    }
}

0

preferredDisplayModeプロパティをUISplitViewControllerに設定するだけです.allVisible

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.