Calling ViewController's method that pushes another ViewController from App Delegate - ios

Before I begin, I have searched Stackoverflow for how to do this, and I saw a lot of related posts, but none worked for me and I'm not sure why.
So basically I have a loginViewController, and in it, I have a method that call's GoogleSignIn:
- (void)googleTap:(id)sender
{
[[GIDSignIn sharedInstance] signIn];
}
Now the way GoogleSignIn is set up, the result of that sign in call is handled inside AppDelegate.m
- (void)signIn:(GIDSignIn *)signIn
didSignInForUser:(GIDGoogleUser *)user
withError:(NSError *)error {
// Perform any operations on signed in user here.
if (!error) {
NSString *userId = user.userID; // For client-side use only!
NSString *idToken = user.authentication.idToken; // Safe to send to the server
NSString *name = user.profile.name;
NSString *email = user.profile.email;
NSLog(#"Name: %#, User: %#, Token: %#, Email: %#",name, userId, idToken, email);
// ...
}
}
Inside this AppDelegate method, I want to call a method from my loginViewController:
-(void)onSuccessfulLogin{
NSLog(#"On Successful Login");
[self.navigationController pushViewController:[collectionViewController new] animated:YES];
}
I tried these answers: Calling UIViewController method from app delegate
want to call ViewController's method from appdelegate
and the NSLog is called, but the new ViewController is never pushed...why is that and how can I get that to work?

If this is your app delegate you have no self.navigationController. You probably changed the name of your NavigationController to something like UINavigationController *navigationController = [[UINavigationController alloc] init] You need to set a #property for a nav controller on the delegate class. Then where you initialize the nav controller
UINavigationController *navigationController = [[UINavigationController alloc] init]`
self.navigationController = navigationController// You need this line.
//In your method
[self.navigationController pushViewController:[collectionViewController new] animated:YES];

Declare your onSuccessfulLogin method in YourViewController.h (header file)
In (void)signIn:didSignInForUser:withError: method, put below code at bottom
if ([self.navigationController.viewControllers.lastObject respondsToSelector:#selector(onSuccessfulLogin)]) {
[((YourViewController *)self.navigationController.viewControllers.lastObject) onSuccessfulLogin];
}

I was thinking about this wrong all along. You can move the GDSignInDelegate from AppDelegate to viewcontroller.h.
Then you can simply move the -(void)signIn: didSignInUser: method to your ViewController. And you can call your method from there!

Related

How does a receiver receive a call in Sinch?

I'm looking through the code, specifically the Main View Controller that initiates calls with the call button. Both users must be on this View Controller after inputting their names to be found in a database.
But I'm confused as to how the callee is notified of a call and how it segues into a Calling View Controller that shows that they can answer or hangup.
I know that prepareForSegue sets the call to be whoever called, but I'm still confused with the remaining few lines after that.
So note the last two delegate methods: the first delegate method performs a segue, which makes sense. But what about the second one because I'm confused as to how it segues into call view controller that lets the callee answer or decline.
MainViewController.m
#import "MainViewController.h"
#import "CallViewController.h"
#import <Sinch/Sinch.h>
#interface MainViewController () <SINCallClientDelegate>
#end
#implementation MainViewController
- (id<SINClient>)client {
return [(AppDelegate *)[[UIApplication sharedApplication] delegate] client];
}
- (void)awakeFromNib {
self.client.callClient.delegate = self;
}
- (IBAction)call:(id)sender {
if ([self.destination.text length] > 0 && [self.client isStarted]) {
id<SINCall> call = [self.client.callClient callUserWithId:self.destination.text];
[self performSegueWithIdentifier:#"callView" sender:call];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
CallViewController *callViewController = [segue destinationViewController];
callViewController.call = sender;
}
#pragma mark - SINCallClientDelegate
// Outgoing Call?
- (void)client:(id<SINCallClient>)client didReceiveIncomingCall:(id<SINCall>)call {
[self performSegueWithIdentifier:#"callView" sender:call];
}
// Incoming Call?
- (SINLocalNotification *)client:(id<SINClient>)client localNotificationForIncomingCall:(id<SINCall>)call {
SINLocalNotification *notification = [[SINLocalNotification alloc] init];
notification.alertAction = #"Answer";
notification.alertBody = [NSString stringWithFormat:#"Incoming call from %#", [call remoteUserId]];
return notification;
}
(void)client:(id)client didReceiveIncomingCall:(id)call {
[self performSegueWithIdentifier:#"callView" sender:call];
}
is called when the app is in the foreground and an incoming call is in prgress, it will push the viewcontroller with teh call and since its direction is incoming you will be presented with an answer decline button,
(SINLocalNotification *)client:(id)client localNotificationForIncomingCall:(id)call {
SINLocalNotification *notification = [[SINLocalNotification alloc] init];
notification.alertAction = #"Answer";
notification.alertBody = [NSString stringWithFormat:#"Incoming call from %#", [call remoteUserId]];
return notification;
}
is called when the app is the background and you have enabled push
But what about the second one because I'm confused as to how it segues into call view controller that lets the callee answer or decline
The header explains to you what happens:
The return value will be used by SINCallClient to schedule ... a UILocalNotification. That UILocalNotification, when triggered and taken action upon by the user, is supposed to be used in conjunction with
-[SINClient relayLocalNotification:].

GADInterstitial presentFromRootViewController causes current View Controller to close

I'm attempting to show a GADInterstitial ad when a user of my app clicks a certain button to go to a certain ViewController. In the new ViewController's viewDidLoad method I check to see if the GADInterstitial ad is ready, and if it is I attempt to present it. The problem is, when I dismiss the ad I notice that the ViewController that presented the ad is no longer there and instead it has sent me back to the ViewController that launched the ViewController that presented the ad.
Here's my code:
- (void)viewDidLoad {
[super viewDidLoad];
BTRInterstitialHelper *helper = [BTRInterstitialHelper sharedHelper];
if([helper isInterstitialReady]) {
[helper.interstitial presentFromRootViewController:self];
}
My helper method just looks like this:
-(BOOL)isInterstitialReady {
if ([self.interstitial isReady]) {
return YES;
} else {
return NO;
}
}
I do notice thought that when I present the ad, this message shows up in the logs:
Presenting view controllers on detached view controllers is discouraged BTRIndividualLocationViewController: 0x7fd63bcd9c00.
Same problem occurs if I try to present it in viewWillAppear.
What am I doing wrong here?
EDIT:
I have also tried the following, to try to pass in my app's actual rootViewController:
UINavigationController *nav=(UINavigationController *)((BTRAppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;
UIViewController *rootController=(UIViewController *)[nav.viewControllers objectAtIndex:0];
[helper.interstitial presentFromRootViewController:rootController];
Still the same result though, except that message doesn't pop up in the logs.
VC2.h
#import "GADInterstitial.h"
#import "GADInterstitialDelegate.h"
set delegate
#interface VC2 : UIViewController <GADInterstitialDelegate>
and a property
#property(nonatomic, strong) GADInterstitial *interstitial;
VC2.m
in viewdidappear
self.interstitial = [[GADInterstitial alloc] init];
self.interstitial.adUnitID = #"ca-app-pub-yourNumber";
self.interstitial.delegate = self;
GADRequest *request = [GADRequest request];
for test ads in simulator
request.testDevices = #[ GAD_SIMULATOR_ID ];
coud also add location and keywords in the request (optional)
then do request
[self.interstitial loadRequest:request];
listen to delegate
-(void)interstitialDidReceiveAd:(GADInterstitial *)ad {
if ([self.interstitial isReady]) {
[self.interstitial presentFromRootViewController:self];
}
}
If you want to show the interstitial only when pressed from a certain button use NSUserdefaults.
In VC1 when button pressed
[[NSUserDefaults standardUserDefaults] setValue:#"yes" forKey:#"showInterstitial"];
[[NSUserDefaults standardUserDefaults] synchronize];
and wrap all the code for the interstitial in VC2 in viewdidappear in:
if ([[[NSUserDefaults standardUserDefaults] valueForKey:#"showInterstitial"] isEqualToString:#"yes"]) {
}
and add to -(void)interstitialDidReceiveAd:(GADInterstitial *)ad {
[[NSUserDefaults standardUserDefaults] setValue:#"no" forKey:#"showInterstitial"];
[[NSUserDefaults standardUserDefaults] synchronize];
p.s. you could also use a Bool for NSUserdefaults of course, I do use strings caus I had some errors with Bools, but Bools would be more correct I guess
A very similar scenario for me in 2018.
My current ViewControllers look like this:
ViewController 1
-> ViewController 2 (via presentViewController:animated:completion)
-> GADOInterstitialViewController (presentFromRootViewController:)
When GADOInterstitialViewController is dismissed via rewardBasedVideoAdDidClose:, ViewController 2 is instantly dismissed also.
A simple solution to this issue that I have figured out. Simply override ViewController 2 function dismissViewControllerAnimated like this:
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
if(self.presentedViewController != nil){
[self.presentedViewController dismissViewControllerAnimated:flag completion:completion];
} else {
[super dismissViewControllerAnimated:flag completion:completion];
}
}
This code block checks if ViewController 2 has a presented view controller and dismisses it, if not then ViewController 2 gets dismissed.
This works perfectly in my case :)

pass core data item between view controllers

i am making an app that requires core data to store some information, i have managed to get the app to create the entities and add the information, however i am unable to edit it
i have a list view controller that displays a brief outline of this information and another list view controller which acts as a modal view to allow the user to edit their information.
when the user taps a list item i need the app to show the modal view controller with the selected information loaded from the core data model into the relevant text boxes and when the user clicks save, i need the changes to be saved
at the moment when the user taps the list item the information is not passed into the modal view, NSLog confirms that for me - it tells me the item i tapped in the prepareForSegue: method and is null in the modal view's viewDidLoad:
here's the code:
this is from the MedsList view controller, it displays the information
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
} //selMed declared at top of file as NSManagedObject *selMed;
selMed = [self.meds objectAtIndex:indexPath.row];
NSLog(#"SELECTED MED: %#",[selMed valueForKey:#"name"] );
UIStoryboardSegue *segueString = [NSString stringWithFormat:#"%#",#"editMeds"];
NSLog(#"%#",segueString);
NSLog(#"%# %#", #"MED NAME",[self.meds objectAtIndex:indexPath.row]);
[self performSegueWithIdentifier:#"editMeds" sender:indexPath];
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue isEqual: #"editMeds"]) {
NSLog(#"%# %#", #"SELMED AT PREPARE FOR SEGUE: ",selMed);
UINavigationController *nav = segue.destinationViewController;
MedsEditViewController *dest = (MedsEditViewController *)nav.topViewController;
dest.med = selMed;
dest.chName = [selMed valueForKey:#"name"];//chosen med's name
}
}
}
here is MedsEdit.m
#import "MedsEditViewController.h"
#import "fibroMappAppDelegate.h"
#import <CoreData/CoreData.h>
#interface MedsEditViewController ()
{
NSManagedObjectContext *context;
}
#end
#implementation MedsEditViewController
#synthesize tbName;
#synthesize tbDose;
#synthesize tbMaxDose;
#synthesize tbType;
#synthesize med;
double dose;
double maxDose;
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%# %#", #"recieved MEDICATION", self.med);
NSLog(#"%# %#", #"recieved Name", _chName);
fibroMappAppDelegate *delegate = [[UIApplication sharedApplication]delegate];
context = [delegate managedObjectContext];
tbName.text = _chName;
}
chName is declared in the .h file as a NSString
could somebody please help me work out what i have done wrong? and how to fix it..... i've spent a lot of time on this on and off in the last few months
here is a copy of the log after prepare for segue starts
2013-11-07 14:21:15.475 fibromapp[660:70b] SELECTED MED: med1
2013-11-07 14:21:15.476 fibromapp[660:70b] editMeds
2013-11-07 14:21:15.477 fibromapp[660:70b] MED NAME (entity: Medication; id: 0x8a6b0a0 ;
data: {
dose = 3;
lastTaken = nil;
maxDose = 5;
name = med1;
type = Miligrams;
})
2013-11-07 14:21:15.479 fibromapp[660:70b] Warning: Attempt to present on while a presentation is in progress!
---------------------the following is in the MedsEdit viewDidLoad-------------------------
2013-11-07 14:21:15.497 fibromapp[660:70b] recieved MEDICATION (null)
2013-11-07 14:21:15.498 fibromapp[660:70b] recieved Name (null)
[segue isEqual: #"editMeds"] should be [segue.identifier isEqualToString: #"editMeds"] for a kick off...
Have you ever tried to set the MedsEditViewController as your segue.destinationViewController?
First of all, where in the code you posted are you actually setting the med on the destination controller? I see
dest.med = selMed
but it is commented out. If you never set it, then it won't ever be available. Could you please reformat and post the complete logs? It's not clear to me if your code ever reached the SELMED AT PREPARE FOR SEGUE log.
Assuming you did not mean to comment that out, or you received an error I think it may be here - UINavigationController *nav = segue.destinationViewController - unless your MedsEditViewController is actually a UINavigationController I suspect that the segue.destinationViewController is the navigation controller, which in turn is going to present your MedsEditViewController. Double check the documentiation for UINavigationController, but off the top of my head I think you need this:
dest.topViewController.med = selMed

ivar for view controller in appdelegate.m

edit 1
I am adding some code to indicate its state after I have tried to work with codeInOrange's answer so far which so far behaves like my code originally behaved, that is sample link shows up at first in the text field and can be altered by the user, but when the user returns to the VC, any new link text has been replaced by the original sample link. My reason for posting this additional code is to try to reconnect with codeInOrange's promising answer because I am misunderstanding the logical flow of his original suggestions and his later comments.
In the current Storyboard I am leaving the Text field and the Placeholder Text empty because the sample link seems to be adequately supplied by the viewDidLoad method below.
- (void)viewDidLoad
{
[super viewDidLoad];
self.urlNameInput.text = #"sample http";
// Do any additional setup after loading the view, typically from a nib.
self.urlNameInput.clearButtonMode = UITextFieldViewModeAlways;
self.urlNameInput.clearsOnBeginEditing = NO;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if (textField == self.urlNameInput) {
[textField resignFirstResponder];
[self processPbn];
}
return YES;
}
- (void)viewDidAppear:(BOOL)animated
{
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
// self.urlNameInput.text = appDelegate.stringForTextField;
appDelegate.stringForTextField = self.urlNameInput.text;
}
- (void) processPbn
{
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:self.urlNameInput.text] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[NSURLConnection sendAsynchronousRequest:theRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *connection, NSData *data, NSError *error)
{
// lots of detail code has been elided in this method
self.iboard = 0;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:toMatch options:NSRegularExpressionDotMatchesLineSeparators error:&error];
for (NSTextCheckingResult* board in [regex matchesInString:string options:NSRegularExpressionDotMatchesLineSeparators range:NSMakeRange(0, [string length])])
{
if (self.iboard>0) {
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
appDelegate.stringForTextField = self.urlNameInput.text;
}
}];
}
edit 1
edit 0
I do not want to preserve the text between application shutdowns and launches, so the answer using NSUserDefaults is not quite what I need.
Also, it appears from my trials that the solution suggested by Michael Dautermann which suggests either putting my intialization text in viewDidLoad or in the Xib or Storyboard, does not work because the text always returns to its initial value upon return to the VC (likely because the viewDidLoad method is triggered), so I think I do need to create an ivar in my AppDelegate.m as I asked in my original question, and not in my ViewController.m viewDidLoad, to get the desired result, apparently. Perhaps it would be easier to create a B00L ivar in AppDelegate.m which is a flag that tells whether original text or current text is desired. But I cannot figure out how to do that, either. So, please consider this edit in your answer.
edit 0
My AppDelegate.m contains the following code.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
BDViewController *vc = [sb instantiateInitialViewController];
self.viewController = (id)vc;
}
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
In the VC I want an ivar, an NSString, to be set at launch so that it can be the example text in my UITextField. Later I want that UITextField to be adjusted to an new value when the user supplies valid text into a UITextField.
Currently in my VC.h, the text field is declared and is synthesized in VC.m as follows .
#property (nonatomic, strong) UITextField *urlNameInput;
#synthesize urlNameInput;
I have tried putting the following code into didFinishLaunchingWithOptions: but do not see the desired text when I run the app.
self.viewController.urlNameInput.text = #"example http";
How can I programmatically accomplish my goal of initializing the UITextField?
Put that "urlNameInput.text =" bit into your view controller's "viewDidLoad" method, instead of the "didFinishLaunchingWithOptions:" method (where your view controller is not likely yet instantiated.
Even better than that, just set the initial text in your storyboard or XIB file and then you can programmatically adjust it later on.
Ok I'm having a hard time understanding what you're trying to do but creating an NSString iVar on your app delegate (although there are many other solutions) will allow you to set the textfield text to whatever you want when that VC comes back on the screen.
In your AppDelegate.h
#property (strong, nonatomic) NSString *stringForTextField;
This way you can initialize your textfield text when the view is loaded (viewDidLoad)
self.urlNameInput.text = #"example http";
then whenever that text value needs to be changed (say for example in textFieldShouldReturn in the other view controller. I'm assuming you have another textfield based on your question)
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
appDelegate.stringForTextField = textField.text;
and in viewDidAppear in the VC with the textField set that value.
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
self.urlNameInput.text = appDelegate.stringForTextField;
probably not the best way to go about it, but it will work.
EDIT
Ok in viewDidAppear:
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if ([appDelegate.stringForTextField isEqualToString:#""]){
self.urlNameInput.text = #"example http";
} else {
self.urlNameInput.text = appDelegate.stringForTextField;
}
Now what this will do is if the user goes to another view controller, and comes back, the text field text will be what the user last entered, unless in another view controller, stringForTextField is updated to some new value. If this still does not work, look at your processPbn method to make sure the if clause is entered and that value is set. Otherwise it will always say "example http"
I'm not understanding why it's unimportant to persist the previous value across application launches, especially when it is beneficial to your users to have it persist only during the application lifecycle. codeInOrange's answer works by adding a property to the AppDelegate. The only thing I would add to his answer is a conditional if() . If you want to do it without any properties, you can still use the NSUserDefaults.
At the top of your ViewController.m file
#define SetHTTPString(string) [[NSUserDefaults standardUserDefaults]setObject:string forKey:#"HTTPString"] //no semicolon
#define GetHTTPString() [[NSUserDefaults standardUserDefaults]objectForKey:#"HTTPString"] //no semicolon
Then, in viewWillAppear...
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSString *httpString = GetHTTPString();
if (httpString) {
self.urlNameInput.text = httpString;
} else {
self.urlNameInput.text = #"Example http";
}
}
Next, in the method where the user enters text and "enters it"
...methodToEnterURL {
SetHTTPString(self.urlNameInput.text);
}
Finally, if you absolutely want to destroy the value in the NSUserDefaults, add this method to your AppDelegate's didEnterBackground method:
[[NSUserDefaults standardUserDefaults]setObject:#"Example http" forKey:#"HTTPString"];
This is a perfect usage for NSUserDefaults. When the user enters something just store it in NSUserDefaults. Check to see if the NSUserDefaults entry is blank on each launch, and if so just display the original string.
Save the text in NSUserDefaults with something like:
[[NSUserDefaults standardUserDefaults]setObject:#"yourNewString" forKey:#"userTextEntered"];
And then just check it on each launch:
if([[NSUserDefaults standardUserDefaults] objectForKey:#"userTextEntered"])
{
//display the user entered string
}
else
{
//display the string that you want to display prior to text being entered
}
However, this solution is only necessary if you want to preserve the text between application shutdowns and launches.
The code below assumes that the Storyboard contains the initial, default Text (of at least 3 characters length).
I really appreciated the help I got from others, especially from codeInOrange . I actually believe this is codeInOrange's solution, but I was never able to quite put his pieces together until I finally stumbled upon this one.
I hope this is really a valid answer, and apologize to all if I did not state my question clearly or if I mistook others' valid answers, especially codeInOrange.
//
// ViewController.m
// StickyPlaceholder
//
//
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize textInput;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if (!appDelegate.stringForTextField)appDelegate.stringForTextField = self.textInput.text ;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if (textField == self.textInput) {
[textField resignFirstResponder];
// next line is dummy processing
if (self.textInput.text.length>2)appDelegate.stringForTextField = self.textInput.text;
}
return YES;
}
- (void)viewDidAppear:(BOOL)animated
{
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
self.textInput.text = appDelegate.stringForTextField;
}
#end

Create a log in screen on iOS that will always show on top of all views

EDIT: Check below for solution.
I am working on a login screen for my app and I have it working for the most part except for a few edge cases. Ive set things up so that I have a segue from my UITabBar in the story board that I trigger in the app delegate applicationDidBecomeActive: method. As I said it works fine on all but one edge case I've found so far.
My app uses some modal view controllers, some of which are UIActivityViewControllers if that makes a difference, to enter and edit some core data entities. If one of these modal view controllers is opened when the app goes to the background, it will always show up when the app is reopened and my login doesn't show. I get the following console msg
Warning: Attempt to present <UINavigationController: 0x1d51e320> on <MPTabBarViewController: 0x1d5b4810> which is already presenting <UIActivityViewController: 0x1e38fc40>
Here is my code
- (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];
}
}
I understand exactly what this msg is telling me, the tab bar is already presenting a modal controller so it can't present another one. So my question is this, Is there a better way to implement this so that the login will always show, even over top of the modal views?
Okay here is my current solution
as suggested by Bartu and requested to be shared by Shawn
I have a working singleton loginManager class that requires 1 call in app delegate and 1 call in any view controller that could be called to present as modal. I was unable to figure out how to do this as suggested with a ViewController category, but hey a few includes and method calls aren't so bad. I included it in App-Prefix.pch, so its available everywhere. It is written for ARC, so if you like managing your own memory you'll need to modify the singleton for that. The last caveat, at current you will need to roll your own viewController for the login screen. Just look for the commented section in the implementation with all the stars, and put your own view controller there. Mine is still in my app storyboard, its basically 4 digit pin that checks for a match in the keychain and dismisses itself for the correct pin. I may pull that out of my storyboard and nib it so it could be packaged with the loginManager and let it become my first gitHub project at some future date though.
You can configure it to display login for every time the app opens or after a delay with properties. The delay time is also a property set in seconds. It will also block out your apps UI for the few seconds it takes to get the login displayed with a splash using your apps Default.png. This is also configurable with a property.
I would love to get some feedback on this, and if anyone can tell me how to do a category so the extra call in viewControllers is not needed that would be great! Enjoy!
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];
}
Any viewController that may presented as modal at some point
- (void)viewDidLoad
{
[super viewDidLoad];
[[VHLoginManager loginManager] registerViewControllerIfModal:self];
}
The loginManager class
header:
// 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
implementation:
// 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
I had the same problem a short time ago, my solution for this problem is to have a reference to any modal view which is currently presented in your app delegate. So, you can know if your tab bar controller is already presenting a modal controller and if it's the case, you can present your login view over your current modal view.
What I did was to have a switch in my appDelegate. when the app started, if the user had not logged in, I created the login view and make it the window's rootViewController. When the user successfully logged in, I used an animation block to set that view's alpha to 0, then created a UITabBarController, populated it, made it the window's rootViewController (with an alpha of 0, then animated it's alpha to 1). Worked really well. Not sure how to do this with storyboards though.
EDIT: now getting familiar with storyboards. So what you would do is not use the Main.storyboard per se (remove it from info.plist), then add a LoginViewController as a view, and have your UITabbarController there too - but nothing is the initial view controller. You obviously have to name each view so you can create it in code, but asking the Storyboard to create such and such a view controller
So in App Delegate, if logged in, instantiate the tab bar controller and add it as the root view controller. If the user has not logged in, create the LoginView and add it as rootview controller. If the user does login, have some method on the LoginViewController so it can ask the delegate to switch to the tab bar controller.

Resources