2

编辑:检查下面的解决方案。

我正在为我的应用程序开发一个登录屏幕,除了一些边缘情况外,我大部分时间都在工作。我已经进行了设置,以便我在故事板中的 UITabBar 中有一个 segue,我在应用程序委托 applicationDidBecomeActive: 方法中触发。正如我所说,它在我迄今为止发现的一个边缘情况下都可以正常工作。

我的应用程序使用一些模态视图控制器,其中一些是 UIActivityViewControllers,如果这有所不同的话,可以输入和编辑一些核心数据实体。如果在应用程序进入后台时打开了这些模式视图控制器之一,则在重新打开应用程序并且我的登录信息不显示时,它将始终显示。我得到以下控制台消息

Warning: Attempt to present <UINavigationController: 0x1d51e320>  on <MPTabBarViewController: 0x1d5b4810> which is already presenting <UIActivityViewController: 0x1e38fc40>

这是我的代码

   - (void) displayLogin{
        NSLog(@"%s", __PRETTY_FUNCTION__);

        UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;

        NSDate *lastDate = [[NSUserDefaults standardUserDefaults] objectForKey:MPLastCloseDate];
        NSTimeInterval timeDiff = [[NSDate date] timeIntervalSinceDate:lastDate];
        int seconds = timeDiff;

        if ([[NSUserDefaults standardUserDefaults] integerForKey:MPPassCodeDelay] == MPScreenLockAlways || seconds >= 300) {
            NSLog(@"Should see login");
            [tabBarController performSegueWithIdentifier:@"loginScreen" sender:self];
        }
    }

我完全理解这个消息告诉我的内容,标签栏已经呈现了一个模态控制器,所以它不能呈现另一个。所以我的问题是,有没有更好的方法来实现这一点,以便登录将始终显示,甚至在模态视图之上?


好的,这是我目前的解决方案

根据 Bartu 的建议并要求由 Shawn 分享

我有一个正在工作的单例 loginManager 类,它需要在应用程序委托中调用 1 次,并且在任何可以被调用以呈现为模态的视图控制器中调用 1 次。我无法弄清楚如何按照 ViewController 类别的建议执行此操作,但是嘿,一些包含和方法调用还不错。我将它包含在 App-Prefix.pch 中,因此它随处可用。它是为 ARC 编写的,因此如果您喜欢管理自己的内存,则需要为此修改单例。最后一个警告,目前您需要为登录屏幕滚动您自己的 viewController。只需在所有星星的实现中查找注释部分,然后将您自己的视图控制器放在那里。我的仍然在我的应用故事板中,它基本上是 4 位数的密码,用于检查钥匙串中的匹配项并自行关闭以获取正确的密码。

您可以将其配置为在每次应用打开时或属性延迟后显示登录。延迟时间也是以秒为单位设置的属性。它还将阻止您的应用程序 UI 使用您的应用程序 Default.png 以启动显示登录所需的几秒钟。这也可以通过属性进行配置。

我很想得到一些反馈,如果有人能告诉我如何做一个类别,那么在 viewControllers 中的额外调用就不需要了,那就太好了!享受!

应用委托:

- (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.
    [self.window makeKeyAndVisible];

    // these calls are all optional
    [[VHLoginManager loginManager] setShouldBlockUIWithSplashOnResume:NO];
    [[VHLoginManager loginManager] setSecondsRequiredToPassBeforeLockDown:1000];
    [[VHLoginManager loginManager] setScreenLockRequirment:VHLMScreenLockDelayed];

    // this is the only required call to run with defaults - always login and block UI with splash while login loads
    [[VHLoginManager loginManager] presentLogin];
}

任何可能在某些时候呈现为模态的 viewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [[VHLoginManager loginManager] registerViewControllerIfModal:self];
}

loginManager 类

标题:

//  VHLoginManager.h
//  Created by Victor Hudson on 5/31/13.
//  Copyright (c) 2013 Victor Hudson. All rights reserved.
//  Use if you like but be nice and leave my name 

#import <Foundation/Foundation.h>
#define VHLMLastCloseDate @"VHLMLastCloseDate"
#define VHLMPassCodeDelay @"VHLMPassCodeDelay"

typedef enum {
    VHLMScreenLockAlways = 0,
    VHLMScreenLockDelayed = 1,
} VHLMScreenLockRequirement;

@interface VHLoginManager : NSObject
@property (nonatomic) BOOL shouldBlockUIWithSplashOnResume;
// defaults to yes so app contents arent visible before the login screen appears
@property (nonatomic) int secondsRequiredToPassBeforeLockDown;
// defaults to 5 minutes (300)

#pragma mark - Class Methods
+ (VHLoginManager *)loginManager;
// returns the singleton login manager

#pragma mark - Manager Methods
- (void) presentLogin;
// will determine if login should be presented an do so if needed
- (void) registerViewControllerIfModal:(UIViewController *)controller;
// any view controllers that are presented modally should call this with self as controller in viewDidLoad - the pupose of this manager is so login shows even over top of modals
- (void) setScreenLockRequirment:(VHLMScreenLockRequirement) requirement;
// deafaults to always if not adjusted
@end

执行:

//  VHLoginManager.m
//  Created by Victor Hudson on 5/31/13.
//  Copyright (c) 2013 Victor Hudson. All rights reserved.
//  Use if you like but be nice and leave my name

#import "VHLoginManager.h"
static VHLoginManager *loginManager = nil;

@interface VHLoginManager ()
@property (nonatomic, strong) UIViewController *currentModalViewController;
@property (nonatomic) VHLMScreenLockRequirement screenLockrequirement;
@end

@implementation VHLoginManager
#pragma mark - Manager Methods
- (void) presentLogin
{
    // NSLog(@"%s", __PRETTY_FUNCTION__);
    if ([[NSUserDefaults standardUserDefaults] integerForKey:VHLMPassCodeDelay] == VHLMScreenLockAlways || [self timeSinceLastClose] >= self.secondsRequiredToPassBeforeLockDown) {
        //NSLog(@"User should see login");
        // determine who the presenting view controller should be
        UIViewController *viewController;
        if (self.currentModalViewController && self.currentModalViewController.presentingViewController != nil) {
            // NSLog(@"We have a modal view controller on top");
            viewController = self.currentModalViewController;
        } else {
            // NSLog(@"We have NO modal view controller on top");
            // get the root view controller of the app
            viewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
        }

//********************************************************************************************************************************************************************************
        // *** This is still tied into my app storyboard and should be made into a viewcontroller with nib to be portable with loginManager for now implement and present your own loginViewController
        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPhone" bundle:nil];
        UINavigationController *navController = [storyboard instantiateViewControllerWithIdentifier:@"appLoginScreen"];
//********************************************************************************************************************************************************************************

        // present the login to user
        [viewController presentViewController:navController animated:NO completion:nil];
    }
}

- (void) setScreenLockRequirment:(VHLMScreenLockRequirement) requirement
{
    _screenLockrequirement = requirement;
    [[NSUserDefaults standardUserDefaults] setInteger:self.screenLockrequirement forKey:VHLMPassCodeDelay];
}

- (void) registerViewControllerIfModal:(UIViewController *)controller
{
    // NSLog(@"%s", __PRETTY_FUNCTION__);
    if (controller.presentingViewController) {
        NSLog(@"Registering a modalViewController");
        self.currentModalViewController = controller;
    }
}

#pragma mark - Private Methods

- (void) timeStampForBackground
{
    // NSLog(@"%s", __PRETTY_FUNCTION__);
    [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:VHLMLastCloseDate];
    [self setDisplaySplashForBackgroundResume];
}

- (int) timeSinceLastClose
{
    return [[NSDate date] timeIntervalSinceDate:[[NSUserDefaults standardUserDefaults] objectForKey:VHLMLastCloseDate]];
}

#pragma mark Splash Screen management
- (void) setDisplaySplashForBackgroundResume
{
    // NSLog(@"%s", __PRETTY_FUNCTION__);
    if (self.shouldBlockUIWithSplashOnResume) {
        // dismiss all keyboards and input views
        UIView *topView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];
        [topView endEditing:YES];

        // Don't show a splash screen if the application is in UIApplicationStateInactive (lock/power button press)
        UIApplication *application = [UIApplication sharedApplication];
        if (application.applicationState == UIApplicationStateBackground) {
            UIImageView *splash = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Default"]];
            splash.frame = application.keyWindow.bounds;
            [application.keyWindow addSubview:splash];
        }
    }
}

- (void) removeSplashScreen
{
    // NSLog(@"%s", __PRETTY_FUNCTION__);
    if (self.shouldBlockUIWithSplashOnResume) { // we should have a splash image up if true
                                                // so remove it
        UIWindow *thewindow = [[UIApplication sharedApplication] keyWindow];
        if ([[thewindow subviews] count] > 1) {
            [NSThread sleepForTimeInterval:1.0];
            [[[thewindow subviews] lastObject] removeFromSuperview];
        }
    }
}
#pragma mark - Class Management

//prevent additional instances
+ (id)allocWithZone:(NSZone *)zone
{
    return [self loginManager];
}

+ (VHLoginManager *)loginManager
{
    if (!loginManager) {
        //Create The singleton
        loginManager = [[super allocWithZone:NULL] init];
    }

    return loginManager;
}

- (id) init
{
    // If we already have an instance of loginManager
    if (loginManager) {
        //Return The Old One
        return loginManager;
    }

    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(timeStampForBackground)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(removeSplashScreen)
                                                     name:UIApplicationDidBecomeActiveNotification
                                                   object:nil];
        self.shouldBlockUIWithSplashOnResume = YES;
        self.secondsRequiredToPassBeforeLockDown = 300;

        if (![[NSUserDefaults standardUserDefaults] integerForKey:VHLMPassCodeDelay]) {
            [self setScreenLockRequirment:VHLMScreenLockAlways];
        }
    }
    return self;
}
@end
4

2 回答 2

1

不久前我遇到了同样的问题,我对这个问题的解决方案是引用当前在您的应用程序委托中呈现的任何模式视图。因此,您可以知道您的标签栏控制器是否已经呈现模态控制器,如果是这种情况,您可以在当前模态视图上呈现您的登录视图。

于 2013-05-30T14:54:02.530 回答
1

我所做的是在我的 appDelegate 中有一个开关。当应用程序启动时,如果用户尚未登录,我创建了登录视图并将其设置为窗口的 rootViewController。当用户成功登录后,我使用动画块将该视图的 alpha 设置为 0,然后创建一个 UITabBarController,填充它,使其成为窗口的 rootViewController(alpha 为 0,然后将其 alpha 设置为 1)。工作得很好。不知道如何用故事板做到这一点。

编辑:现在熟悉故事板。所以你要做的不是使用 Main.storyboard 本身(从 info.plist 中删除它),然后添加一个 LoginViewController 作为视图,并在那里也有你的 UITabbarController - 但没有什么是初始视图控制器。您显然必须命名每个视图,以便您可以在代码中创建它,但要求 Storyboard 创建这样那样的视图控制器

所以在 App Delegate 中,如果已登录,则实例化标签栏控制器并将其添加为根视图控制器。如果用户尚未登录,则创建 LoginView 并将其添加为 rootview 控制器。如果用户确实登录,请在 LoginViewController 上设置一些方法,以便它可以要求委托切换到选项卡栏控制器。

于 2013-05-30T14:49:53.807 回答