UISplitViewController en retrato en iPhone muestra detalle VC en lugar de maestro

Estoy usando un Universal Storyboard en Xcode 6, dirigido a iOS 7 y superior. He implementado un UISplitViewController que ahora está soportado nativamente en iPhone con iOS 8, y Xcode automáticamente lo backport para iOS 7. Está funcionando muy bien, excepto cuando ejecutas la aplicación en iPhone en retrato con iOS 8, el detalle de la vista dividida El controller de vista se muestra cuando esperaba ver primero el controller de vista maestra. Creí que esto era un error con iOS 8 porque cuando ejecutas la aplicación en iOS 7, muestra correctamente el controller de vista maestra. Pero iOS 8 ahora es GM y esto todavía está ocurriendo. ¿Cómo puedo configurarlo para que cuando el controller de la vista dividida vaya a queuepsarse (solo se visualice un controller de visualización en la pantalla), cuando se muestre el controller de vista dividida, se mostrará el controller de vista maestra, no los detalles?

He creado este controller de vista dividida en Interface Builder. El controller de vista dividida es el primer controller de vista dentro de un controller de barra de tabs. Tanto el maestro como el detalle de los VC son controlleres de navigation con controlleres de vista de tabla embeddeds en el interior.

Oh, hombre, esto me estaba causando un dolor de cabeza por unos días y no podía entender cómo hacer esto. La peor parte fue que crear un nuevo proyecto Xcode iOS con la plantilla master-detail funcionó bien. Afortunadamente, al final, ese pequeño hecho fue cómo encontré la solución.

Hay algunas publicaciones que he encontrado que sugieren que la solución es implementar el nuevo primaryViewControllerForCollapsingSplitViewController: method en UISplitViewControllerDelegate . Lo intenté en vano. Lo que hace Apple en la plantilla master-detail que parece funcionar es implementar lo nuevo (respira profundamente para decir todo esto) splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: delegate method (de nuevo en UISplitViewControllerDelegate ). De acuerdo con los documentos , este método:

Pide al delegado que ajuste el controller de vista principal e incorpore el controller de vista secundaria en la interfaz contraída.

Asegúrese de leer en la parte de discusión de ese método para get detalles más específicos.

La forma en que Apple maneja esto es:

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

Esta implementación básicamente hace lo siguiente:

  1. Si el controller secondaryViewController es lo que esperamos (un UINavigationController ), y muestra lo que esperamos (un DetailViewController Controller), pero no tiene ningún model ( detailItem ), luego " Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded. " Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
  2. De lo contrario, devuelva " NO para permitir que el controller de vista dividida intente e incorpore el contenido del controller de vista secundario en la interfaz contraída"

Los resultados son los siguientes para el iPhone en retrato (ya sea comenzando en retrato o girando a retrato, o más compacto con class de tamaño):

  1. Si tu vista es correcta
    • y tiene un model, muestra el controller de vista de detalle
    • pero no tiene model, muestra el controller de vista maestra
  2. Si su vista no es correcta
    • muestra el controller de vista maestra

Claro como el barro.

Aquí está la respuesta aceptada en Swift. Simplemente cree esta subclass y asígnela a su splitViewController en su 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 } } 

Versión rápida de la respuesta correcta de Mark S

Según lo provisto por la plantilla Master-Detail de 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 } 

Aclaración

(Lo que Mark S dijo fue un poco confuso)

Este método de delegado se llama splitViewController: collapseSecondaryViewController: ontoPrimaryViewController: porque eso es lo que hace. Al cambiar a un tamaño de ancho más compacto (por ejemplo, al girar el teléfono de un paisaje a otro), debe queuepsar el controller de vista dividida en solo uno de ellos.

Esta function devuelve un boolean para decidir si debe queuepsar el Detalle y mostrar el Maestro o no.

Entonces, en nuestro caso, decidiremos según si se seleccionó un detalle o no. ¿Cómo sabemos si se selecciona nuestro detalle? Si seguimos la plantilla Master-Detail de Apple, el controller de vista de detalle debe tener una variable opcional que tenga información de detalle, por lo que si es nula (.None), aún no se ha seleccionado nada y deberíamos mostrar el maestro para que el usuario pueda seleccionar algo.

Eso es.

  #import <UIKit/UIKit.h> @interface SplitProductView : UISplitViewController<UISplitViewControllerDelegate> @end 

.metro:

 #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 

Si no tiene los valores pnetworkingeterminados para mostrar el controller de vista en detalle, simplemente puede eliminar la opción pnetworkingeterminada entre SplitViewController y su UIViewController de detalle en el tablero de historias. Esto hará que siempre entre en Master View Controller primero.

El efecto secundario de esto es que, en lugar de ver dos vistas en el paisaje, verá una vista en tamaño completo en SplitViewController hasta que se muestre Mostrar detalle en el controller de vista maestra.

Mi aplicación se escribió en Swift 2.x y podría funcionar bien. Después de convertirlo en Swift 3.0 (usando el convertidor XCode), primero comienza a mostrar detalles en lugar de maestro en modo vertical. El problema es el nombre de la function splitViewController no se cambia para que coincida con el nuevo de UISplitViewControllerDelegate.

Después de cambiar el nombre de esa function de forma manual, mi aplicación ahora puede funcionar correctamente:

 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 } 

En la documentation , debe usar un delegado para decirle al UISplitViewController no incorpore la vista de detalles en la "interfaz contraída" (es decir, el "Modo retrato" en su caso). En Swift 4, el método delegado para implementar para eso se ha cambiado el nombre:

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

En mi opinión, debería resolver este problema más genérico. Puede subclasificar UISplitViewController e implementar un protocolo en los controlleres de vista embeddeds.

 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 } 

Implementación de ejemplo en UITableViewController:

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

Espero eso ayude. Entonces puedes reutilizar esta class y solo necesitas implementar un protocolo.

Para todas las personas que no pudieron encontrar la sección viernes de cs193p:

En Swift 3.1.1 crear una subclass de UISplitViewController e implementar uno de sus methods delegates me funcionó como un encanto:

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

Mi storyboard

Simplemente quite DetailViewController de los controlleres SplitView cuando lo necesite para comenzar desde 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; }