在app启动时以模态方式呈现视图控制器

时间:2015-01-31 03:01:39

标签: ios objective-c

我的应用程序有一个设置屏幕,如果满足某些条件,应该在根视图控制器上以模态方式显示。

我已经浏览了SO和互联网,到目前为止最接近的答案是如何去做这件事:

AppDelegate, rootViewController and presentViewController

然而,这种方法存在两个问题:

  1. 在iOS 8中,这样做会使日志出现在控制台中,这似乎不是一个错误,但可能不是很好:
  2. Unbalanced calls to begin/end appearance transitions for UITabBarController: 0x7fe20058d570.

    1. 根视图控制器实际上会在应用程序启动时非常简短地显示,然后淡入到呈现的视图控制器中(即使我在animated:NO方法上明确调用presentViewController)。
    2. 我知道我可以在applicationDidFinishLaunchingWithOptions:中动态设置我的根控制器,但我特别希望以模态方式呈现设置屏幕,这样当用户完成它时,它会解散并且真正的应用程序的第一个视图透露。这就是说,我不想动态地将我的根视图控制器更改为我的设置屏幕,并在用户完成设置时以模态方式呈现我的应用体验。

      在我的根视图控制器viewDidLoad方法上显示视图控制器也会在第一次启动应用程序时导致UI明显闪烁。

      是否有可能在应用程序呈现任何内容之前以编程方式呈现视图控制器,以便第一个视图就位于模态视图控制器中?

      更新:感谢您的评论,按照建议添加我当前的代码:

      在我的AppDelegate.m

      - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
      {
          UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
      
          [self.window makeKeyAndVisible];
          [self.window.rootViewController presentViewController:[storyboard instantiateViewControllerWithIdentifier:@"setupViewController"] animated:NO completion:NULL];
      
          return YES;
      }
      

      这是我需要的,除了它在应用程序启动时简要显示窗口的根视图控制器一秒钟的事实,然后淡化setupViewController,我觉得奇怪,因为我呈现它没有动画和褪色不是无论如何都要呈现模态视图控制器。

      唯一能让我接近的是手动添加视图在根视图控制器的视图中执行加载方法如下:

      - (void)viewDidLoad
      {
          [self.view addSubview:setupViewController.view];
          [self addChildViewController:setupViewController];
      }
      

      这种方法的问题在于我不能再“原生”地关闭setupViewController,现在需要处理视图层次结构并自己动画出来,如果它是唯一的解决方案,这很好,但我希望在根视图控制器显示之前,有一种在没有动画的情况下以模态方式添加视图控制器的制裁方法。

      更新2 :在尝试了很多事情并等待2个月的答案后,这个问题提出了最有创意的解决方案:

      iOS Present modal view controller on startup without flash

      我想现在是时候接受只是不可能在根视图控制器出现之前在没有动画的情况下以模态方式呈现视图。但是,该线程中的建议是创建启动屏幕的实例并将其保留为默认值,直到模态视图控制器有机会呈现为止。

3 个答案:

答案 0 :(得分:10)

  

我想现在是时候接受它是不可能的在根视图控制器出现之前以模态方式呈现视图而没有动画。

出现之前,不,您无法出现。但是有多种有效的方法可以直观地解决这个问题。我建议使用下面的解决方案A,因为它简单。

一种。将launchScreen添加为子视图,然后显示,然后删除启动屏幕

Solution is presented here by ullstrm并且不会受到开始/结束外观转换的不平衡调用

let launchScreenView = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()!.view!
launchScreenView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
launchScreenView.frame = window!.rootViewController!.view.bounds
window?.rootViewController?.view.addSubview(launchScreenView)
window?.makeKeyAndVisible()
// avoiding: Unbalanced calls to begin/end appearance transitions.
DispatchQueue.global().async {
    DispatchQueue.main.async {
        self.window?.rootViewController?.present(myViewControllerToPresent, animated: false, completion: {
            launchScreenView.removeFromSuperview()
        })
    }
}

B中。首先是addChildViewController,然后删除,然后出现

Solution is presented here by Benedict Cohen

答案 1 :(得分:2)

我和你在同一条船上找到了同样的答案。我了解到,通过将setupViewController的modalPresentationStyle设置为.OverCurrentContext.OverFullScreen,您可以摆脱第一个问题(不平衡呼叫警告)。问题解决了 - 所以我想。

直到后来我才注意到第二个问题,那是我无法忍受的......回到原点。

正如您所说,我想要一个具有正常视图层次结构的解决方案,而我并不想假装'一些东西。我认为最优雅的解决方案是在首次解除你的setupViewController时切换你的windows rootViewController。

因此,在启动时,您将setupViewController设置为rootViewController(如果需要):

var window: UIWindow?
var tabBarController: UITabBarController!

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    tabBarController = window!.rootViewController as! UITabBarController
    if needsToShowSetup() {
        let setupViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SetupViewController") as! SetupViewController
        window?.rootViewController = setupViewController
    }
    return true
}

设置完成后,您可以在appDelegate中调用方法切换到“真实”' RootViewController的:

func switchToTabBarController() {
    let setupUpViewController = window!.rootViewController!

    tabBarController.view.frame = window!.bounds
    window!.insertSubview(tabBarController.view, atIndex: 0)

    let height = setupUpViewController.view.bounds.size.height
    UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1, options: .allZeros, animations: { () -> Void in
        setupUpViewController.view.transform = CGAffineTransformMakeTranslation(0, height)
        }) { (completed) -> Void in
            self.window!.rootViewController = self.tabBarController
    }
}

我正在追踪一个垂直的'解雇动画。对于交叉淡化和其他,您可以使用UIView.transitionFromView(fromView: UIView, toView: UIView...)。此后你可以正常方式呈现/解除你的setupController,所以你的doneButton动作可能是这样的:

@IBAction func doneButtonSelected(sender: UIButton) {
    if presentingViewController != nil {
        presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
    } else {
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        appDelegate.switchToTabBarController()
    }
}

实际上,我是通过委托实现的,第一次appDelegate是代表。

答案 2 :(得分:1)

  

我知道我可以在applicationDidFinishLaunchingWithOptions中动态设置我的根控制器:但我特别希望以模态方式呈现设置屏幕,这样当用户完成它时,它会解散并显示应用程序的真实第一个视图。

我有两个建议。一种是尝试在viewDidAppear:中执行此操作。我试过了,虽然如果仔细观察你确实看到了根视图控制器的视图,你几乎看不到它,有时候如果你眨眼就看不到它了:

-(void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"setupViewController"] animated:NO completion:NULL];
}

当然你需要添加一个标志,这样你每次调用viewDidAppear:都不会这样做 - 否则你将永远无法回到这个视图控制器!但这是微不足道的,我把它作为读者的练习。

我的另一个建议 - 您已经清楚地考虑过这样做 - 是使用自定义嵌入式(子)视图控制器。这解决了整个“演示”事物的局限性。

我会动态启动和设置事物,如果需要,可以使用子视图控制器,在启动过程中对其进行全部配置。子视图控制器的视图将覆盖根视图控制器的视图。这就是用户在应用启动时会看到的内容。

然后,当用户的设置过程结束并且用户“关闭”此视图时,您将使用动画拆除该视图,并删除子视图控制器 - 在下面显示根视图控制器的视图。动画将使这一点与所呈现视图的解雇无法区分,即使它不是真的。