web-dev-qa-db-fra.com

contrôleur de vue modal iOS présent au démarrage sans flash

Je voudrais présenter modalement, au premier démarrage, un assistant de tutoriel à l'utilisateur.

Existe-t-il un moyen de présenter une UIViewController modale au démarrage de l'application, sans voir, au moins pendant une milliseconde, la rootViewController derrière?

Maintenant, je fais quelque chose comme ceci (en omettant les vérifications de premier lancement pour plus de clarté):

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

sans chance. J'ai essayé de déplacer [self.window makeKeyAndVisible]; avant l'instruction [... presentViewController:tutorialViewController ...], mais le modal n'apparaît même pas.

32
sonxurxo

Toutes les méthodes presentViewController exigent que le contrôleur de vue de présentation soit apparu en premier. Afin de masquer la racine VC, une superposition doit être présentée. L’écran de lancement peut continuer à être présenté sur la fenêtre jusqu’à ce que la présentation soit terminée, puis disparaître progressivement.

    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];
    }];
}];
30
Bruce Burnham

peut être votre peut utiliser le "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];

Lorsque vous devez renvoyer votre tuteur, vous pouvez supprimer sa vue de la vue d'ensemble. Vous pouvez également ajouter une animation à la vue en définissant la propriété alpha.Hope utile :)

8
Pandara

Réponse positive de Bruce dans 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()
                    })
                })
            }
        }
    }
7
ullstrm

Ce problème existe toujours sous iOS 10. Mon correctif était le suivant:

  1. dans viewWillAppear ajouter le modal VC en tant que childVC à rootVC
  2. dans la viewDidAppear:
    1. Supprimez le modalVC en tant qu'enfant de rootVC
    2. Présenter modalement le childVC sans animation

Code:

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

        //....
    }
}
6
Benedict Cohen

La réponse de Bruce m'a orienté dans la bonne direction, mais comme mon modal peut apparaître plus souvent que juste au lancement (c'est un écran de connexion, il doit donc apparaître s'il se déconnecte), je ne voulais pas lier directement ma superposition présentation du contrôleur de vue.

Voici la logique que je suis venu avec:

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

Dans une section différente, j'exécute la logique de présentation de mon identifiant de connexion VC au format self.window.rootViewController presentViewController:... typique que je peux utiliser, qu'il s'agisse d'un lancement d'application ou autrement.

Si cela vous intéresse, voici comment j'ai créé ma vue superposée:

@implementation WSILaunchImageView

- (instancetype)init
{
    self = [super initWithFrame:[UIScreen mainScreen].bounds];
    if (self) {
        self.image = WSILaunchImage();
    }
    return self;
}

Et voici la logique de l'image de lancement elle-même:

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

En guise de conclusion, voici à quoi ressemblent ces méthodes 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;
}
0
Stakenborg

Il peut être tard mais dans votre AppDelegate, vous pouvez faire ceci:

//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;
}
0
Bejil
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
    }
}
0
Azon

Peut-être une mauvaise solution, mais vous pouvez créer un ViewController avec 2 conteneurs, les deux conteneurs étant liés à un VC chacun. Ensuite, vous pouvez contrôler quel conteneur doit être visible dans le code, c'est une idée

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;
}
0
Erik