UISplitViewController in verticale su iPhone mostra dettagli VC anziché master


177

Sto usando uno storyboard universale in Xcode 6, destinato a iOS 7 e versioni successive. Ho implementato un UISplitViewControllersupporto ora supportato nativamente su iPhone con iOS 8 e Xcode eseguirà automaticamente il backport per iOS 7. Funziona davvero bene, tranne quando avvii l'app su iPhone in verticale con iOS 8, la vista dettagliata della vista divisa il controller viene visualizzato quando mi aspettavo di vedere il controller della vista principale. Credevo che si trattasse di un bug con iOS 8 perché quando esegui l'app su iOS 7, mostra correttamente il controller della vista principale. Ma iOS 8 ora è GM e questo si sta ancora verificando. Come posso impostarlo in modo tale che quando il controller della vista divisa sta per essere compresso (solo un controller della vista visualizzato sullo schermo), quando il controller della vista divisa viene visualizzato, mostra il controller della vista principale non i dettagli?

Ho creato questo controller con vista divisa in Interface Builder. Il controller della vista divisa è il primo controller della vista all'interno di un controller della barra delle schede. Sia i VC master che i dettagli sono controller di navigazione con controller di visualizzazione tabella incorporati all'interno.

Risposte:


238

Oh amico, questo mi ha causato mal di testa per alcuni giorni e non sono riuscito a capire come farlo. La parte peggiore è stata che la creazione di un nuovo progetto iOS Xcode con il modello di dettaglio principale funzionava perfettamente. Fortunatamente, alla fine, quel piccolo fatto è stato come ho trovato la soluzione.

Ci sono alcuni post che ho trovato che suggeriscono che la soluzione è implementare il nuovo primaryViewControllerForCollapsingSplitViewController:metodo UISplitViewControllerDelegate. L'ho provato inutilmente. Quello che Apple fa nel modello master-dettaglio che sembra funzionare è implementare il nuovo splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:metodo delegato (fai un respiro profondo per dire tutto questo) (di nuovo UISplitViewControllerDelegate). Secondo i documenti , questo metodo:

Chiede al delegato di regolare il controller della vista primaria e di incorporare il controller della vista secondaria nell'interfaccia compressa.

Assicurati di leggere sulla parte di discussione di quel metodo per dettagli più specifici.

Il modo in cui Apple gestisce questo è:

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

    }
}

Questa implementazione sostanzialmente fa quanto segue:

  1. Se secondaryViewControllerè quello che ci stiamo aspettando (a UINavigationController), e sta mostrando ciò che ci aspettiamo (a DetailViewController- il tuo controller di visualizzazione), ma non ha un modello ( detailItem), quindi " Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded."
  2. Altrimenti, torna " NOper consentire al controller della vista divisa di provare e incorporare il contenuto del controller della vista secondaria nell'interfaccia compressa"

I risultati sono i seguenti per l'iPhone in verticale (a partire da verticale o ruotando in verticale - o in una classe di dimensioni più precisamente compatta):

  1. Se la tua vista è corretta
    • e ha un modello, mostra il controller di visualizzazione dettagli
    • ma non ha un modello, mostra il controller della vista principale
  2. Se la tua vista non è corretta
    • mostra il controller della vista principale

Chiaro come il fango.


8
Risposta fantastica! Ho semplicemente eseguito una sottoclasse UISplitViewControllere sono sempre tornato YESda quel metodo, quindi ho semplicemente cambiato la classe di visualizzazione divisa in Storyboard, poiché voglio sempre mostrare il master su iPhone in verticale. :)
Jordan H,

2
Voglio che il mio controller di visualizzazione principale sia nascosto se "iPhone" è in modalità "Ritratto" perché ho una configurazione predefinita del controller di visualizzazione dettagli. Come lo posso fare. Il mio padrone e il dettaglio sono entrambi del tipo VC. In particolare il mio dettaglio è MMDrawerController. Per favore
aiutatemi

3
Ho provato il suggerimento di Joey di sottoclasse UISplitViewControllerma ho scoperto che non ha funzionato: splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:non è mai stato chiamato. Invece ho copiato il modello di Apple e l'ho inserito in AppDelagate. Ciò ha reso necessarie alcune modifiche alla creazione di UISplitViewController application didFinishLaunchingWithOptions:(dove ho anche copiato il modello di Apple).
Nick,

7
Il commento di @ joey funziona con l'impostazione self.delegate = self; nel viewdidload! E aggiungendo <UISplitViewControllerDelegate> nel .h Grazie!
fellowworldcitizen,

2
Questa sembra la risposta giusta per me, poiché sto avendo lo stesso identico problema. Tuttavia, per qualche motivo il mio splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:non viene mai chiamato. Sembra che il delegato stia impostando correttamente il applicationDidFinishLaunchingWithOptions:metodo del delegato della mia app . Qualcun altro ha visto questo problema e NON ha funzionato questa soluzione?
Tim Dean,

60

Ecco la risposta accettata in Swift. Basta creare questa sottoclasse e assegnarla a splitViewController nello storyboard.

//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
Ottimo, questo aiuta molto. Ma è sorto un nuovo problema. Il pulsante indietro che mi porta al master ora scompare (non mostra mai). Come posso riaverlo? EDIT: Non importa, ho pensato :-). Per altri utenti: aggiungilo nel dettaglioView: self.navigationItem.leftBarButtonItem = self.splitViewController? .DisplayModeButtonItem () self.navigationItem.leftItemsSupplementBackButton = true
Tom Tallak Solbu

3
Ora in Swift qualunque cosa siafunc splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
Dan Rosenstark il

2
Sembra che quel metodo delegato venga chiamato solo quando la dimensione della classe è compatta. Viene chiamato su iPhone, ma non su iPad verticale, il che significa che non risolve il problema, poiché iPad verticale è anche in modalità compressa. Testato con iOS 12.1
Daniel

21

Versione rapida della risposta corretta di Mark S

Come fornito dal modello Master-Detail di Apple.

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
}

Una precisazione

(Ciò che Mark S ha detto è stato leggermente confuso)

Questo metodo delegato viene chiamato splitViewController: collapseSecondaryViewController: ontoPrimaryViewController:, perché è quello che fa. Quando si passa a una larghezza più compatta (ad esempio quando si ruota il telefono da orizzontale a verticale), è necessario comprimere il controller della vista divisa in uno solo di essi.

Questa funzione restituisce un valore booleano per decidere se comprimere il dettaglio e mostrare il master o meno.

Quindi nel nostro caso, decideremo se ci fosse un dettaglio selezionato o meno. Come facciamo a sapere se i nostri dettagli sono selezionati? Se seguiamo il modello Master-Detail di Apple, il controller di visualizzazione dettagli dovrebbe avere una variabile opzionale con le informazioni di dettaglio, quindi se è zero (.Nessuno), non c'è ancora nulla di selezionato e dovremmo mostrare il Master in modo che l'utente possa selezionare qualcosa.

Questo è tutto.


Giusto per chiarire perché sono tornato indietro dalla modifica di @sschale. Quel codice è una citazione da Apple's Master-Detail template, non è inteso per essere grande o conciso, ma solo reale. :)
NiñoScript,

10

Dalla documentazione , è necessario utilizzare un delegato per indicare di UISplitViewController non incorporare la vista dei dettagli nell'interfaccia "compressa" (ovvero la "Modalità ritratto" nel proprio caso). In Swift 4, il metodo delegato da implementare è stato rinominato:

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

La mia app è stata scritta in Swift 2.x e potrebbe funzionare bene. Dopo averlo convertito in Swift 3.0 (usando il convertitore XCode), inizia a mostrare prima i dettagli anziché i master in modalità verticale. Il problema è che il nome della funzione splitViewController non viene modificato in modo che corrisponda a quello nuovo di UISplitViewControllerDelegate.

Dopo aver modificato manualmente il nome di quella funzione, la mia app ora può funzionare correttamente:

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
}

Sto riscontrando lo stesso problema, ma non capisco la tua soluzione. Non vedo alcun cambiamento nel codice che hai pubblicato qui. Potresti essere più specifico. Grazie
bibscy il

Molti metodi sono leggermente rinominati.
Dave,

La risposta di Tony è la sintassi di Swift 3 alla risposta di @ NiñoScript (che è scritta per le precedenti versioni di Swift)
Hellojeffy,

2
per swift 3, non dimenticare di applicare self.delegate = selfil viewDidLoadmetodo.
Fer

7

Se non hai valori predefiniti da mostrare nel controller della vista di dettaglio, puoi semplicemente eliminare i seguenti valori predefiniti tra SplitViewController e UIViewController di dettaglio nella storyboard. Ciò renderà sempre prima il Master View Controller.

L'effetto collaterale di questo è che invece di vedere due viste in orizzontale, vedrai una vista a schermo intero in SplitViewController fino a quando non viene attivato Show Segue Detail nel controller di vista master.


buon trucco. La mia app è solo in modalità verticale e posso farlo.
Peacemoon,

Questo è vero tranne che nell'orientamento orizzontale vedrai la parte destra vuota della vista possibilmente grigia riempita.
vedrano,

4

Per tutte le persone che non sono riuscite a trovare la sezione del venerdì di cs193p:

In Swift 3.1.1 creare una sottoclasse di UISplitViewController e implementare uno dei suoi metodi delegati ha funzionato per me come un incantesimo:

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

Il mio storyboard


Come ha sottolineato @olito, in Swift 4 la sintassi è cambiata in: public func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool
Robuske

3

Secondo me dovresti risolvere questo problema in modo più generico. È possibile eseguire la sottoclasse di UISplitViewController e implementare un protocollo nei controller di visualizzazione incorporati.

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
}

Esempio di implementazione in UITableViewController:

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

Spero che sia d'aiuto. Quindi puoi riutilizzare questa classe e devi solo implementare un protocollo.


Il metodo delegato non viene mai chiamato!
K_Mohit,

non viene chiamato su iPad e iPhone 6/7/8 Plus. È questo il tuo problema? Date un'occhiata a: stackoverflow.com/questions/29767614/...
Maik639

2

Rimuovi DetailViewController dai controller SplitView quando ti serve per avviarlo da Master.

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

Questo ha funzionato per me su iOS-11 e 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

La funzione è stata rinominata nelle nuove versioni di Swift, quindi questo codice funziona su 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

Soluzione 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

Basta impostare la preferredDisplayModeproprietà di UISplitViewControllera.allVisible

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.