Segue to a view controller from appDelegate - ios

I have an app set up to receive push notifications. When a user receives a notification, I have a callback in my appDelegate. I need to be able to segue from here if the app was inactive and the user clicks on the notification from the panel on the device.
The flow of the app is a login view controller (which gets skipped if the loginBool is true) which leads to a tab controller. On the tab controller I have 3 places where I can segue to the same viewController with an id of "FeedDetailedController".
It is the FeedDetailedController I need to segue to and pass in a variable which I am receiving in my notification. This controller can be accessed from 3 different places 2 of which are tabs with table views, when you click on a row, it passes in a variable and performs a segue. I need to miic this from my app delegate i.e. pass in data from the notification, like what I am doing with the row.
Attempt so far:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
println("received a notification")
PFPush.handlePush(userInfo)
if application.applicationState == UIApplicationState.Inactive {
println("in the notification if with \(userInfo)")
if let info = userInfo["custom"] as? Dictionary<String, AnyObject> {
if let reportId = info["reportId"] as? String {
println("\nFrom APS-dictionary with key \"type\": \(reportId)")
//pass in reportId to the viewcontroller somehow
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("NewFeedDetailedController") as! UIViewController
let navigationController = UINavigationController(rootViewController: vc)
self.window?.rootViewController?.presentViewController(navigationController, animated: true, completion: nil)
}
}
PFAnalytics.trackAppOpenedWithRemoteNotificationPayload(userInfo)
}
else{
println("in the notification else")
//this is when the app is active, do I need to detect which view controller I am currently on before I can seg???
}
}
The current code gives the following message:
Warning: Attempt to present <UINavigationController: 0x12ed763d0> on <UINavigationController: 0x12ed11ce0> whose view is not in the window hierarchy!
Which makes sense but I don't know how I am supposed to get the hierarchy right coming from appDelegate code

Would it be possible to check for that notification in the initial view controller of your app? Like pass it the notification as a boolean? You could look somewhere like viewWillAppear: and check for it - if it is there control the segue from that view instead of trying to do it in the appDelegate itself?
That way it would appear to go directly into the new view, but really be passing through a view controller that has the hierarchy loaded into it?

Related

How to handle showing different view controllers when the app starts based on whether the user has logged in or not?

In an app that I am developing, I need to show different view controllers based on whether the user has logged in or not when the app starts
If the user has logged in, I need to show HomeViewController
If the user has not logged in, I need to show LoginViewController
Presently, I have coded in the following way-
the rootViewController of window of AppDelegate is always LoginViewController embedded inside a UINavigationController.
In the viewDidAppear method of LoginViewController, I check if the user has logged in. If yes, then I push HomeViewController.
In the HomeViewController, when the user logs out, I pop the HomeViewController to show LoginViewController
Is there a better way to do this?
Which is the better way is totally depends on your requirement and self-satisfaction.
Now think about your approach:
If you're going with this approach then login screen will display for fraction of time. If it is okay for you then you can for
it.
Now think about my approach:
I think the better way is to check the same condition in didFinishLaunchingWithOptions instead of loginVC
If you are going this approach then your HomeVC is directly displayed after your splash screen. It is the main advantage of this
approach
Code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let isLoggedIn = true
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let navigationController = storyboard.instantiateViewController(withIdentifier: "NavigationController") as! UINavigationController
var viewController:UIViewController
if !isLoggedIn{
viewController = storyboard.instantiateViewController(withIdentifier: "LoginVC")
}else{
viewController = storyboard.instantiateViewController(withIdentifier: "HomeVC")
}
navigationController.viewControllers = [viewController]
window?.rootViewController = navigationController
return true
}
Download sample code
Update for Anbu.Karthik:
Just define the method in AppDelegate.swift the file then calls it when required. Also, the same method is implemented in the sample code too.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.logoutFromApp()
Code to change the root view controller:
func logoutFromApp(){
guard let rootNavigationController = window?.rootViewController as? UINavigationController else {
return
}
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "LoginVC")
rootNavigationController.viewControllers = [viewController]
}
Sometimes I use a custom root view controller, that can change a child view controller based on business logic (the root controller is a subclass of UIViewController not of UINavigationControler or UITabBarController). For example, the root controller can subscribe to notification about login/logout of user and show required view controller as a child view controller.
To support this, the root controller should be a custom container view controller (part with title Implementing a Container View Controller).
This solution allows to divide the interface to different story boards and save memory because you can destruct invisible view controllers.
I think the better and easier approach will be to check is user signed in in AppDelegate (the best approach to create separate class for this logic).
You need to store user token in keychain and in AppDelegate check is user signed in. If it's require API request, while request is executing, replace Splash screen with spinner and depends from the API response, display necessary controller

Accessing a modal viewController from Appdelegate

I have a situation where if the user is not logged in to Firebase, when they launch my app, a modal viewcontroller is presented asking them to log in.
I am also using UIActivityViewController to send data between devices, but want to notify the user with an alert if he is not logged in.
I figure I can do this from the AppDelegate in the open url application function:
func application(_ app: UIApplication, open url: URL, ...
I just have to figure out how to navigate to that viewcontroller to call the function that will present my alert.
Within this function, I have this:
if status == .notLoggedIn {
guard
let rootVC = window?.rootViewController as? UITabBarController,
let collectionSplitVC = rootVC.viewControllers?.first as? UISplitViewController,
let navVC = collectionSplitVC.viewControllers.first as? UINavigationController,
let MyBookshelfCollectionVC = navVC.children.first as? MyBookshelfCollectionVC
// here is where I want to get access to the modal viewController presented on this MyBookshelfCollectionVC.
// it is called LoginVC
else { return true }
LoginVC.showAlertFromAppDelegate(status: status)
}
So the question his, how do I access this modal viewcontroller?

how to launch different view controllers for different local notifications in ios 10

I am receiving multiple local notifications in my app at different times. I want to launch different view controllers for my app depending on the local notification received. I know about the launch options in didfinishlaunching and didreceive notifications But I dont know how to detect which notification is received and take action according to the notification received.Even when I managed to do that, the problem is that I am able to open a view controller from the app delegate but it is no longer attached to the navigation controller that it is otherwise attached to the storyboard. How to do that? Heres my code:
func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let customViewController:WebViewController = storyboard.instantiateViewController(withIdentifier: "webVC") as! WebViewController
let index=Constants.instructionsData.count - 1
customViewController.url=Constants.instructionsData[index].weblink!
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = customViewController
self.window?.makeKeyAndVisible()
}
You want to use notification in iOS 10, so I understand you want to use UNNotification. When you use UNNotificationRequest there is argument called identifier. You can use it to distinguish, which notification send this request. If you use the "old" notifications, you just give selector which will be executed when notification is received.
Edited:
In your code you are changing root view controller, so how do you want the navigation controller to last? You are not using segues.
try:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let customViewController:WebViewController = storyboard.instantiateViewController(withIdentifier: "webVC") as! WebViewController
self.window?.rootViewController?.navigationController?.pushViewController(customViewController, animated: true)

IOS - How to jump between viewControllers in different scenarios

I have 5 viewControllers(A,B,C,D,E), all these viewControllers are programmatically connected, and i could push and pop between them successfully.
Using:
navigationController?.pushViewController(loginVc, animated: true)
_ = navigationController?.popViewController(animated: true)
NOTE: ViewController A appears first as a default initial viewController.
Now, what i want is, when the user installs the App, only for the first time the viewController A must be shown as the initial viewController, rest of the times when the App is opened, the viewController D must be the initial viewController and from there i should be able to jump between previous viewControllers. How can i implement this. Im using Xcode 8.2, Swift 3.0
Thanks in advance.
In order to do that, you simply could add a boolean to your NSUserDefaults, using the following code:
let defaults = UserDefaults.standard
if (!defaults.bool(forKey: "firstTime")) { //will be false if does not exist yet
navigationController?.pushViewController(yourDesiredVC, animated: true) //push desired vc
defaults.set(true, forKey: "firstTime") //set the key so it never executes again
} else {
navigationController?.pushViewController(yourDesiredVC, animated: true) //push the other vc
}
Your question doesn't really say very much without some code, but one suggestion (given the current quality of the question) would be to use UserDefaults. Add your current version of the app to a key called e.g. LatestVersion. Compare it at launch with the apps current version, if they don't match, show ViewControllerA, if not show ViewControllerB.
Another way is just saving launchedForFirstTime. If its not set show ViewControllerA, however the above would take in account future versions of the app where you might want to show that view as well.
You can keep a value in UserDefaults to keep track of the returning users and check if it's there:
if let returning :Bool = UserDefaults.standard.bool(forKey: "initial_controller_shown") {
//returning user flow
} else {
//new user flow
}
A common place to check for this is in the applicationDidBecomeActive or didFinishLaunchingWithOptions
when first time your app launched then use a flag and store some value in it so that next time when your app run then you can check that whether user visit the app for the first time or not .. Now after that go to appDelegate and paste the following code in DidFinishLaunchingWithOption...
if yourFlag == true
{
let mainStoryboard: UIStoryboard = UIStoryboard(name: "MainStoreyBoard", bundle: nil)
let controller = mainStoryboard.instantiateViewController(withIdentifier: "StoreyBoardIdofYourViewController") as! UINavigationController
self.window?.rootViewController = controller
}
This will launch D viewcontroller .....
You can check it in your app delegate.m file whether the app installed first time ViewController A will appear as a initial view controller else view controller D. Check it in the following function:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//check the initial view controller here//
return true
}
Swift 3:
let launchBefore = UserDefaults.standard.bool(forKey: "launchBefore")
if launchBefore {
print("Not first launch.") } else {
print("First launch, setting UserDefault.")
UserDefaults.standard.set(true, forKey: "launchBefore") }
You are using one Navigation Controller so it will be hard to implement your behavior. It will be much easier for you to use separate View Controllers for View A and D and call them using:
present(vc, animated: true)
and dismiss calling:
dismiss(animated: true)

Call ViewControllers Method or Segue from App Delegate (Swift)

I have created an app that uses storyboard and have successfully created a tableview and detail page which all works.
I would like it so that users swiping the localNotifications can be sent to the correct detail page within the app.
It appears that I can call functions from the ViewController but whenever they refer to themselves to update any details or perform a segue the app crashes.
The code i have in my ViewController is as follows:
func handleMessageNotification (messageID:String)
{
// this function should act as a conduit for the call from the delegate
println("notif messageID: \(messageID)");
self.performSegueWithIdentifier("showMessageDetail", sender: self);
self.messageView.reloadData();
}
and this is called from my appDelegate
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
if (application.applicationState == UIApplicationState.Background || application.applicationState == UIApplicationState.Inactive)
{
var rootViewController = self.window!.rootViewController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController") as ViewController
rootViewController?.navigationController?.popToViewController(setViewController, animated: false)
setViewController.handleMessageNotification(messageID);
}
}
the println works correctly but performSegue fails (fatal error: unexpectedly found nil while unwrapping an Optional value) and the messageView.reload() fails (same error).
How do I get a notification to fire the app to the correct place upon opening?
This solution 'Get Instance Of ViewController From AppDelegate In Swift' uses much of the same but mine will not allow access to anything with the ViewController.
======= Update - Solution =======
For anyone else with this issue. Following on from what Gabuh had suggested below; the full solution for me was to do the following:
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
if (application.applicationState == UIApplicationState.Background || application.applicationState == UIApplicationState.Inactive)
{
let navigationController = application.windows[0].rootViewController as UINavigationController;
navigationController.popToRootViewControllerAnimated(false); // I need to push back to root before performing segue
let rootViewController = navigationController.visibleViewController;
rootViewController.performSegueWithIdentifier("showMessageDetail", sender: self);
}
}
this also allows me to make calls on functions in the view such as in the original example e.g.
rootViewController.handleMessageNotificaion(messageID);
or
rootViewController.messageView.reloadData();
You are creating an instance of a new ViewController. And you are trying to call a function on that ViewController, that is trying to perform a segue, when the ViewController is not even displayed. I think that's not going to work...
If you want to perform a segue from your detail view, you have to access to the current instantiated view Controller and perform the segue from it.
If you want to know how to get the instance of the UIViewController that is being displayed, in this post there are several ways that show you how to do it:
Get the current view controller from the app delegate
In pseudo-code, in your appDelegate:
1- get current UIViewController instance (not new one)
2- perfom segue from that view controller (or present modally or whatever transition you want)
In Swift 3:
guard let rvc = self.window?.rootViewController as? VCName else { return }
rvc.methodInYourVC()
The above assumes
That you don't want to create a whole new instance of VCName, just want a reference to it
That you know the VC you want to reference is your root VC

Resources