UISplitViewController en vertical en iPhone muestra detalles de VC en lugar de master


177

Estoy usando un Storyboard universal en Xcode 6, dirigido a iOS 7 y superior. He implementado uno UISplitViewControllerque ahora es compatible de forma nativa en iPhone con iOS 8, y Xcode lo respaldará automáticamente para iOS 7. Funciona realmente bien, excepto cuando inicia la aplicación en iPhone en modo vertical con iOS 8, la vista detallada de la vista dividida el controlador se muestra cuando esperaba ver por primera vez el controlador de vista maestra. Creí que esto era un error con iOS 8 porque cuando ejecutas la aplicación en iOS 7, muestra correctamente el controlador de vista maestro. Pero iOS 8 ahora es GM y esto todavía está ocurriendo. ¿Cómo puedo configurarlo de modo que cuando el controlador de vista dividida se va a colapsar (solo se muestra un controlador de vista en la pantalla), cuando se muestra el controlador de vista dividida, muestra el controlador de vista maestro, no el detalle?

He creado este controlador de vista dividida en Interface Builder. El controlador de vista dividida es el primer controlador de vista dentro de un controlador de barra de pestañas. Tanto el VC maestro como el de detalle son controladores de navegación con controladores de vista de tabla integrados dentro.

Respuestas:


238

Oh hombre, esto me estaba causando dolor de cabeza durante unos días y no podía encontrar la manera de hacerlo. 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:método UISplitViewControllerDelegate. Lo intenté en vano. Lo que Apple hace en la plantilla de detalles maestros que parece funcionar es implementar el nuevo splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:método delegado ( respire profundamente para decir todo esto) (nuevamente encendido UISplitViewControllerDelegate). Según los documentos , este método:

Pide al delegado que ajuste el controlador de vista primario e incorpore el controlador de vista secundario en la interfaz contraída.

Asegúrese de leer la parte de discusión de ese método para obtener 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 secondaryViewControlleres lo que esperamos (a UINavigationController), y muestra lo que esperamos (a DetailViewController- su controlador de vista), pero no tiene modelo ( detailItem), entonces " Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded."
  2. De lo contrario, regrese " NOpara permitir que el controlador de vista dividida intente incorporar el contenido del controlador de vista secundario en la interfaz contraída"

Los resultados son los siguientes para el iPhone en vertical (ya sea comenzando en vertical o girando a vertical, o una clase de tamaño compacto más precisa):

  1. Si tu opinión es correcta
    • y tiene un modelo, muestra el controlador de vista detallada
    • pero no tiene modelo, muestra el controlador de vista maestro
  2. Si su punto de vista no es correcto
    • mostrar el controlador de vista maestro

Claro como el barro.


8
Fantástica respuesta! Simplemente subclasifico UISplitViewControllery siempre regreso YESde ese método, luego solo cambié la clase de vista dividida en Storyboard, ya que siempre quiero mostrar el maestro en iPhone en vertical. :)
Jordan H

2
Quiero que mi controlador de vista maestro esté oculto si el "iPhone" está en modo "Vertical" porque tengo una configuración predeterminada del controlador de vista detallada. Cómo puedo hacer eso. Mi maestro y detalle son del tipo VC. Específicamente mi detalle es MMDrawerController. Por favor ayuda
Harshit Gupta

3
Intenté la sugerencia de Joey de subclasificar, UISplitViewControllerpero descubrí que eso no funcionaba: splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:nunca se llamaba. En cambio, copié la plantilla de Apple y la puse en AppDelagate. Esto también requirió algunos cambios para crear el UISplitViewController application didFinishLaunchingWithOptions:(donde también copié la plantilla de Apple).
Nick

77
El comentario de @ joey funciona con la configuración self.delegate = self; en el viewdidload! Y agregando <UISplitViewControllerDelegate> en .h ¡Gracias!
fellowworldcitizen

2
Esta parece ser la respuesta correcta para mí, ya que tengo exactamente el mismo problema. Sin embargo, por alguna razón, splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:nunca me llaman. Parece que el delegado está configurando correctamente el applicationDidFinishLaunchingWithOptions:método de delegado de mi aplicación . ¿Alguien más ha visto este problema y NO ha funcionado esta solución?
Tim Dean el

60

Aquí está la respuesta aceptada en Swift. Simplemente cree esta subclase y asígnela a su splitViewController en su guión gráfico.

//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
Genial, esto ayuda mucho. Pero surgió un nuevo problema. El botón de retroceso que me lleva al maestro ahora desaparece (nunca se muestra). ¿Cómo lo recupero? EDITAR: No importa, me di cuenta :-). Para otros usuarios: agregue esto en detailView: self.navigationItem.leftBarButtonItem = self.splitViewController? .DisplayModeButtonItem () self.navigationItem.leftItemsSupplementBackButton = true
Tom Tallak Solbu

3
Ahora en Swift, sea lo que seafunc splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
Dan Rosenstark

2
Parece que ese método delegado solo se llama cuando el tamaño de la clase es compacto. Se llama en iPhone, pero no en iPad vertical, lo que significa que no resuelve el problema, ya que iPad vertical también está en modo colapsado. Probado con iOS 12.1
Daniel

21

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 dijo Mark S 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 horizontal a vertical), debe contraer el controlador de vista dividida en solo uno de ellos.

Esta función devuelve un valor booleano para decidir si debe contraer el detalle y mostrar el maestro o no.

Entonces, en nuestro caso, decidiremos en función de si se seleccionó un detalle o no. ¿Cómo sabemos si se seleccionan nuestros detalles? Si seguimos la plantilla Master-Detail de Apple, el controlador de vista detallada debería tener una variable opcional que tenga la información detallada, por lo que si es nula (.Ninguna), todavía no hay nada seleccionado y deberíamos mostrar el Master para que el usuario pueda seleccionar algo.

Eso es.


Solo para aclarar por qué retrocedí de la edición de @ sschale. Ese código es una cita de Apple's Master-Detail template, no pretende ser grandioso o conciso, solo fáctico. :)
NiñoScript

10

De la documentación , debe usar un delegado para decirle alUISplitViewController no incorpore la vista detallada en la "interfaz contraída" (es decir, el "modo vertical" en su caso). En Swift 4, se ha cambiado el nombre del método delegado para implementar:

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

9
   #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

9

Mi aplicación fue escrita en Swift 2.xy podría funcionar bien. Después de convertirlo a Swift 3.0 (usando el convertidor XCode), comienza a mostrar detalles primero en lugar de master en modo vertical. El problema es que el nombre de la función splitViewController no se cambia para que coincida con la nueva de UISplitViewControllerDelegate.

Después de cambiar el nombre de esa función manualmente, 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
}

Tengo el mismo problema que tú, pero no entiendo tu solución. No veo ningún cambio en el código que ha publicado aquí. Podrías ser más específico. Gracias
bibscy

Muchos métodos se renombran ligeramente.
Dave

La respuesta de Tony es la sintaxis de Swift 3 a la respuesta de @ NiñoScript (que está escrita para versiones anteriores de Swift)
Hellojeffy

2
para Swift 3, no te olvides de ponerte self.delegate = selfel viewDidLoadmétodo.
Fer

7

Si no tiene valores predeterminados para mostrar en el controlador de vista detallada, simplemente puede eliminar el segue predeterminado entre el SplitViewController y su UIViewController detallado en el guión gráfico. Esto hará que siempre entre primero en Master View Controller.

El efecto secundario de esto es que, en lugar de ver dos vistas en horizontal, verá una vista en tamaño completo en SplitViewController hasta que se active Mostrar detalle Segue en el controlador de vista maestro.


buen truco. Mi aplicación solo está en modo vertical y puedo hacer esto.
Peacemoon

Esto es cierto, excepto en la orientación horizontal, verá la parte derecha vacía de la vista posiblemente llena de gris.
vedrano

4

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

En Swift 3.1.1, crear una subclase de UISplitViewController e implementar uno de sus métodos delegados me funcionó de maravilla:

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


Como @olito ha señalado, en Swift 4 la sintaxis para esto ha cambiado a: public func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool
Robuske

3

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

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 puede reutilizar esta clase y solo necesita implementar un protocolo.


¡Nunca se llama al método delegado!
K_Mohit

no se llama en iPad y iPhone 6/7/8 Plus. ¿Ese es tu problema? Eche un vistazo a: stackoverflow.com/questions/29767614/…
Maik639

2

Simplemente quite DetailViewController de los controladores 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;
}

2

Esto funcionó para mí en iOS-11 y 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 función cambia de nombre en las nuevas versiones de Swift, por lo que este código funciona en 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

Solución 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

Simplemente establezca la preferredDisplayModepropiedad de UISplitViewControllera.allVisible

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.