iOS在没有闪光灯的情况下启动时呈现模式视图控制器



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

有没有一种方法可以在应用程序启动时显示模式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 ...]语句之前,但后来模态甚至没有出现。

所有presentalViewController方法都要求呈现视图控制器首先出现。为了隐藏根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];
    }];
}];

Bruce在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()
                    })
                })
            }
        }
    }

可能是您可以使用的"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特性在视图上添加一些动画。希望有所帮助:)

此问题在iOS 10中仍然存在。我的解决方案是:

  1. viewWillAppear中,将模态VC作为子VC添加到根VC
  2. 在CCD_ 6中:
    1. 删除作为rootVC的子级的modalVC
    2. 无动画的模式化呈现儿童VC

代码:

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

可能是一个糟糕的解决方案,但您可以制作一个包含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;
}

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

可能会很晚,但在您的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;
}

这就是我对故事板的处理方式,它可以处理多个模式。这个例子有3。底部、中间和顶部。

只需确保在界面生成器中正确设置每个viewController的故事板ID即可。

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

最新更新