guidelines - iOS presente controlador de vista modal en el arranque sin flash
tab bar ios (9)
Me gustaría presentar de manera modal, al primer inicio, un asistente de tutorial para el usuario.
¿Hay alguna manera de presentar un UIViewController
modal en el inicio de la aplicación, sin ver, al menos durante un milisegundo, el rootViewController
detrás de él?
Ahora estoy haciendo algo como esto (omitir los controles de primer lanzamiento para mayor claridad):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// ...
UIStoryboard *storyboard = self.window.rootViewController.storyboard;
TutorialViewController* tutorialViewController = [storyboard instantiateViewControllerWithIdentifier:@"tutorial"];
tutorialViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.window makeKeyAndVisible];
[self.window.rootViewController presentViewController:tutorialViewController animated:NO completion:NULL];
}
sin suerte He intentado mover [self.window makeKeyAndVisible];
antes de la [... presentViewController:tutorialViewController ...]
, pero el modal ni siquiera aparece.
Así es como lo hago con guiones gráficos y funciona con múltiples modales. Este ejemplo tiene 3. Inferior, medio y superior.
Solo asegúrese de tener el storyboardID de cada viewController configurado correctamente en el constructor de interfaces.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
BottomViewController *bottomViewController = [storyboard instantiateViewControllerWithIdentifier:@"BottomViewController"];
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[window setRootViewController:bottomViewController];
[window makeKeyAndVisible];
if (!_loggedIn) {
MiddleViewController *middleViewController = [storyboard instantiateViewControllerWithIdentifier:@"middleViewController"];
TopViewController *topViewController = [storyboard instantiateViewControllerWithIdentifier:@"topViewController"];
[bottomViewController presentViewController:middleViewController animated:NO completion:nil];
[middleViewController presentViewController:topViewController animated:NO completion:nil];
}
else {
// setup as you normally would.
}
self.window = window;
return YES;
}
Este problema aún existe en iOS 10. Mi solución fue:
- en
viewWillAppear
agrega el VC modal como childVC al rootVC - en el
viewDidAppear
:- Elimine modalVC como hijo de rootVC
- Modalmente presente el childVC sin animación
Código:
extension UIViewController {
func embed(childViewController: UIViewController) {
childViewController.willMove(toParentViewController: self)
view.addSubview(childViewController.view)
childViewController.view.frame = view.bounds
childViewController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addChildViewController(childViewController)
}
func unembed(childViewController: UIViewController) {
assert(childViewController.parent == self)
childViewController.willMove(toParentViewController: nil)
childViewController.view.removeFromSuperview()
childViewController.removeFromParentViewController()
}
}
class ViewController: UIViewController {
let modalViewController = UIViewController()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//BUG FIX: We have to embed the VC rather than modally presenting it because:
// - Modal presentation within viewWillAppear(animated: false) is not allowed
// - Modal presentation within viewDidAppear(animated: false) is not visually glitchy
//The VC is presented modally in viewDidAppear:
if self.shouldPresentModalVC {
embed(childViewController: modalViewController)
}
//...
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//BUG FIX: Move the embedded VC to be a modal VC as is expected. See viewWillAppear
if modalViewController.parent == self {
unembed(childViewController: modalViewController)
present(modalViewController, animated: false, completion: nil)
}
//....
}
}
La respuesta de Bruce me apuntó en la dirección correcta, pero como mi modal puede aparecer más a menudo que solo en el lanzamiento (es una pantalla de inicio de sesión, por lo que debe aparecer si se desconectan), no quería atar mi capa directamente al presentación del controlador de vista.
Aquí está la lógica que se me ocurrió:
self.window.rootViewController = _tabBarController;
[self.window makeKeyAndVisible];
WSILaunchImageView *launchImage = [WSILaunchImageView new];
[self.window addSubview:launchImage];
[UIView animateWithDuration:0.1f
delay:0.5f
options:0
animations:^{
launchImage.alpha = 0.0f;
} completion:^(BOOL finished) {
[launchImage removeFromSuperview];
}];
En una sección diferente, llevo a cabo la lógica de presentar mi VC de inicio de sesión en el típico self.window.rootViewController presentViewController:...
formato que puedo usar independientemente de si es un lanzamiento de la aplicación o no.
Si a alguien le importa, así es como creé mi vista de superposición:
@implementation WSILaunchImageView
- (instancetype)init
{
self = [super initWithFrame:[UIScreen mainScreen].bounds];
if (self) {
self.image = WSILaunchImage();
}
return self;
}
Y esta es la lógica para la imagen de lanzamiento en sí:
UIImage * WSILaunchImage()
{
static UIImage *launchImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (WSIEnvironmentDeviceHas480hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-700"];
else if (WSIEnvironmentDeviceHas568hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-700-568h"];
else if (WSIEnvironmentDeviceHas667hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-800-667h"];
else if (WSIEnvironmentDeviceHas736hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-800-Portrait-736h"];
});
return launchImage;
}
Y solo por completitud, estos son los métodos de EnvironmentDevice:
static CGSize const kIPhone4Size = (CGSize){.width = 320.0f, .height = 480.0f};
BOOL WSIEnvironmentDeviceHas480hScreen(void)
{
static BOOL result = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
result = CGSizeEqualToSize([UIScreen mainScreen].bounds.size, kIPhone4Size);
});
return result;
}
La respuesta votada de Bruce en Swift 3:
if let vc = window?.rootViewController?.storyboard?.instantiateViewController(withIdentifier: "LOGIN")
{
let launch = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()!
launch.view.frame = vc.view.bounds
launch.view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
window?.makeKeyAndVisible()
window?.addSubview(launch.view)
//Using DispatchQueue to prevent "Unbalanced calls to begin/end appearance transitions"
DispatchQueue.global().async {
// Bounce back to the main thread to update the UI
DispatchQueue.main.async {
self.window?.rootViewController?.present(vc, animated: false, completion: {
UIView.animate(withDuration: 0.5, animations: {
launch.view.alpha = 0
}, completion: { (_) in
launch.view.removeFromSuperview()
})
})
}
}
}
Puede ser tarde, pero en su AppDelegate puede hacer esto:
//Set your rootViewController
self.window.rootViewController=myRootViewController;
//Hide the rootViewController to avoid the flash
self.window.rootViewController.view.hidden=YES;
//Display the window
[self.window makeKeyAndVisible];
if(shouldPresentModal){
//Present your modal controller
UIViewController *lc_viewController = [UIViewController new];
UINavigationController *lc_navigationController = [[UINavigationController alloc] initWithRootViewController:lc_viewController];
[self.window.rootViewController presentViewController:lc_navigationController animated:NO completion:^{
//Display the rootViewController to show your modal
self.window.rootViewController.view.hidden=NO;
}];
}
else{
//Otherwise display the rootViewController
self.window.rootViewController.view.hidden=NO;
}
Puede ser una mala solución, pero podría crear un ViewController con 2 contenedores, donde ambos contenedores están vinculados a un VC cada uno. Entonces puedes controlar qué contenedor debería ser visible en el código, eso es una idea
if (!firstRun) {
// Show normal page
normalContainer.hidden = NO;
firstRunContainer.hidden = YES;
} else if (firstRun) {
// Show first run page or something similar
normalContainer.hidden = YES;
firstRunContainer.hidden = NO;
}
Todos los métodos presentes de ViewController requieren que el controlador de vista de presentación aparezca primero. Para ocultar el VC raíz se debe presentar una superposición. La pantalla de inicio puede seguir presentándose en la ventana hasta que se complete la presentación y luego se difumina la superposición.
UIView* overlayView = [[[UINib nibWithNibName:@"LaunchScreen" bundle:nil] instantiateWithOwner:nil options:nil] firstObject];
overlayView.frame = self.window.rootViewController.view.bounds;
overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
UIStoryboard *storyboard = self.window.rootViewController.storyboard;
TutorialViewController* tutorialViewController = [storyboard instantiateViewControllerWithIdentifier:@"tutorial"];
tutorialViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.window makeKeyAndVisible];
[self.window addSubview:overlayView];
[self.window.rootViewController presentViewController:tutorialViewController animated:NO completion:^{
NSLog(@"displaying");
[UIView animateWithDuration:0.5 animations:^{
overlayView.alpha = 0;
} completion:^(BOOL finished) {
[overlayView removeFromSuperview];
}];
}];
puede ser tu puedes usar el "childViewController"
UIStoryboard *storyboard = self.window.rootViewController.storyboard;
TutorialViewController* tutorialViewController = [storyboard instantiateViewControllerWithIdentifier:@"tutorial"];
[self.window addSubview: tutorialViewController.view];
[self.window.rootViewController addChildViewController: tutorialViewController];
[self.window makeKeyAndVisible];
Cuando necesite descartar a su tutor, puede eliminar su vista de la supervista. También puede agregar algo de animación en la vista estableciendo la propiedad alpha. Espero que sea útil :)
let vc = UIViewController()
vc.modalPresentationStyle = .custom
vc.transitioningDelegate = noFlashTransitionDelegate
present(vc, animated: false, completion: nil)
class NoFlashTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate {
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
if source.view.window == nil,
let overlayViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController(),
let overlay = overlayViewController.view {
source.view.addSubview(overlay)
UIView.animate(withDuration: 0, animations: {}) { (finished) in
overlay.removeFromSuperview()
}
}
return nil
}
}