iOS在启动时呈现模态视图控制器而不使用flash

Posted

技术标签:

【中文标题】iOS在启动时呈现模态视图控制器而不使用flash【英文标题】:iOS Present modal view controller on startup without flash 【发布时间】:2014-10-14 08:16:16 【问题描述】:

我想在首次启动时向用户展示一个教程向导。

有没有办法在应用程序启动时呈现模态UIViewController,而至少在一毫秒内看不到它背后的rootViewController

现在我正在做这样的事情(为了清楚起见省略了首次启动检查):

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

没有运气。我试图将[self.window makeKeyAndVisible]; 移到[... presentViewController:tutorialViewController ...] 语句之前,但是模态甚至没有出现。

【问题讨论】:

为什么不将 TutorialViewController 设为 RootViewController? @UlasSancak 因为我希望在用户完成教程后以模态方式将其关闭(最后一个屏幕有一个Let's Start 按钮)。 这些答案有帮助吗? @Pandara 的回答解决了主要问题(闪烁),但目前尚无模态 VC 的解决方案 【参考方案1】:

所有 presentViewController 方法都需要先出现呈现视图控制器。为了隐藏根 VC,必须呈现一个覆盖。启动屏幕可以继续在窗口上显示,直到显示完成,然后淡出覆盖。

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

【讨论】:

如果有人面临开始/结束外观转换的不平衡调用,请参阅 Spoek 答案以获取使用DispatchQueue 的解决方案。 我认为 Spoek 把他的名字改成了 ullstrm,反正这是解决方案 Cœur 指的是:***.com/a/41469734/84783 【参考方案2】:

布鲁斯在 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()
                    )
                )
            
        
    

【讨论】:

迄今为止最好的解决方案。您甚至可以添加解释为什么必须使用DispatchQueue.global().async 来解决开始/结束外观转换的不平衡调用 在某些设备上,这似乎偶尔会导致我在启动屏幕上无休止地挂起。其他人有这个问题吗? 似乎是一个“试图在视图上呈现不在视图层次结构中的视图”问题,我试图通过在切换到主线程时添加延迟来解决这个问题,尽管我可以更漂亮,并将其设置为等到主视图控制器完全加载。 我在 ios 13 beta 6 上遇到了这个问题。我设置了launch .modalPresentationStyle = .fullScreen,但window?.addSubview(launch.view) 导致演示不是全屏,而是全屏和卡片演示之间出现了一些奇怪的混合.还有其他人在 iOS 13 上遇到问题吗? 好的,似乎使用启动 VC 的第二个实例并将那个视图添加到窗口而不是同一个实例修复了 iOS 13 上的问题。与此示例不同,我使用的是视图要提交的 vc 的数量。【参考方案3】:

也许你可以使用“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];

当您需要解散您的导师时,您可以从超级视图中删除其视图。您还可以通过设置 alpha 属性在视图上添加一些动画。希望有帮助:)

【讨论】:

我只是在尝试这种方法。我不能接受这是正确的答案,因为它不使用模态演示,但我赞成它,谢谢! 哈哈哈,你可以参考一些关于“childViewController”的文档,这是一种管理视图的有效方法。做一件事没有唯一正确的方法,对吧?;) 是的,但是对于在这里寻找解决方案的人来说,恕我直言,这不是正确的。无论如何我都会使用这种方法,因为它解决了根本问题:) 在我看来这是正确的解决方案,因为它不能使用 modalviewcontroller :-)【参考方案4】:

这个问题在 iOS 10 中仍然存在。我的解决方法是:

    viewWillAppear 中将模态VC 作为childVC 添加到rootVC 在viewDidAppear
      将 modalVC 作为 rootVC 的子项移除 以模态方式呈现不带动画的 childVC

代码:

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)
        

        //....
    

【讨论】:

这是一个不错的替代解决方案。但是由于我们正在处理启动问题,因此处理变通方法不应该是 viewController 的责任。这就是为什么我更喜欢 Spoek 的解决方案,它完全在 AppDelegate 中完成。 注意:Spoek 名称现在是 ullstrm @Cœur 这个问题不一定只发生在启动时;当视图控制器第一次出现时,它可能会在视图控制器想要在其自身之上呈现模态视图时发生。该解决方案解决了一般情况,并且比破解 AppDelegate 的启动序列更好。如果显示模式的决定是由视图控制器做出的,那么 AppDelegate 与它有任何关系是不合适的。 @devios1 啊,我不知道这个问题可能发生在启动之外。我想我需要尝试一下。但我不能给出更多的分数:两年前我已经对这个答案投了赞成票。【参考方案5】:

可能是一个糟糕的解决方案,但您可以制作一个包含 2 个容器的 ViewController,其中两个容器都链接到一个 VC。然后你可以控制哪个容器应该在代码中可见,这是一个想法

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;

【讨论】:

【参考方案6】:

Bruce 的回答为我指明了正确的方向,但是因为我的模式可能会比启动时更频繁地出现(这是一个登录屏幕,所以如果他们注销就需要出现),我不想绑定我的叠加层直接到视图控制器的呈现。

这是我想出的逻辑:

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

在不同的部分中,我执行了以典型的self.window.rootViewController presentViewController:... 格式呈现我的登录 VC 的逻辑,无论它是应用启动还是其他方式,我都可以使用它。

如果有人关心,我是这样创建叠加视图的:

@implementation WSILaunchImageView

- (instancetype)init

    self = [super initWithFrame:[UIScreen mainScreen].bounds];
    if (self) 
        self.image = WSILaunchImage();
    
    return self;

这是启动图像本身的逻辑:

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;

Aaaa 只是为了完成,下面是那些 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;

【讨论】:

【参考方案7】:
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
    

【讨论】:

你能提供一些上下文吗?【参考方案8】:

可能会迟到,但在您的 AppDelegate 中您可以这样做:

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

【讨论】:

这仍然会在演示发生时导致一些闪烁。【参考方案9】:

这就是我使用故事板的方式,它适用于多种模式。 此示例有 3 个。底部、中间和顶部。

请确保在界面生成器中正确设置每个 viewController 的 storyboardID。

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

【讨论】:

谢谢,不过这样短闪还是会出现 我在这段代码中根本看不到 rootviewcontroller,即使加载了 3 个控制器也是如此。尽管我使用的控制器并没有太多的功能。也许您一次尝试加载太多?会不会是您看到的启动屏幕? 无论我尝试在根视图控制器中加载什么,有时都会出现。 sometimes 不适合我:( 您可能需要查看自定义过渡。首先加载顶视图控制器,并使用自定义过渡/转场创建类似于模态解雇的动画。

以上是关于iOS在启动时呈现模态视图控制器而不使用flash的主要内容,如果未能解决你的问题,请参考以下文章

应用程序激活时呈现模态视图

UIActivity activityViewController 在 iPad 上以模态方式呈现,而不是在弹出窗口中

当视图控制器从 nib 文件加载时,以模态方式在当前上下文中呈现视图控制器

在模态呈现另一个视图控制器后视图控制器会发生啥

为啥在呈现这个模态视图时会有延迟?

关闭从模态呈现的视图控制器翻转的视图控制器