UISplitViewController im Hochformat auf dem iPhone zeigt Detail-VC anstelle von Master

177

Ich verwende ein Universal Storyboard in Xcode 6 für iOS 7 und höher. Ich habe eine implementiert, UISplitViewControllerdie jetzt nativ auf dem iPhone mit iOS 8 unterstützt wird, und Xcode portiert sie automatisch für iOS 7. Sie funktioniert sehr gut, es sei denn, Sie starten die App auf dem iPhone im Hochformat mit iOS 8, der Detailansicht der geteilten Ansicht Der Controller wird angezeigt, wenn ich erwartet habe, dass der Master View Controller zum ersten Mal angezeigt wird. Ich habe geglaubt, dass dies ein Fehler mit iOS 8 ist, da beim Ausführen der App unter iOS 7 der Master View Controller korrekt angezeigt wird. Aber iOS 8 ist jetzt GM und dies tritt immer noch auf. Wie kann ich es so einrichten, dass beim Reduzieren des Split-View-Controllers (nur ein View-Controller wird auf dem Bildschirm angezeigt) beim Anzeigen des Split-View-Controllers dem Master-View-Controller nicht die Details angezeigt werden?

Ich habe diesen Split-View-Controller in Interface Builder erstellt. Der Split-View-Controller ist der erste View-Controller innerhalb eines Registerkarten-Controllers. Sowohl die Master- als auch die Detail-VCs sind Navigationscontroller mit darin eingebetteten Table View-Controllern.

Jordan H.
quelle

Antworten:

238

Oh Mann, das bereitete mir ein paar Tage lang Kopfschmerzen und konnte nicht herausfinden, wie ich das machen sollte. Das Schlimmste war, dass das Erstellen eines neuen Xcode iOS-Projekts mit der Master-Detail-Vorlage einwandfrei funktionierte. Glücklicherweise war diese kleine Tatsache am Ende, wie ich die Lösung fand.

Es gibt einige Beiträge, die darauf hindeuten, dass die Lösung darin besteht, die neue primaryViewControllerForCollapsingSplitViewController:Methode zu implementieren UISplitViewControllerDelegate. Ich habe das ohne Erfolg versucht. Was Apple in der Master-Detail-Vorlage tut, die zu funktionieren scheint, ist die Implementierung der neuen splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:Delegat-Methode ( atmen Sie tief ein, um all dies zu sagen) (wieder aktiviert UISplitViewControllerDelegate). Laut den Dokumenten ist diese Methode:

Fordert den Delegierten auf, den primären Ansichtscontroller anzupassen und den sekundären Ansichtscontroller in die reduzierte Schnittstelle zu integrieren.

Lesen Sie den Diskussionsteil dieser Methode, um genauere Informationen zu erhalten.

Apple geht folgendermaßen damit um:

- (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;

    }
}

Diese Implementierung führt im Wesentlichen Folgendes aus:

  1. Wenn secondaryViewControlleres das ist, was wir erwarten (a UINavigationController) und es zeigt, was wir erwarten (a DetailViewController- Ihr View Controller), aber kein Modell hat ( detailItem), dann " Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded."
  2. Andernfalls kehren Sie zurück, " NOdamit der Controller für geteilte Ansichten versuchen kann, den Inhalt des Controllers für sekundäre Ansichten in die reduzierte Oberfläche zu integrieren".

Die Ergebnisse sind für das iPhone im Hochformat wie folgt (entweder im Hochformat oder im Hochdrehen - oder genauer gesagt in der kompakten Größenklasse):

  1. Wenn Ihre Ansicht korrekt ist
    • und hat ein Modell, zeigen Sie die Detailansicht Controller
    • Hat aber kein Modell, zeigen Sie den Master View Controller
  2. Wenn Ihre Ansicht nicht korrekt ist
    • Zeigen Sie den Master View Controller an

Klar wie Schlamm.

Mark S.
quelle
8
Fantastische Antwort! Ich habe einfach eine Unterklasse erstellt UISplitViewControllerund kehre immer YESvon dieser Methode zurück. Dann habe ich die Klasse für geteilte Ansichten in Storyboard geändert, da ich den Master immer im Hochformat auf dem iPhone anzeigen möchte. :)
Jordan H
2
Ich möchte, dass mein Master View Controller ausgeblendet wird, wenn sich das "iPhone" im "Portrait" -Modus befindet, da ich ein Standard-Setup für den Detail View Controller habe. Wie kann ich das machen. Mein Meister und Detail sind beide vom Typ VC. Insbesondere ist mein Detail MMDrawerController. Bitte helfen Sie
Harshit Gupta
3
Ich habe Joeys Vorschlag einer Unterklasse ausprobiert, UISplitViewControlleraber festgestellt, dass das nicht funktioniert hat: Es splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:wurde nie aufgerufen. Stattdessen habe ich Apples Vorlage kopiert und in das AppDelagate eingefügt. Dies erforderte einige Änderungen beim Erstellen des UISplitViewController unter application didFinishLaunchingWithOptions:(wo ich auch Apples Vorlage kopiert habe).
Nick
7
@ joeys Kommentar funktioniert mit dem Setzen von self.delegate = self; in der viewdidload! Und Hinzufügen von <UISplitViewControllerDelegate> in der .h Danke!
Mitweltbürger
2
Dies scheint die richtige Antwort für mich zu sein, da ich genau das gleiche Problem habe. Aus irgendeinem Grund wird mein jedoch splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:nie angerufen. Es scheint, dass der Delegat die applicationDidFinishLaunchingWithOptions:Methode meines App-Delegaten richtig festlegt . Hat jemand anderes dieses Problem gesehen und diese Lösung NICHT funktioniert?
Tim Dean
60

Hier ist die akzeptierte Antwort in Swift. Erstellen Sie einfach diese Unterklasse und weisen Sie sie Ihrem splitViewController in Ihrem Storyboard zu.

//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
  }

}
Clifton Labrum
quelle
3
Großartig, das hilft sehr. Aber es entstand ein neues Problem. Der Backbutton, der mich zum Master bringt, verschwindet jetzt (wird nie angezeigt). Wie bekomme ich es zurück? EDIT: Egal, dachte ich mir :-). Für andere Benutzer: Fügen Sie dies in der Detailansicht hinzu: self.navigationItem.leftBarButtonItem = self.splitViewController? .DisplayModeButtonItem () self.navigationItem.leftItemsSupplementBackButton = true
Tom Tallak Solbu
3
Jetzt in Swift, was auch immer es istfunc splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
Dan Rosenstark
2
Es scheint, dass die Delegatmethode nur aufgerufen wird, wenn die Klassengröße kompakt ist. Es wird auf dem iPhone aufgerufen, aber nicht auf dem iPad-Porträt, was bedeutet, dass es das Problem nicht löst, da sich das iPad-Porträt auch im reduzierten Modus befindet. Getestet mit iOS 12.1
Daniel
21

Schnelle Version der richtigen Antwort von Mark S.

Wie in der Master-Detail-Vorlage von Apple bereitgestellt.

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
}

Klärung

(Was Mark S sagte, war etwas verwirrend)

Diese Delegatenmethode wird aufgerufen splitViewController: collapseSecondaryViewController: ontoPrimaryViewController: , weil sie genau das tut. Wenn Sie zu einer kompakteren Breitengröße wechseln (z. B. beim Drehen des Telefons von Querformat zu Hochformat), muss der geteilte Ansichts-Controller in nur eine davon reduziert werden.

Diese Funktion gibt einen Booleschen Wert zurück, um zu entscheiden, ob das Detail reduziert und der Master angezeigt werden soll oder nicht.

In unserem Fall entscheiden wir also basierend darauf, ob ein Detail ausgewählt wurde oder nicht. Woher wissen wir, ob unser Detail ausgewählt ist? Wenn wir der Master-Detail-Vorlage von Apple folgen, sollte der Detailansichts-Controller eine optionale Variable mit den Detailinformationen haben. Wenn sie also Null (.None) ist, ist noch nichts ausgewählt, und wir sollten den Master anzeigen, damit der Benutzer etwas auswählen kann.

Das ist es.

NiñoScript
quelle
Nur um zu verdeutlichen, warum ich von @ sschales Bearbeitung zurückgesetzt habe. Dieser Code ist ein Zitat aus Apple's Master-Detail template, er soll nicht großartig oder prägnant sein, sondern nur sachlich. :)
NiñoScript
10

Aus der Dokumentation benötigen Sie einen Delegaten verwenden , um das zu sagen , UISplitViewController nicht die Detailansicht in den „kollabierte Schnittstelle“ (dh des „Portrait - Modus“ in diesem Fall) zu integrieren. In Swift 4 wurde die dafür zu implementierende Delegate-Methode umbenannt:

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

Meine App wurde in Swift 2.x geschrieben und konnte gut laufen. Nach der Konvertierung in Swift 3.0 (mithilfe des XCode-Konverters) werden zuerst Details angezeigt, anstatt Master im Hochformat. Das Problem ist, dass der Name der Funktion splitViewController nicht geändert wird, um mit der neuen Funktion von UISplitViewControllerDelegate übereinzustimmen.

Nachdem der Name dieser Funktion manuell geändert wurde, kann meine App jetzt ordnungsgemäß funktionieren:

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
}
Tony
quelle
Ich habe das gleiche Problem wie Sie, aber ich verstehe Ihre Lösung nicht. Ich sehe keine Änderung in dem Code, den Sie hier gepostet haben. Könnten Sie genauer sein. Danke
bibscy
Viele Methoden werden leicht umbenannt.
Dave
Tonys Antwort ist die Swift 3-Syntax für die Antwort von @ NiñoScript (die für frühere Swift-Versionen geschrieben wurde)
Hellojeffy,
2
für eine schnelle 3, vergessen Sie nicht , setzen self.delegate = selfauf viewDidLoadVerfahren.
Fer
8
   #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
Gank
quelle
7

Wenn Sie keine Standardwerte haben, die im Detailansicht-Controller angezeigt werden sollen, können Sie einfach den Standard-Übergang zwischen dem SplitViewController und Ihrem Detail-UIViewController im Storyboard löschen. Dadurch wird es immer zuerst in den Master View Controller verschoben.

Der Nebeneffekt davon ist, dass Sie in SplitViewController nicht zwei Ansichten im Querformat, sondern eine Ansicht in voller Größe sehen, bis Show Detail Segue im Master View Controller ausgelöst wird.

Hao-Cher Hong
quelle
Guter Trick. Meine App befindet sich nur im Hochformat und ich kann dies tun.
Peacemoon
Dies gilt nur, wenn Sie im Querformat einen leeren rechten Teil der Ansicht sehen, der möglicherweise grau gefüllt ist.
Vedrano
4

Für alle Leute, die den Freitag-Bereich von cs193p nicht finden konnten:

In Swift 3.1.1 hat das Erstellen einer Unterklasse von UISplitViewController und das Implementieren einer seiner Delegate-Methoden für mich wie ein Zauber funktioniert:

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
} }

Mein Storyboard

Bartosz Kunat
quelle
Wie @olito betont hat, hat sich in Swift 4 die Syntax dafür geändert zu: public func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool
Robuske
3

Meiner Meinung nach sollten Sie dieses Problem allgemeiner lösen. Sie können den UISplitViewController in Unterklassen unterteilen und ein Protokoll in den eingebetteten Ansichtscontrollern implementieren.

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
}

Beispielimplementierung in UITableViewController:

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

Ich hoffe es hilft. Sie können diese Klasse also wiederverwenden und müssen lediglich ein Protokoll implementieren.

Maik639
quelle
Die Delegate-Methode wird nie aufgerufen!
K_Mohit
Es wird nicht auf dem iPad und iPhone 6/7/8 Plus aufgerufen. Ist das dein Problem? Werfen
Maik639
2

Entfernen Sie einfach DetailViewController von SplitView-Controllern, wenn Sie es benötigen, um vom Master aus zu starten.

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;
}
Borys Shcherbyna
quelle
2

Dies funktionierte für mich unter iOS-11 und 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
    }
}
Vishal Chaudhry
quelle
2

Die Funktion wird in neuen Versionen von Swift umbenannt, sodass dieser Code unter Swift 4 funktioniert:

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
    }

}
Saeed Ir
quelle
0

Xamarin / C # -Lösung

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;
    }
}
Mark Moeykens
quelle
0

Setzen Sie einfach die preferredDisplayModeEigenschaft von UISplitViewControllerauf.allVisible

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
Arash Etemad
quelle