How to know which viewcontroller is presented on app start - ios

The situation is :
App is on background
The user click on icon app
App open and show the view controller where we were before apps entered background last time.
I'd like to know which view controller is about to be presented. I'm looking for something like :
- (void)applicationDidBecomeActive:(UIApplication *)application {
if ([self.window.viewControllerOnScreen isKindOfClass:[HomeViewController class]]) {
//do sthg
}
}
Because in case, it's the home view controller (embed in a navigation controller and i use storyboards) i would perform some reload method.

[[self.navigationController viewControllers] lastObject];
The first part will give you an array of all of the viewControllers on the stack, with the last object being the one that is currently display. Check its class type to see is it the homeViewController

As per this link Each object receive a UIApplicationDidEnterBackgroundNotification notification when the app goes in background. Similarly UIApplicationWillEnterForegroundNotification gets fired when app comes in foreground.
so you can use it to keep track of which view controller is opened when app enters foreground
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(appEnteredForeground:)
name:UIApplicationDidEnterForegroundNotification
object:nil];

Try this
- (UIViewController *)topViewController{
return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController *)topViewController:(UIViewController *)rootViewController
{
if (rootViewController.presentedViewController == nil) {
return rootViewController;
}
if ([rootViewController.presentedViewController isMemberOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [self topViewController:lastViewController];
}
UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
return [self topViewController:presentedViewController];
}

I have done this for getting the current viewController
if (![[appDelegate.rootNavController topViewController] isMemberOfClass:NSClassFromString(#"LGChatViewController")]) {}

self.tabBarController.viewControllers = [NSArray arrayWithObjects: [self LoadAccount], [self LoadContacts], [self LoadPhoneLine], [self LoadSettings], nil];
if you use tab bar then you set like this and show first account and go on......

Related

Presented view controller gets dismissed if i move my application to background and then come to foreground in iOS

I had a requirement to cover the screen with black layout when the user moves the application to the background applicationDidEnterBackground in order to maintain the privacy of some sensitive datas on the screen. So for this i made use of AppDelegate function to present with a black color in background and then remove that by dismissing it when comes to foreground applicationDidEnterBackground. The code is:
#import "AppDelegate.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
UIViewController *blankViewController = [UIViewController new];
blankViewController.view.backgroundColor = [UIColor blackColor];
[self.window makeKeyAndVisible];
[self.window.rootViewController presentViewController:blankViewController animated:NO completion:NULL];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[self.window.rootViewController dismissViewControllerAnimated:false completion:nil];
}
#end
Now in my application everything is working fine but in one screen i am presenting a ViewContollerB on a button click by using : [self presentViewController:webview animated:YES completion:nil]; The application as usually gets covered with black color when moving to background but when i take the application to foreground after this, then the presented ViewContollerB also gets dismissed . How to prevent my presented ViewController to get dismissed once coming from background?
Create a new 'UIViewController' called OverLayViewController and load it in
applicationDidEnterBackground method
- (void)applicationDidEnterBackground:(UIApplication *)application {
OverLayViewController *blankViewController = [OverLayViewController new];
blankViewController.view.backgroundColor = [UIColor blackColor];
[self.window makeKeyAndVisible];
UIViewController *rvc = self.window.rootViewController;
UIViewController *pvc = rvc.presentedViewController; // you may need to loop through presentedViewControllers if you have more than one
if(pvc != nil) {
[pvc presentViewController: blankViewController animated: NO completion:nil];
}
else{
[self.window.rootViewController presentViewController: blankViewController animated: NO completion:nil];
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
UIViewController *test = [self topViewController];
if ([test isKindOfClass:[OverLayViewController class]]) {
[test dismissViewControllerAnimated:false completion:nil];
}
}
- (UIViewController*)topViewController {
return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
-(UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
if ([rootViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController*)rootViewController;
return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
} else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*)rootViewController;
return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
} else if (rootViewController.presentedViewController) {
UIViewController* presentedViewController = rootViewController.presentedViewController;
return [self topViewControllerWithRootViewController:presentedViewController];
} else {
return rootViewController;
}
}
In this case, dismissViewContoller code wont apply for your webview.
You are trying to do the following: rootviewcontroller A presents a blank viewcontroller B. Your app goes into background, and you present viewcontroller C from there. Now B closes as A is presenting both B and C, and that is not allowed.
You will need to check if your rootviewcontroller is presenting any viewcontrollers, and if those are presenting any others recursively. You can check for those by using the property presentedViewController on a viewcontroller.
Personally, I would (in a base viewcontroller class that all viewcontrollers inherit from) keep a variable that checks if this one is visible (by keeping track of viewDidAppear and viewDidDisappear). If this is the one that is visible, you add a blank view on top.
Answer in Swift because I can't be trusted with Objective-C without an editor:
class BaseViewController: UIViewController {
var appeared = false
func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated: animated)
appeared = true
}
func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated: animated)
appeared = false
}
}
Then you need to fire a notification from the AppDelegate that is caught in this viewcontroller, and then show a blank view on top.

UISplitViewController with nav stacks in both master and detail - how to remove a VC from one of the stacks?

I am debugging legacy code which is always fun. The old code tried to mock the splitView delegate methods, causing all sorts of issues - mainly crashing: on a Plus device in Portrait, rotating to landscape caused the crash - if there was no detail view set, old code attempted to create one in a dodgy hack and it was just useless...
My app is UISplitViewController based, where I have a navigation stack in both master and detail sides of the splitView.
By reading though SO and using this example and was able to implement UISplitViewController delegate methods and everything is working correctly in regards to rotation, and showing the correct master/detail views when appropriate. Here is my implementation: (apologies for wall of code snippets)
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController {
if ([secondaryViewController isKindOfClass:[UINavigationController class]]
&& [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[AECourseHTMLTableViewController class]]
&& ([(AECourseHTMLTableViewController *)[(UINavigationController *)secondaryViewController topViewController] htmlContentEntry] == nil)) {
// If the detail controller doesn't have an item, display the primary view controller instead
return YES;
}
return NO;
}
And the other splitView delegate method - see comments in code for where I'm stuck.
- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController {
// If detail view already exists
if ([primaryViewController isKindOfClass:[UINavigationController class]]) {
for (UIViewController *controller in [(UINavigationController *)primaryViewController viewControllers]) {
if ([controller isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)controller visibleViewController] isKindOfClass:[AECourseHTMLTableViewController class]]) {
return controller;
}
}
}
// Create detail view
UINavigationController *navController = [self.storyboard instantiateViewControllerWithIdentifier:#"CourseHTMLNav"];
if ([navController.viewControllers.firstObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
AECourseHTMLTableViewController *courseViewController = navController.viewControllers.firstObject;
[self configureViewController:courseViewController entry:self.contentSection.sections[0] indexPath:courseViewController.currentIndexPath];
}
// Enable back button
UIViewController *controller = [navController visibleViewController];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
if (!self.splitViewController.isCollapsed) {
UINavigationController *navController = self.splitViewController.viewControllers.firstObject;
AEContentMenuTableViewController *contentMenuVC = navController.viewControllers.firstObject; // This controller needs to be master in Landscape
NSMutableArray<UIViewController *> *controllers = [navController.viewControllers mutableCopy]; // Contains 3 controllers, first needs removed
NSMutableArray *toDelete = [NSMutableArray new];
for (UIViewController *viewController in controllers)
if ([viewController isKindOfClass:[contentMenuVC class]] || [viewController isKindOfClass:[AECourseHTMLTableViewController class]]) {
[toDelete addObject:viewController]; // Remove first VC, so master should become AEContentMenuVC?
break;
}
// Remove the object
[controllers removeObjectsInArray:toDelete];
// Set viewControllers
navController.viewControllers = controllers;
}
return navController;
}
AECourseHTMLTableViewController has next/prev buttons to select the next row in the tableview of the tableview menu class class (AEContentMenuTableViewController). I have a delegate function which can tell me the current indexPath in which AECourseHTML... is using from AEContentMenu..., and when calling it, it selects the menu tableview row and instantiates a new AECourseHTML... and pushes it.
This is where I'm stuck. In Portrait, pressing next/prev is fine, it selects the correct row and works as expected. But once I rotate the device, both master and detail views show the detail view. I can press "Back" on the master view, and it takes me to the correct AEContentMenu... class. As noted in the code snippet comments, I need to remove a ViewController from the master stack (the first object actually), and AEContentMenu... should become the first object of that stack - so when rotating, that should be the master view.
Apologies for such a long post, I've been banging my head with this for weeks now and I want to include as much info as possible in this question. Thanks in advance.
I found a solution which works well for my use cases. It may not be the cleanest code, but I'm happy with what I've got.
splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:
remains unchanged. I have updated my splitViewController:separateSecondaryViewControllerFromPrimaryViewController: delegate method with the solution. Any feedback is welcome.
- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController {
// If detail view already exists
if ([primaryViewController isKindOfClass:[UINavigationController class]]) {
for (UIViewController *controller in [(UINavigationController *)primaryViewController viewControllers]) {
if ([controller isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)controller visibleViewController] isKindOfClass:[AECourseHTMLTableViewController class]]) {
return controller;
}
}
}
// Return CourseVC
UINavigationController *navController = splitViewController.viewControllers.firstObject;
UIViewController *viewController;
for (viewController in navController.viewControllers) {
if ([navController.viewControllers.lastObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
return viewController;
} else {
// Create detail view
UINavigationController *navController = [self.storyboard instantiateViewControllerWithIdentifier:#"CourseHTMLNav"];
if ([navController.viewControllers.firstObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
// Enable back button
UIViewController *controller = [navController visibleViewController];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
AECourseHTMLTableViewController *courseViewController = navController.viewControllers.firstObject;
// If next/prev has been tapped, configure current ContentHTML
if (self.currentContentHTML) {
[self configureViewController:courseViewController entry:self.currentContentHTML indexPath:courseViewController.currentIndexPath];
} else {
// Create new ContentHTML from first row of AEContentMenuVC
[self configureViewController:courseViewController entry:self.contentSection.sections[0] indexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
}
return navController;
}
}
}
return navController;
}
Your top if statement should return nil. Since you were returning the nested navigation controller you were missing out on the default behaviour of popping the master navigation's top controller which is required so it can then be placed on the right.
The default behaviour will find that nested nav controller and pop it. However the reason you still need to search for it yourself is if it isn't there then you need to load the detail nav from the storyboard as you have done.

Present the previously active ViewController from the Login ViewController

I have 4 view controllers: loginVC -> homeVC -> aVC -> bVC.
Let's assume the user is currently at bVC view controller and decided to switch to another app. When the user switch back to my app, I want to present the loginVC to authenticate the user. Once authenticated, the previous bVC view controller is to be presented to the user to continue whatever he/she was doing. I'm not using Navigation Controller in my project.
In applicationDidBecomeActive: method, I was able to present the loginVC view controller, but once the user is authenticated, what do I do present the bVC view controller? I assume bVC is still on the stack when the app resign from active?
// AppDelegate.m
-(void) applicationDidBecomeActive: (UIApplication*) application {
NSString *storyboardId = #"LoginIdentifier";
self.window.rootViewController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:storyboardId];
}
// loginVC.m
-(void)authenticateSuccessful {
// This doesn't do anything... as I want to present bVC
[self dismissViewControllerAnimated:YES completion:nil];
}
1 Instantiate and show your LoginVC as modal one
2 Dismiss it when a user is authenticated
//AppDelegate.m
- (UIViewController *)visibleViewController:(UIViewController *)rootViewController
{
if (rootViewController.presentedViewController == nil)
{
return rootViewController;
}
if ([rootViewController.presentedViewController isKindOfClass: [UINavigationController class]])
{
UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [self visibleViewController:lastViewController];
}
if ([rootViewController.presentedViewController isKindOfClass:[UITabBarController class]])
{
UITabBarController *tabBarController = (UITabBarController *)rootViewController.presentedViewController;
UIViewController *selectedViewController = tabBarController.selectedViewController;
return [self visibleViewController:selectedViewController];
}
UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
return [self visibleViewController:presentedViewController];
}
-(void) applicationDidBecomeActive: (UIApplication*) application {
NSString *storyboardId = #"LoginIdentifier";
UIViewController *lvc = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:storyboardId];
// I.e show it modally instead of putting to rootViewController
UIViewController *visibleVC = [self visibleViewController:self.window.rootViewController];
[visibleVC presentViewController:lvc animated:NO completion:NULL];
}
// loginVC.m
-(void)authenticateSuccessful {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
I believe what you need to do is to present (or Modal) a temporary viewController over the current View controller (which is bVC in this case) when the application becomes active.
When a viewcontroller is presented, the object or your bVC is not destroyed and hence you could simply dismiss the loginView once the user is authenticated.
[[NSNotificationCenter defaultCenter]addObserver:self
selector:#selector(yourMethod)
name:UIApplicationDidBecomeActiveNotification
object:nil];
Adding a notification like this could be helpful if you need to present a LoginView from any requiredView Controller
Please refer the documentation on PresentingView controllers
You can do that by presenting your loginViewController as a modal one, as said before, but I think that this 'feature' will make your users to go away of your app, because they want the login part done once in their user's life (at their first use of your app).
If you really need the user to be logged in your app (or server) every time he comes back to your app, I advise you to chose to store the user's login informations in an NSUserDefaults.
Keep in mind that the user wants things to be done as seamlessly as possible, they really don't want to type their login/password every time they just go back to your app :)

Issues presenting UIActivityViewController

I want to give a share screen throughout my app. I am using UIActivityViewController for that purpose. The problem is, as per your location in the app, the current root view controller can be of kind UINavigationController(Case1) or UIViewController(Case2).
I can present UIActivityViewController using
[viewController presentViewController:viewController animated:YES completion:nil];
But I have to get currently visible UIViewController of UINavigationController in (Case1), and root view controller itself in (Case2).
But how to detect which kind of root view controller is present & code accordingly?
Thanks.
Try:
UIViewController *root = [[[[[UIApplication sharedApplication] keyWindow] subviews] objectAtIndex:0] nextResponder];
and then:
if ([root isKindOfClass:[UINavigationController class]]) {
// Navigation Controller
} else {
// The other one
}
that should tell you which one is presented.
Use the above method to detect for a UINavigationControler;
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
//If this will be called it is a navigation controller
}
use performselector to start and stop rorating activity indicater
Here AI is activity indicater variable name
[self performSelector:#selector(animateAI) withObject:self afterDelay:0.1];
[self performSelector:#selector(stopAI) withObject:self afterDelay:0.9];
-(void)animateAI
{
[AI startAnimating];
}
-(void)stopAI
{
[AI stopAnimating];
}

call popToRootViewController from another tab

I have tab bar with navigation controller app using storyboard ,
my purpose is to press a button in tab3 and in the background I want tab1 to "popToRootViewController"
the button in tab3 viewcontroller:
- (IBAction)Action:(id)sender {
vc1 * first = [[vc1 alloc]init];
[first performSelector:#selector(popToRootViewController) withObject:Nil];
}
the code in the tab1 viewcontroller
-(void)popToRootViewController{
[self.navigationController popToRootViewControllerAnimated:NO];
NSLog(#"popToRootViewController");
}
I get the popToRootViewController in logs, but the action didn't perform.
that solve the problem:
- (IBAction)Action:(id)sender {
[[self.tabBarController.viewControllers objectAtIndex:0]popToRootViewControllerAnimated:NO];
}
The way you are doing it:
vc1 * first = [[vc1 alloc]init];
[first performSelector:#selector(popToRootViewController) withObject:Nil];
is not correct. Indeed, you are creating a whole new controller here, completely independent from your existing controllers and not belonging to any navigation controller. For this reason, self.navigationController is nil in popToRootViewController.
You might try doing something like:
//-- this will give you the left-most controller in your tab bar controller
vc1 * first = [self.tabBarController.viewControllers objectAtIndex:0];
[first performSelector:#selector(popToRootViewController) withObject:Nil];
Bind TabBar with tabBarViewController-
In tabBarViewController.m
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
NSArray *array = [tabBarController viewControllers];
if([[array objectAtIndex:tabBarController.selectedIndex] isKindOfClass:[UINavigationController class]])
[(UINavigationController *)[array objectAtIndex:tabBarController.selectedIndex] popToRootViewControllerAnimated: NO];
}
It worked perfectly for me.
To press a button in tab3 and in the background I want tab1 to "popToRootViewController"
If you want to perform popToRootViewController in tab1 by pressing button in tab3 then i would like to suggest use NSNotificationCenter. For example follow below code:-
In your firstViewController class add the observer of NSNotification
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(yourMethod:)
name:#"popToRootViewControllerNotification" object:nil];
}
-(void)yourMethod:(NSNotification*)not
{
[self.navigationController popToRootViewControllerAnimated:NO];
}
In your ThirdViewController class post the notification in below code:-
- (IBAction)Action:(id)sender {
// vc1 * first = [[vc1 alloc]init];
// [first performSelector:#selector(popToRootViewController) withObject:Nil];
//Post your notification here
[[NSNotificationCenter defaultCenter] postNotificationName:#"popToRootViewControllerNotification" object:nil];
}
If your tab1 and tab2 arein different navigationController, then try this in - (IBAction)action:(id)sender
NSArray *viewControllers = [self.tabbarController viewControllers];
for (UIViewController *viewController in viewControllers) {
if ([viewController isKindOfClass:[vc1 class]]) {
vc1 * first = (vc1 *) viewController;
[first.navigationController popToRootViewControllerAnimated:NO];
}
}

Resources