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

Posted

技术标签:

【中文标题】应用程序激活时呈现模态视图【英文标题】:Present a Modal View When App Becomes Active 【发布时间】:2012-09-21 15:40:56 【问题描述】:

我想在我的应用启动时展示一个模态视图控制器(用于登录屏幕),以及在用户点击主页按钮然后重新启动应用后它再次变为活动状态时。

我首先尝试在根视图控制器的viewDidAppear: 方法中呈现模态视图。这在应用首次启动时效果很好,但在应用再次激活时不会调用此方法。

- (void)viewDidAppear:(BOOL)animated 
    [super viewDidAppear:animated];
    [self presentModalView];


- (void)presentModalView 
    if(![AuthenticationService sharedInstance].isAuthenticated) 
        _modalVC = [self.storyboard instantiateViewControllerWithIdentifier:self.modalViewControllerIdentifier];
        _modalVC.delegate = self;
        [self presentViewController:_modalVC animated:YES completion:nil];
    

接下来,我尝试在applicationDidBecomeActive: 方法中从我的应用委托调用它。

- (void)applicationDidBecomeActive:(UIApplication *)application

    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

    ModalPresentingUISplitViewController *splitViewController = (ModalPresentingUISplitViewController *)self.window.rootViewController;
    [splitViewController presentModalView];

这在表面上似乎工作正常,但我在日志中收到 Unbalanced calls to begin/end appearance transitions for <ModalPresentingUISplitViewController: 0x7251590> 警告。我觉得我在 UISplitView 完成呈现之前以某种方式呈现模态视图,但我不知道如何解决这个问题。

当应用程序激活时,如何“自动”从根视图控制器呈现模态视图,并在“正确”时刻执行,以免使拆分视图控制器失衡?

【问题讨论】:

【参考方案1】:

忘记了这个问题在这里。是的,我有一个解决方案。我不禁觉得有一种更优雅或更正确的方法可以做到这一点,但这对我有用......

这假设您使用的是 ARC 和故事板;你已经为你的登录视图创建了一个 UIViewController,它带有来自 UISplitViewController(或任何你的根视图控制器)的模态序列。

UISplitViewController(或任何你的根视图控制器)

- (id)initWithCoder:(NSCoder *)aDecoder 
    if(self = [super initWithCoder:aDecoder]) 
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(presentModalView) name:UIApplicationDidBecomeActiveNotification object:nil];
    
    return self;


- (void)dealloc 
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];


- (void) viewDidAppear:(BOOL)animated 
    [super viewDidAppear:animated];
    self.viewHasAppeared = YES;
    [self presentModalView];


- (void) presentModalView 
    if(self.viewHasAppeared && !self.userAuthenticated) 
        [self performSegueWithIdentifier:@"ShowLoginView" sender:self];
    


- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 
    if([[segue identifier] isEqualToString:@"ShowLoginView"]) 
        JDPLoginViewController *dest = [segue destinationViewController];
        dest.delegate = self;
    


- (void) dismissLogin 
    self.userAuthenticated = YES;
    [self dismissViewControllerAnimated:YES completion:nil];

以下是代码中需要注意的重要部分...

    我们在两个地方调用presentModalView - 在viewDidAppear 中,它将负责在应用首次启动时呈现我们的登录视图,并且 我们将presentModalView 注册为 UIApplicationDidBecomeActiveNotification 事件的观察者,以便在应用程序在后台激活后调用该方法。 最后,我们在 UISplitViewController 上创建一个 BOOL 属性 viewHasAppeared 来跟踪 UISplitViewController 的视图是否出现,因此我们不会尝试在 UISplitViewController 的视图出现之前呈现模式登录。李>

以下是不同的场景...

应用首次启动:

presentModalViewUIApplicationDidBecomeActiveNotification 事件调用,但由于未加载 UISplitViewController 的视图(并且 viewHasAppeared BOOL 为 NO,因此没有任何反应。赢。我们不应该在不应该的时候呈现视图。 然后最终调用viewDidAppear,它将viewHasAppeared 设置为YES,然后调用presentModalView。显示登录屏幕。一切正常 - 耶!

应用在后台后激活

presentModalView 再次被UIApplicationDidBecomeActiveNotification 事件调用,就像在第一个场景中一样,但是这次viewHasAppeared 是YES,所以登录视图按预期呈现。耶!

就像我说的,这感觉有点难看,但在我找到更好的解决方案之前,它可以完成工作。希望它对你有用。

【讨论】:

如果根视图控制器已经呈现了其他(可能是多个)视图控制器怎么办——最上面的活动视图控制器不负责呈现给登录 vc 吗? 自从我在 ios 中工作以来已经有一段时间了。在“正常”情况下,您最顶层的 VC 将负责呈现模态,但对于可能必须在任何时候呈现的登录(即使其他模态呈现的 VC 可见),我不知道除了从根目录呈现之外,您还有其他选择。 当根视图控制器显示登录视图控制器并且根视图不是最顶层时,我在调试控制台中收到警告 Presenting view controllers on detached view controllers is discouraged。我认为您可能需要在必要时为每个视图控制器增加显示登录视图的负担...如果您的根视图控制器是选项卡栏控制器(或上面的拆分视图),那么它的 always 位于顶部。 【参考方案2】:

你试过 UIView 的 viewWillAppear 吗?

【讨论】:

viewWillAppear 在视图被添加到视图层次结构之前(以及在viewDidAppear 之前)被调用,所以这绝对是为时过早。【参考方案3】:

链接@jpolete 的回答我做了一些不同的事情。此外,我希望登录屏幕仅在应用程序在后台运行超过 15 秒后才出现(用户总是必须重新登录很痛苦)。

这个演示的源代码可以在github找到

和@jpolete 一样,我将大部分逻辑封装在根视图控制器中,在我的例子中它是一个导航控制器(iPhone 示例)。 userLoggedIn 标记用户是否已通过身份验证。 presentingLoginController 标志让我知道当前是否显示登录屏幕。 backgroundTime 保存用户进入后台的时间戳。这是类扩展:

@interface RootNavigationController () <LoginDelegate>
@property (assign, nonatomic) BOOL userLoggedIn;
@property (strong, nonatomic) NSDate *backgroundTime;
@property (assign, nonatomic) BOOL presentingLoginController;
-(void)applicationDidBecomeActive:(NSNotification*) notification;
-(void)applicationDidEnterBackground:(NSNotification*) notification;
@end

当视图被加载时,我添加了适当的通知钩子:

@implementation RootNavigationController

- (void)viewDidLoad

    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationDidBecomeActive:)
                                                 name:UIApplicationDidBecomeActiveNotification
                                               object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationDidEnterBackground:)
                                                 name:UIApplicationDidEnterBackgroundNotification
                                               object:nil];


-(void)dealloc 
    [[NSNotificationCenter defaultCenter] removeObserver:self];

如果用户没有通过身份验证并且我们当前没有显示登录控制器,我们将触发登录序列。

-(void)loginIfNecessary 
    if (!self.userLoggedIn && !self.presentingLoginController) 
        self.presentingLoginController = YES;
        [self performSegueWithIdentifier:@"RootLoginSegue" sender:self];
    

这里我们将根视图控制器设置为登录控制器的loginDelegate。 当成功登录时会通知此委托(登录控制器嵌入在另一个导航控制器中):

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 
    if ([segue.identifier isEqualToString:@"RootLoginSegue"]) 
        UINavigationController *navController = segue.destinationViewController;
        LoginTableViewController *loginController = (LoginTableViewController *) navController.topViewController;
        loginController.loginDelegate = self;
    

当成功登录时,我们执行以下操作:

-(void)didLogin  // LoginDelegate method called to login controller after successsful login
    self.presentingLoginController = NO;
    self.userLoggedIn = YES;

当视图第一次出现时,当它在后台出现时,或者在被“隐藏”后我们登录(如果需要):

-(void)viewDidAppear:(BOOL)animated 
    [super viewDidAppear:animated];
    [self loginIfNecessary];

当我们进入后台我们记录时间:

-(void)applicationDidEnterBackground:(NSNotification*) notification 
    self.backgroundTime = [NSDate date];

当我们进入前台并且第一次或足够的时间已经过去时,我们 强制用户重新登录(如有必要):

-(void) applicationDidBecomeActive:(NSNotification*) notification 
    const NSTimeInterval maxBackgroundTime = 15.0;
    if (!self.backgroundTime || [[NSDate date] timeIntervalSinceDate:self.backgroundTime] > maxBackgroundTime) 
        self.userLoggedIn = NO;
    
    [self loginIfNecessary];


@end

【讨论】:

以上是关于应用程序激活时呈现模态视图的主要内容,如果未能解决你的问题,请参考以下文章

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

为啥一个简单的模态视图控制器在呈现和关闭时会滞后?

从模态视图呈现视图控制器给出错误

从 didFinishLaunchingWithOptions 呈现模态视图

从应用程序委托不平衡调用开始/结束外观呈现模态视图

横向 IOS 应用程序在呈现模态视图控制器时抛出异常