Push to ViewController from AppDelegate with stack and navigation Bar - swift

I'm developing a messaging app and I'm having trouble implementing push notification behaviour.
What I want is that when a user taps on the notification, the conversation of that group opens. But there has to be a Navigation bar to go back to the list of contacts.
I already know how to extract information from the notification to get the contact name. I'm trying this in the function didReceive on AppDelegate.
So far what I've tried is:
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ChatViewController") as! ChatViewController
vc.contactName = name as String
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = vc
self.window?.makeKeyandVisible()
This open the right conversation and the messages appear, but UI elements are no responsive (can't write when tapping textfield, cant navigate to gallery when tapping button, etc.) and also there is no Navigation bar.
So after doing some research, I've tried this:
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ChatViewController") as! ChatViewController
vc.contactName = name as String
let rootViewController = self.window?rootViewController as! UINavigationController
rootViewController. pushViewController(vc, animated: true)
This doesn't work. There is no error message but the screen is just white.
This is my Storyboard:
I'm using SWRevealViewController for the side menu and the messaging part has a tabBarController, with the contact list on the 2ยบ tab.
EDIT: With the second method, there is an error.
Could not cast value of type 'SWRevealViewController to UINavigationController'

I think you should go with NotificationCenter to do this.
When the user click on the notification you should post a NotificationCenter in the didReceive like this:
NotificationCenter.default.post(name: .onMessageRecieved, object: notificationObject)
And based on which view controller you're in you should add an observer there in the viewDidLoad() func so it understands a message has been recieved like this:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(MyViewController.onMessageRecieved(notification:)), name: .onMessageRecieved, object: nil)
}
And you should have a func (onMessageRecieved) in the view controller to do the navigation like this:
func onMessageRecieved(notification: NSNotification){
// open the conversation
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ChatViewController") as! ChatViewController
vc.contactName = name as String
self.navigationController?.pushViewController(vc, animated: true)
}
This way you are doing the navigation from the view controller so the navigation will be there as well.

Could not cast the value of type 'SWRevealViewController to UINavigationController'
This issue is because of the "SWRevealViewController" is not a subclass of UINavigationController. So you can't cast it this way.
To solve the chat ViewController issue, you should modify your code like this.
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ChatViewController") as! ChatViewController
let nav = vc.navigationController
vc.contactName = name as String
let rootViewController = self.window?rootViewController as! UIViewController
rootViewController.presentViewController(nav, animated: true, completion: nil)
But the chat VC will be in the presenter mode. You should handle the dismiss.
You just try this and let me know is it working or not.

Related

Why pushing to a ViewController displays it as popup?

I have been trying to figure this for a while now. When I'm pushing to a ViewController it displays it as a popup instead of the fullscreen.
I have UINavigationViewController embedded in MainPageViewController. Inside MainPageViewController, I have a "Sign in" button that once clicked it should display the HomePageViewController. The question is why it is displayed as a popup instead of fullscreen?
Below is the code that I'm using to push to the HomePageViewController, inside the Sign In button action method:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
let destinationNavigationController = storyboard.instantiateViewController(withIdentifier: "NavigationViewController") as! UINavigationController
You have to give ViewController Presentation as a Full Screen
If you need to do it by code, you can set presentation and transition style like this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
destinationViewController.modalPresentationStyle = .fullScreen
destinationViewController.modalTransitionStyle = .coverVertical
And then push from your navigation controller

Instantiating deep View Controller with Tab bar and Navigation

I am using Xcode 11 and Swift 5.
Upon receiving an APNS notification, I need to jump to a view controller deep in my storyboard from AppDelegate. This viewController (chatVC) is behind a tabbar and a navigation controller and several other view controllers. See the image below. I know how to check the notification for tags and how to use launchOptions in AppDelegate to trigger the jump. But I am struggling with how to establish the context for that last view controller so that the user can use the back button all the way back to the tab bar controller.
I have read many SO answers, and tried many approaches but none seem to have the same embedding of a nav controller inside a tab bar controller. Here is my code in AppDelegate (after reading the tag in the notification):
if tag == "CS" {
// Set up a Chat View Controller.
if let chatVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.chatVC) as? NewChatViewController,
let tabBarVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.tabBarController) as? UITabBarController,
let csVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.csViewController) as? CustomerServiceViewController,
let helpVC = UIStoryboard(name:"Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.helpVC) as? HelpViewController
{
// Set the customer service document Id.
chatVC.cs = cs
// Make the tabBarVC the Root View Controller
self.window?.rootViewController = tabBarVC
self.window?.makeKeyAndVisible()
// Select the Favorites Index.
tabBarVC.selectedIndex = 0
// Push the Customer Service VC on top.
tabBarVC.show(csVC, sender: Any?.self)
// Push the Help VC on top of Customer Service VC.
csVC.show(helpVC, sender: Any?.self)
// Push the the chat detail page on top.
helpVC.show(chatVC, sender: Any?.self)
}
}
}
return true
What can I do to jump to the chatVC and set up the navigation context beneath it so the back button can be used?
Here is how you can do it:
guard let tabBarVC = UIApplication.shared.windows.filter( {$0.rootViewController is UITabBarController } ).first?.rootViewController as? UITabBarController else { return }
tabBarVC.selectedIndex = 0 //you can select another tab if needed
guard let chatVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: Constants.Storyboard.chatVC) as? NewChatViewController else { return }
if let navController = tabBarVC.viewControllers?[0] as? UINavigationController {
navController.pushViewController(chatVC, animated: true)
}
You can also pass objects from your notification to your chatVC here before pushing it, in case you need to do that.
Hope this works for you!

Swift - How switch between storyboards with back button?

I'm in a storyboard and I need to go to another storyboard by tapping a button. Well, this I've already done, but I need to go back to previous storyboard. Here is my code:
func goContact() {
let storyboard = UIStoryboard(name: "FAQ", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "ContactViewController")
self.navigationController?.show(vc, sender: nil)
}
By tapping a button on my UITableViewCell (where I have a protocol), this function (goContact) is called and I switch to another storyboard (FAQ). But, how can I go back to main.storyboard?
I've already tried do this too:
func goContact() {
let storyboard = UIStoryboard(name: "FAQ", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "ContactViewController")
self.navigationController?.show(vc, sender: nil)
// self.navigationController?.pushViewController(vc, animated: true)
// self.present(vc, animated: true, completion: nil)
}
Use only storyboard property , it references to Main storyboard of the app or you can write it like let storyboard = UIStoryboard(name: "Main", bundle: nil)
To go back after push do
self.navigationController?.popViewController(animated: true)
You should push your new view controller onto the navigation controller using push, like in your commented-out code. That will cause the new view controller to have a back button, which will pop it for you without needing any extra code.

Attempt to present * whose view is not in the window hierarchy

I am using the following code in AppDelegate to display a popup window within the app when a button is selected, i will eventually move this to a label hyperlink but just testing currently.
let storyboard = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("test") as! ViewController
let popOverVC = UIStoryboard(name:"Main", bundle: nil).instantiateViewControllerWithIdentifier("sbPopUpID") as! PopUpViewController
storyboard.addChildViewController(popOverVC)
popOverVC.view.frame = storyboard.view.frame
storyboard.view.addSubview(popOverVC.view)
popOverVC.didMoveToParentViewController(storyboard)
self.window?.rootViewController?.presentViewController(storyboard, animated: true, completion: nil)
The first time i select a button this works correctly, however on all subsequent button presses the following error is displayed.
2016-10-28 11:27:40.551 testfordeeplinks[20496:104536] Warning:
Attempt to present
on whose view is not
in the window hierarchy!
For anyone who stumbles across this in the future, this is how i resolved;
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = storyboard
rootViewController doesn't necessarily refer to the view controller that is currently visible. For example, if you utilise a UINavigationController, rootViewController will hold a reference to that navigation controller which in itself is buried deep in the view hierarchy and hence when you try to present a view controller on it get an error. What I would recommend doing is grabbing the last view controller in navController.viewControllers and presenting your popup on that. See the following code:
let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
if let navController = appDel.keyWindow?.rootViewController as? UINavigationController {
if let visibleVC = navController.viewControllers.last {
visibleVC.presentViewController(storyboard, animated: true, completion: nil)
}
}
Give that a try and let me know. :)

How to reset/restart viewcontroller in Swift?

I want to have a button on my navigation bar "Reset" and I would like this to be connected to an IBAction to sort of "restart" the controller.
I have some segues from another controller that changes some aspects of the viewcontroller (that has a collectionview) and I want the user to be able to start over. Does anyone have any suggestions as to how to proceed?
If you embed navigation controller to your view controller then you can use this code:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("ViewController")
let viewcontrollers = self.navigationController.viewControllers
viewcontrollers.removeLast()
viewcontrollers.append(vc)
self.navigationControllers?.setViewControllers(viewcontrollers, animate: true)
You can change rootViewController of your application:
UIApplication.shared.keyWindow?.rootViewController = UIViewController()
This is how I do it in Swift 2.1 with my Home view inside a UINavigationController:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let homeVC = storyboard.instantiateViewControllerWithIdentifier("HomeViewController")
self.navigationController?.presentViewController(homeVC, animated: false, completion: nil)
self.navigationController?.dismissViewControllerAnimated(false, completion: nil)