sizes human guidelines español developer application apple app iphone ipad user-interface uisplitviewcontroller appstore-approval

iphone - human - ¿La mejor forma de cambiar entre UISplitViewController y otros controladores de vista?



submit app to app store (9)

Estoy creando una aplicación para iPad. Una de las pantallas de la aplicación es perfectamente adecuada para usar un UISplitViewController. Sin embargo, el nivel superior de la aplicación es un menú principal, para el cual no quiero usar UISplitViewController. Esto presenta un problema, porque Apple dice que:

  1. UISplitViewController debe ser el controlador de vista de nivel superior en la aplicación, es decir, su vista debe agregarse como la subvista de UIWindow

  2. si se utiliza, UISplitViewController debe estar allí durante toda la vida de la aplicación, es decir, no eliminar su vista de UIWindow y poner otra en su lugar, o viceversa

Después de haber leído y experimentado, parece que la única opción viable es satisfacer los requisitos de Apple y la nuestra es usar diálogos modales. Entonces, nuestra aplicación tiene un UISplitViewController en el nivel raíz (es decir, su vista se agrega como la subvista de UIWindow), y para mostrar nuestro menú principal, lo presionamos como un cuadro de diálogo modal de pantalla completa en el UISplitViewController. Luego, descartando el diálogo modal del controlador de vista de menú principal, podemos mostrar nuestra vista dividida.

Esta estrategia parece funcionar bien. Pero plantea las preguntas:

1) ¿Hay alguna forma mejor de estructurar esto, sin modales, que también cumpla con todos los requisitos mencionados? Parece un poco extraño tener la interfaz de usuario principal aparece en virtud de ser empujado como un diálogo modal. (Los modos se supone que son para tareas de usuario enfocadas).

2) ¿Estoy en riesgo de rechazo de la tienda de aplicaciones debido a mi enfoque? Esta estrategia modal es probablemente ''mal uso'' de diálogos modales, según las pautas de interfaz humana de Apple. Pero, ¿qué otra opción me han dado? ¿Sabrían ellos que estoy haciendo esto, de todos modos?


¿Y quién dijo que solo puedes tener una ventana? :)

Vea si mi respuesta sobre esta pregunta similar puede ayudar.

Este enfoque está funcionando muy bien para mí. Siempre que no tenga que preocuparse por las pantallas múltiples o la restauración del estado, este código vinculado debería ser suficiente para hacer lo que necesita: no tiene que hacer que su lógica mire hacia atrás ni reescribir el código existente, y aún así puede aprovecharlo de UISplitView en un nivel más profundo dentro de su aplicación, sin (AFAIK) rompiendo las pautas de Apple.


Acabo de toparme con este problema en un proyecto y pensé que compartiría mi solución. En nuestro caso (para iPad), queríamos comenzar con un UISplitViewController con ambos controladores de vista visibles (usando preferredDisplayMode = .allVisible ). En algún punto de la jerarquía de detalles (derecha) (también teníamos un controlador de navegación para este lado), queríamos empujar un nuevo controlador de vista sobre todo el controlador de vista dividida (no usar una transición modal).

En iPhone, este comportamiento es gratis, ya que solo un controlador de vista está visible en cualquier momento. Pero en el iPad tuvimos que pensar en algo más. Terminamos yendo con un controlador de vista de contenedor raíz que agrega el controlador de vista dividida como un controlador de vista secundaria. Este controlador de vista raíz está incrustado en un controlador de navegación. Cuando el controlador de vista detallada en el controlador de vista dividida quiere empujar un nuevo controlador sobre todo el controlador de vista dividida, el controlador de vista raíz empuja este nuevo controlador de vista con su controlador de navegación.


Agregando a la respuesta de @tadija estoy en una situación similar:

Mi aplicación era solo para teléfonos, y estoy agregando una tableta UI. Decidí hacerlo en Swift en la misma aplicación y eventualmente migrar toda la aplicación para usar el mismo guión gráfico (cuando siento que la versión de iPad es estable, usarla para teléfonos debería ser trivial con las nuevas clases de XCode6).

Aún no se definieron segues en mi escena y todavía funciona.

Mi código en el delegado de mi aplicación está en ObjectiveC, y es ligeramente diferente, pero usa la misma idea. Tenga en cuenta que estoy usando el controlador de vista predeterminado de la escena, a diferencia de los ejemplos anteriores. Creo que esto también funcionará en IOS7 / IPhone en el que el tiempo de ejecución generará un UINavigationController regular en lugar de un UISplitViewController . Incluso podría agregar un nuevo código que presione el controlador de vista de inicio de sesión en IPhones, en lugar de cambiar el rootVC.

- (void) setupRootViewController:(BOOL) animated { UIViewController *newController = nil; UIStoryboard *board = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil]; UIViewAnimationOptions transition = UIViewAnimationOptionTransitionCrossDissolve; if (!loggedIn) { newController = [board instantiateViewControllerWithIdentifier:@"LoginViewController"]; } else { newController = [board instantiateInitialViewController]; } if (animated) { [UIView transitionWithView: self.window duration:0.5 options:transition animations:^{ self.window.rootViewController = newController; NSLog(@"setup root view controller animated"); } completion:^(BOOL finished) { NSLog(@"setup root view controller finished"); }]; } else { self.window.rootViewController = newController; } }


Hice una UISplitView como vista inicial, que va modalmente a una UIView de pantalla completa y de regreso a UISplitView. Si necesita regresar a SplitView, debe usar un segue personalizado.

Lea este enlace (tradúzcalo desde japonés)

UIViewController para UISplitViewController


Me gustaría contribuir con mi enfoque para presentar un UISplitViewController, como te gustaría a través de -presentViewController:animated:completion: (todos sabemos que no funcionará). Creé una subclase UISplitViewController que responde a:

-presentAsRootViewController -returnToPreviousViewController

La clase, que al igual que otros enfoques exitosos, establece el UISplitViewController como el control raíz de la ventana, pero lo hace con una animación similar a la que obtienes (de forma predeterminada) con -presentViewController:animated:completion:

PresentableSplitViewController.h

#import <UIKit/UIKit.h> @interface PresentableSplitViewController : UISplitViewController - (void) presentAsRootViewController; @end

PresentableSplitViewController.m

#import "PresentableSplitViewController.h" @interface PresentableSplitViewController () @property (nonatomic, strong) UIViewController *previousViewController; @end @implementation PresentableSplitViewController - (void) presentAsRootViewController { UIWindow *window=[[[UIApplication sharedApplication] delegate] window]; _previousViewController=window.rootViewController; UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES]; window.rootViewController = self; [window insertSubview:windowSnapShot atIndex:0]; CGRect dstFrame=self.view.frame; CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform); offset.width*=self.view.frame.size.width; offset.height*=self.view.frame.size.height; self.view.frame=CGRectOffset(self.view.frame, offset.width, offset.height); [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ self.view.frame=dstFrame; } completion:^(BOOL finished) { [windowSnapShot removeFromSuperview]; }]; } - (void) returnToPreviousViewController { if(_previousViewController) { UIWindow *window=[[[UIApplication sharedApplication] delegate] window]; UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES]; window.rootViewController = _previousViewController; [window addSubview:windowSnapShot]; CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform); offset.width*=windowSnapShot.frame.size.width; offset.height*=windowSnapShot.frame.size.height; CGRect dstFrame=CGRectOffset(windowSnapShot.frame, offset.width, offset.height); [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ windowSnapShot.frame=dstFrame; } completion:^(BOOL finished) { [windowSnapShot removeFromSuperview]; _previousViewController=nil; }]; } } @end


Otra opción: en el controlador de vista de detalles, muestro un controlador de vista modal:

let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate if (!appDelegate.loggedIn) { // display the login form let storyboard = UIStoryboard(name: "Storyboard", bundle: nil) let login = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController self.presentViewController(login, animated: false, completion: { () -> Void in // user logged in and is valid now self.updateDisplay() }) } else { updateDisplay() }

No descarte el controlador de inicio de sesión sin configurar el indicador de inicio de sesión. Tenga en cuenta que en IPhones, el controlador de vista maestra será el primero, por lo que un código muy similar deberá estar en el controlador de vista maestra.



Touche! Se encontró con el mismo problema y lo resolvió de la misma manera usando modales. En mi caso, era una vista de inicio de sesión y luego el menú principal también para mostrarse antes de la vista dividida. Usé la misma estrategia que pensaste. Yo (así como otros mucha gente inteligente de iOS con la que hablé) no pude encontrar una mejor salida. Funciona bien para mí El usuario nunca nota el modal de todos modos. Preséntalos así. Y sí, también puedo decirte que hay bastantes aplicaciones haciendo lo mismo bajo los trucos de la tienda de aplicaciones. :) En otra nota, hágamelo saber si usted encuentra una mejor manera de salir de alguna manera alguna vez :)


En serio, no creía que este concepto de tener algún UIViewController para mostrar antes de UISplitViewController (formulario de inicio de sesión, por ejemplo) resulta ser tan complicado, hasta que tuve que crear ese tipo de vista jerárquica.

Mi ejemplo se basa en iOS 8 y XCode 6.0 (Swift), por lo que no estoy seguro de si este problema existía anteriormente de la misma manera, o se debe a algunos nuevos errores introducidos con iOS 8, pero a partir de todas las preguntas similares, encontrado, no vi solución completa ''no muy hacky'' a este problema.

Te guiaré por algunas de las cosas que probé antes de terminar con una solución (al final de esta publicación). Cada ejemplo se basa en la creación de un nuevo proyecto a partir de la plantilla Master-Detail sin CoreData habilitado.

Primer intento (transición modal a UISplitViewController):

  1. crear una nueva subclase UIViewController (LoginViewController por ejemplo)
  2. agregue un nuevo controlador de vista en el guión gráfico, configúrelo como controlador de vista inicial (en lugar de UISplitViewController) y conéctelo a LoginViewController
  3. agregue UIButton a LoginViewController y cree una transición modal de ese botón a UISplitViewController
  4. mover el código de configuración repetitivo para UISplitViewController desde el didFinishLaunchingWithOptions didFinishLaunchingWithOptions de AppDelegate al didFinishLaunchingWithOptions de LoginViewController

Esto casi funcionó. Digo casi, porque después de que la aplicación se inicia con LoginViewController y toca el botón y pasa a UISplitViewController, hay un extraño error: mostrar y ocultar el controlador de vista maestra en el cambio de orientación ya no está animado.

Después de un tiempo luchando con este problema y sin una solución real, pensé que de alguna manera estaba conectado con esa extraña regla de que UISplitViewController debe ser rootViewController (y en este caso no lo es, LoginViewController) así que renuncié a esta solución no tan perfecta .

Segundo intento (transición modal desde UISplitViewController):

  1. crear una nueva subclase UIViewController (LoginViewController por ejemplo)
  2. agregar nuevo controlador de vista en el guión gráfico y conectarlo a LoginViewController (pero esta vez deje que UISplitViewController sea el controlador de vista inicial)
  3. crear transición modal de UISplitViewController a LoginViewController
  4. agregue UIButton a LoginViewController y cree un desenrollado segue desde ese botón

Finalmente, agregue este código a didFinishLaunchingWithOptions de AppDelegate después del código repetitivo para configurar UISplitViewController:

window?.makeKeyAndVisible() splitViewController.performSegueWithIdentifier("segueToLogin", sender: self) return true

o intente con este código en su lugar:

window?.makeKeyAndVisible() let loginViewController = splitViewController.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController splitViewController.presentViewController(loginViewController, animated: false, completion: nil) return true

Ambos ejemplos producen las mismas cosas malas:

  1. Salidas de consola: Unbalanced calls to begin/end appearance transitions for <UISplitViewController: 0x7fc8e872fc00>
  2. UISplitViewController debe mostrarse primero antes de iniciar sesión de modo automático (prefiero presentar únicamente el formulario de inicio de sesión para que el usuario no vea UISplitViewController antes de iniciar sesión)
  3. Desactivar segue no se llama (esto es totalmente otro error, y no voy a entrar en esa historia ahora)

Solución (actualizar rootViewController)

La única forma que encontré que funciona correctamente es si cambia el control de raíz de la ventana sobre la marcha:

  1. Defina Storyboard ID para LoginViewController y UISplitViewController, y agregue algún tipo de propiedad loggedIn a AppDelegate.
  2. En función de esta propiedad, crea una instancia del controlador de vista apropiado y luego configúralo como rootViewController.
  3. Hazlo sin animación en didFinishLaunchingWithOptions pero animado cuando se llama desde la interfaz de usuario.

Aquí hay un código de muestra de AppDelegate:

var loggedIn = false func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { setupRootViewController(false) return true } func setupRootViewController(animated: Bool) { if let window = self.window { var newRootViewController: UIViewController? = nil var transition: UIViewAnimationOptions // create and setup appropriate rootViewController if !loggedIn { let loginViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController newRootViewController = loginViewController transition = .TransitionFlipFromLeft } else { let splitViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("SplitVC") as UISplitViewController let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem() splitViewController.delegate = self let masterNavigationController = splitViewController.viewControllers[0] as UINavigationController let controller = masterNavigationController.topViewController as MasterViewController newRootViewController = splitViewController transition = .TransitionFlipFromRight } // update app''s rootViewController if let rootVC = newRootViewController { if animated { UIView.transitionWithView(window, duration: 0.5, options: transition, animations: { () -> Void in window.rootViewController = rootVC }, completion: nil) } else { window.rootViewController = rootVC } } } }

Y este es código de muestra de LoginViewController:

@IBAction func login(sender: UIButton) { let delegate = UIApplication.sharedApplication().delegate as AppDelegate delegate.loggedIn = true delegate.setupRootViewController(true) }

También me gustaría saber si hay alguna forma mejor / más limpia para que esto funcione correctamente en iOS 8.