Pushing ViewController from didReceiveLocalNotification in AppDelegate.swift - ios

I am having problem in pushing ViewController from AppDelegate when user pressed the Notification
Below is my code, but this code Crashes because the navigationController is nil
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
var rootViewController = self.window!.rootViewController;
let customDetailsViewController = CustomDetailsViewController();
rootViewController?.navigationController!.pushViewController(customDetailsViewController, animated: true);
}
Any idea? Thank you!!

If your navigation controller is nil, then your rootViewController (the one with the arrow in your Storyboard) is not inside a NavigationController. Can you post a screenshot of the relevant part of your Storyboard?
EDIT:
As you are using RESideController, you'll have a RootViewController not connected to anything in your Storyboard. That RootViewController conforms to ``protocol and you have some code like:
#implementation FASRootViewController
- (void)awakeFromNib
{
self.menuPreferredStatusBarStyle = UIStatusBarStyleLightContent;
self.scaleContentView = NO;
self.scaleMenuView = NO;
self.panGestureEnabled = YES;
self.contentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"contentViewController"];
self.rightMenuViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"rightMenuViewController"];
self.delegate = (id<RESideMenuDelegate>)self.rightMenuViewController;
}
To "launch" your View controllers your need to add a identifier in the Storyboard
Also, this View Controller should be a UINavigationController (not your first "content" view controller)

Related

Access UIViewController from UITabBarController and UINavigationController

I am developing an application based on UITabbar and the view hierarchy as follows.
UITabBarController ----> UINavigationController ----> UIViewController
I have push notification payload which will open specific UIViewController, i can explicitly open UIViewController directly using view controller Storyboard ID, but tabBar and Navbar won't show.
How can I go to specific View Controller and show TabBar and NavController from AppDelegate didReceiveRemoteNotifications.
Thanks!
You have to instantiate all of your VC and set all of them as root of his predecessor :
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let mainStoryboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let yourVC = mainStoryboard.instantiateViewControllerWithIdentifier("YourVC_Identifier");
let yourNavController = mainStoryboard.instantiateViewControllerWithIdentifier("YourNAV_Identifier") as! UINavigationController
let yourTabController = mainStoryboard.instantiateViewControllerWithIdentifier("YourTAB_Identifier") as! UITabBarController
yourNavController.setViewControllers([yourVC], animated: false)
yourTabController.setViewControllers([yourNavController], animated: false)
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = yourTabController
self.window?.makeKeyAndVisible()
return true
}
[[UIApplication sharedApplication] keyWindow] has a property .rootViewController. Presumably thats your tab bar. On this controller, you can set the active tab and switch out view controllers with the .viewControllers property. Now assuming one of these is your UINavigationController that should also have a property .rootViewController. Instantiate from the storyboard and either set the root or push the view controller on top of the navigation controller.
Follow the hierarchy from App Delegate programmatically.
In case your entry point is from the Storyboard, setup a UIWindow in your AppDelegate so you could set the UITabBarController as the following.
//self.tabBarController is you TabBar from Storyboard, or programatically initialized
self.window.rootViewController = self.tabBarController;
Then whenever you have a notification in didReceiveRemoteNotifications sort the notification out by type, and find the view controller:
//Let's say the View Controller being accessed is in the first position of the stack of viewcontroller from UITabBarController & UINavigationController
UINavigationController *navViewController = self.tabBarController.viewControllers.firstObject;
UIViewController *accessedViewController = navViewController.viewcontroller.firstObject;

Can't Call Segue By Identifier from AppDelegate

So I have a segue from ViewController to SecondViewController. This segue is triggered by a UIButton in ViewController and the modally presents SecondViewController. The Segue's Identifier is set ("SegueIdentifier") and I am able to call the segue programatically from within my ViewController.
When I try to do the same in my AppDelegate, I get the error that the compiler can't find a segue with the Identifier I set.
let viewController = ViewController()
viewController.performSegueWithIdentifier("SegueIdentifier", sender: nil)
Again, I literally copied and pasted the performSegueWithIdentifier method call from the aforementioned method in ViewController in which I also call performSegueWithIdentifier for the same segue and it works.
Any ideas?
In my one project I am managing this situation like,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSString *identifier;
BOOL isSaved = [[NSUserDefaults standardUserDefaults] boolForKey:#"loginSaved"];
if (isSaved)
{
identifier=#"home1";
}
else
{
identifier=#"dis1";
}
UIStoryboard * storyboardobj=[UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *screen = [storyboardobj instantiateViewControllerWithIdentifier:identifier];
[self.window setRootViewController:screen];
return YES;
}
It is in objective c and just for reference. If it can help you :)
Update :
In swift something like,
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
var identifier: String = String()
let isSaved = NSUserDefaults.standardUserDefaults().boolForKey("loginsaved")
if isSaved
{
identifier = "home1"
}
else{
identifier = "dis1"
}
let storyboardobj: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let screen: UIViewController = storyboardobj.instantiateViewControllerWithIdentifier(identifier)
self.window!.rootViewController = screen
// Override point for customization after application launch.
return true
}
Hope this will help :)
If you are not trying to show your initial view then do like this:
UIApplication.sharedApplication().keyWindow?.rootViewController.performSegueWithIdentifier("SegueIdentifier", sender: nil)
And also it depends that you segue is set to your RootViewController. Please check that also before you do this.
Segue connection has some info with it like Source Controller, Destination Controller. So while you are calling the performSegue method from ViewController class, it will work because that class has the info of this Segue connection.
While you are calling that same Segue method from App Delegate, it will not work. Because App Delegate class doesn't have the info or definition about that Segue object.
You should also check whether the calling object comes under Navigation Controller or not.
A storyboard segue has to be tied to the specific viewController that you are trying to segue from. So you have to have an instance of the current view controller on the screen. You may use the UIApplication.sharedApplication().keyWindow?.rootViewController
Method to get access to the rootViewController in the window, however if you presented a view controller above the rootViewController and you want to perform the segue on the presented view controller you have to access the rootViewController.presentedViewController. And since you may have multiple presentedViewControllers, what you do is this:
//get the root view controller
var currentViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
//loop over the presented view controllers and until you get the top view controller
while currentViewController.presentedViewController != nil{
currentViewController = currentViewController.presentedViewController
}
//And finally
currentViewController.performSegueWithIdentifier("identifier", sender: nil)

Segue from the MasterViewController in a SplitViewController to another View Controller

I have a SplitViewController with a UITableViewController as the masterViewController and a UIViewController as the detailViewController.
When the user taps a cell, I need to push to a new UITableViewController. So I added a segue from the cell to a UITableViewController. But what happens is the UITableViewController gets added to the masterViewController's stack.
How can I push to a whole new UITableViewController from the masterViewController?
Here is a simple example how I approach such functionality (I created a new Master-Detail Application):
Storyboard:
Notice that the root VC is now a UINavigationController. Therefore AppDelegate must be changed accordingly:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let navCtr = self.window!.rootViewController as UINavigationController
let splitViewController = navCtr.visibleViewController as UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
splitViewController.delegate = self
return true
}
And then finally in MasterViewController add this:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if indexPath.row % 2 == 0 {
self.performSegueWithIdentifier("showDetail", sender: nil)
} else {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
if let let rootCtr = appDelegate.window?.rootViewController {
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let newSplit = storyboard.instantiateViewControllerWithIdentifier("SplitVC") as UISplitViewController
/// Because of Apple's "Split View Controllers cannot be pushed to a Navigation Controller"
let yetAnotherNavCtr = UINavigationController(rootViewController: newSplit)
rootCtr.presentViewController(newSplit, animated: true, completion: nil)
}
}
}
Important notes:
If you create new MasterDetail Application from the template, you have to disconnect the showDetail segue, because it is directly linked to the cell's selected callback. If you want to preserve that functionality as well, simply connect it again not from the cell itself, but from the whole VC. To be able to use it as in my funky didSelect... method that performs the showDetail segue on even rows.
The Split View presentation will work only once - I haven't implemented the whole rootViewController replacement - the lldb will complain saying: Attempt to present UISplitViewController on UINavigationController whose view is not in the window hierarchy! if you'll try to do it for the second time. But that's really up to your requirements for how you want the app to behave.
Name the SplitView Controller in Storyboard "SplitVC" (Storyboard ID) if you want to present the Split View Controller like I am doing in my code.
I got it! First I should mention Michal's answer helped me to get an idea and point me in the right direction so thanks to him.
What I did was simple actually. Before I had a View Controller with a container view embedding the split view controller. I simply went ahead and embedded that view controller in a navigation controller.
Then I added the view controller I want to segue to in the storyboard but no segue attached to it. I'm doing it programatically in the masterViewController's didSelectRowAtIndexPath method.
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
if let rootVC = appDelegate.window?.rootViewController {
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let mapVC = storyboard.instantiateViewControllerWithIdentifier("MapVC") as! UIViewController
rootVC.showViewController(mapVC, sender: nil)
}
I get a reference to the rootViewController which is a navigation controller through the AppDelegate and I push the new view controller I want to it's stack.
That's it! Here's a demo project in case anyone's interested.

How do I set selected tab in UITabBarController using StoryBoard?

How can I switch to some tab in UITabBarController using StoryBoard? I have tried the code below but without success (the tab is not selected):
self.tabBar.selectedIndex = 3;
Honestly I used nib files without StoryBoard and this code above worked fine in
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions
:(NSDictionary *)launchOptions
but now I can't set the tab programatically. Maybe there is another problem that is not connected to selecting the tab. How can I switch tabs?
Grab your instance of UITabBarController then set the selectedViewController property:
yourTabBarController.selectedViewController=[yourTabBarController.viewControllers objectAtIndex:3];//or whichever index you want
Alexander, I think your problem is getting correct instance of your tab bar. If your tab bar is your root view controller, then you can do it like this in your appdelegate if didFinishLoading method:
UITabBarController *tabBar = (UITabBarController *)self.window.rootViewController;
[tabBar setSelectedIndex:3];
Give it a try and tell me the result please.
*Swift Comment - 18 months later if you convert Yanchi's solution into Swift in your appDelegate you'll get the expected result. The Swift translation is:
let tabBar: UITabBarController = self.window?.rootViewController as! UITabBarController
tabBar.selectedIndex = 1
This will work in stroryboard too...
[self.tabBarController setSelectedIndex:3]; with this add UITabBarControllerDelegate in .h
and then use this delegate method
- (BOOL)tabBarController:(UITabBarController *)theTabBarController shouldSelectViewController:(UIViewController *)viewController
{
return (theTabBarController.selectedViewController != viewController);
}
Swift 4.1
In your TabBarViewController class, you can add this key line
self.selectedIndex = 0
You can achieve this also using storyboards only. Ctrl-drag from the tabBarController to your ViewController(s), and select 'Relationship Segue, View controllers'. The first ViewController you select will be the default/initial ViewController.
I'm using IBInspected in LSwift:
extension UITabBarController {
#IBInspectable var selected_index: Int {
get {
return selectedIndex
}
set(index) {
selectedIndex = index
}
}
}
Swift 5.3
Let tabBar be an instance of UITabBarController then :
tabBar.selectedViewController = tabBar.viewControllers![2]
Note:
Instead of 2, put your desired viewController's index
Here is what I do (Swift 5.x):
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let tabBarController = self.window?.rootViewController as? UITabBarController {
if let viewControllers: [UIViewController] = tabBarController.viewControllers {
tabBarController.selectedIndex = viewControllers.count-1
}
}
return true
}
You can also set the default tab in "User Defined Runtime Attributes" using storyboard.
Select your Tab Bar Controller from storyboard, in the right pane select Identity Inspector and add an attribute:
Key Path : selectedIndex
Type : Number
Value : 2 ( whatever number you want )

Perform Segue on ViewDidLoad

In iOS 5 I have a Storyboard with a modal view controller, that I would like to display if its the user's first time in the app, after that I would like to skip this view controller.
I set an NSDefault key to handle this but when I check to see if this is set and then use performSegueWithIdentifier to initiate the segue, nothing happens. If i put this segue behind a button it works fine...
I answered a similar question where the developer wanted to show a login screen at the start. I put together some sample code for him that can be downloaded here. The key to solving this problem is calling things at the right time if you want to display this new view controller, you will see in the example you have to use something like this
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
[vc setModalPresentationStyle:UIModalPresentationFullScreen];
[self presentModalViewController:vc animated:YES];
}
I also have an explanation of how segues and storyboards work that you can see here
Loading in ViewDidLoad caused "under-layer" to flash. I solved this by loading my Storyboard programmatically. Thus, under Target/Main Storyboard - leave this blank. Then add the following:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Load Main App Screen
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
HomeScreenVC *homeScreenVC = [storyboard instantiateInitialViewController];
self.window.rootViewController = homeScreenVC;
[self.window makeKeyAndVisible];
// Load Login/Signup View Controller
UIViewController *mainLoginVC = [storyboard instantiateViewControllerWithIdentifier:#"MainLoginVC"];
[mainLoginVC setModalPresentationStyle:UIModalPresentationFullScreen];
[homeScreenVC presentModalViewController:mainLoginVC animated:NO];
return YES;
}
The problem is you are adding a second view to the hierarchy before the first is fully added. Try putting your code in:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Present your modal from here
}
After [super viewDidAppear] is called you have a fully loaded view to modify.
There is no principal problem with performing segues in viewDidLoad (after the call to super of course).
The problem is performing segues before the window of the application is made visible.
The UIViewController you want to display is part of the main storyboard so it is loaded into memory before the app begins running it's code in the app delegate. In your case, the viewDidLoad is called by iOS before your application window got message: MakeKeyAndVisible.
The important part is the visibility.
Performing a segue on a view hierarchy in which the window is not visible does nothing!
You can try to do something like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// The window initialized with hidden = YES, so in order to perform the segue we need to set this value to NO.
// After this action, the OS will add the window.rootViewController's view as a subview of the window.
self.window.hidden = NO;
[self.window.rootViewController performSegueWithIdentifier:_IDENTIFIER_ sender:self.window.rootViewController];
// Now that the window is not hidden, we must make it key.
[self.window makeKeyWindow];
return YES;
}
UPDATE: this solution no longer works in iOS 8.
A correct way to solve your problem is to trigger the segue / present modal view controller in applicationDidBecomeActive: app delegate method or in a UIApplicationDidBecomeActiveNotification notification handler.
Apple's documentation actually advises the same:
If your app was previously in the background, you could also use it to refresh your app’s user interface.
This solution has the advantage that it works with Main storyboard loading mechanism so that you don't need to load anything manually and write unnecessary code.
I use this solution successfully on iOS 6.1, 7.0 and 7.1 and it should work on iOS 5 either.
For Swift:
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("toView2", sender: self)
}
For Swift 3:
DispatchQueue.main.async {
self.performSegueWithIdentifier("toView2", sender: self)
}
This is how I did it in SWIFT. This also hides the View Controller.
override func viewWillAppear(animated: Bool) {
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
let isloggedIn = prefs.objectForKey("isLoggedIn") as? Bool
if (isloggedIn != false) {
self.view.hidden = true
} else {
self.view.hidden = false
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
let isloggedIn = prefs.objectForKey("isLoggedIn") as? Bool
if (isloggedIn != false) {
println("this should work")
self.performSegueWithIdentifier("Login", sender: self)
}
}
Swift 3
override func viewWillAppear(_ animated: Bool) {
if authPreference.isExist() == true {
self.view.isHidden = true
} else {
self.view.isHidden = false
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
if authPreference.isExist() == true {
navigateToSegue()
}
}
I had the same problem. Before finding this question, I solved this issue by using async in the main thread. This way, this code will be called by the UI thread right after creating the view.
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"segueAlbums" sender:self];
});
This code can be called in the viewDidLoad method.
Updated for Swift 3
The code snippet below allows you to load whichever viewController you want. In my case it was a TabBarController if the user had a valid facebook login token. The benefit to this solution over the other Swift 3 solution is that it's instantaneous with no screen flicker.
func applicationDidBecomeActive(_ application: UIApplication) {
if FBSDKAccessToken.current() != nil {
self.window?.rootViewController?.present((self.window?.rootViewController?.storyboard?.instantiateViewController(withIdentifier: "TabBarController"))!, animated: false, completion: nil)
}
}
The best solution is to do this:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self performSegueWithIdentifier:#"NameSegue" sender:self];
}
I adapted #bearMountain answer for Swift 3.
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let yourInitialVC: UIViewController? = storyboard.instantiateViewController(withIdentifier: "TermsVC")
window?.rootViewController = termsVC
window?.makeKeyAndVisible()
return true
}

Resources