在iOS上创建一个始终显示在所有视图之上的登录屏幕

时间:2013-05-30 14:43:46

标签: ios login modalviewcontroller

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

我正在为我的应用程序的登录屏幕工作,除了一些边缘情况之外,我在大多数情况下工作。我设置了一些东西,以便我在故事板中的UITabBar中有一个segue,我在app delegate 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类,需要在app delegate中调用1次,在任何视图控制器中调用1次,可以调用它作为模态。我无法弄清楚如何使用ViewController类别建议这样做,但是他们有一些包含和方法调用并不是那么糟糕。我将它包含在App-Prefix.pch中,因此它随处可见。它是为ARC编写的,所以如果你喜欢管理自己的内存,你需要为此修改单例。最后一点需要注意,目前您需要为登录屏幕滚动自己的viewController。只需在所有星星的实现中查找注释部分,并将自己的视图控制器放在那里。我仍然在我的应用程序故事板中,它基本上是4位数的引脚,用于检查钥匙串中的匹配并解除其自身的正确引脚。我可以将它从我的故事板中拉出来并将其弄清楚所以它可以与loginManager打包并让它成为我未来某个日期的第一个gitHub项目。

您可以将其配置为每次打开应用程序或使用属性延迟后显示登录信息。延迟时间也是以秒为单位设置的属性。它还会阻止您的应用程序用户界面,只需几秒钟即可使用您的应用程序Default.png启动登录。这也可以使用属性进行配置。

我希望得到一些反馈,如果有人能告诉我如何做一个类别,那么不需要在viewControllers中额外调用那将是很棒的!享受!

的AppDelegate:

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

2 个答案:

答案 0 :(得分:1)

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

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

因此,在App Delegate中,如果已登录,则实例化选项卡栏控制器并将其添加为根视图控制器。如果用户尚未登录,请创建LoginView并将其添加为rootview控制器。如果用户登录,在LoginViewController上有一些方法,这样它就可以让代表切换到标签栏控制器。

答案 1 :(得分:1)

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