I have created containerView which contain tableViewController.
The main issue, when user click on information, tableViewController must show the information which user clicked.
it show the information when I use present(<#T##UIViewController#>, animated: <#T##Bool#>, completion: <#T##(() -> Void)?#>)
but doesnt work if I call self.navigationController?.pushViewController
the main issue is when I make vc.info = info it doesnt work, the info has value but using injection the value in another class is nil.
Here is my code:
func showLoginDetailsOnIpad(encryptedDataBase: info) {
self.view.addSubview(loginContainer)
let mainStoryboard : UIStoryboard?
mainStoryboard = UIStoryboard(name: "StoryboardiPad", bundle: nil)
let vc = mainStoryboard!.instantiateViewController(withIdentifier: "tableVC") as! tableVC
vc.info = info
vc.showingLoginInfo = true
vc.modalPresentationStyle = .automatic
self.navigationController?.pushViewController(vc, animated: true)
}
Explanation
You mentioned the use of a containerView. The table controller being displayed inside the containerView, doesn't automatically get a reference to the nearest navigationController.
So in this line: self.navigationController?.pushViewController(vc, animated: true)
self.navigationController might actually be nil. You can confirm this by printing its value before running the push method like so:
print(self.navigationController) // See if this prints nil.
self.navigationController?.pushViewController(vc, animated: true)
Fixes
To fix the issue, I suggest to pass a reference to the parent controller's navigationController to the child controller - through the embed segue.
In your parent controller, you can use this code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? UITableViewController{
destination.navigationController = self.navigationController
}
}
And in your table view controller, leave the code as is.
Related
I'm setting up a UISplitViewController programatically. This code appears to automatically segue to the detailViewController which is unwanted behaviour. Instead I would like it to present the masterViewController and let the user chose the detailViewController with didSelectRowAt IndexPath. Any help appreciated.
let splitViewController = UISplitViewController(nibName: nil, bundle: nil)
let masterNavigationController = UINavigationController(rootViewController: MasterViewController(nibName: nil, bundle: nil))
let detailNavigationController = UINavigationController(rootViewController: DetailViewController(nibName: nil, bundle: nil))
splitViewController.viewControllers = [masterNavigationController, detailNavigationController]
present(splitViewController, animated: true, completion: nil)
Implement the UISplitViewControllerDelegate method splitViewController(_:showDetail:sender:) (documentation here) to override the behaviour of your split vc.
In your specific case it should return true all the time except the time when the split vc is presented initially. In this case, you could set up a flag variable, e.g.
var isInitialState: Bool = true
then set it to false once the split vc has been presented completely – here I'm not sure when would be the best time, but I would guess
override func viewDidAppear(_ animated: Bool) {
[...]
isInitialState = false
[...]
}
I have a controller which is in the Main storyboard. When I click on a button, I call the displayBorneDetailsAction() action, it presents a modal view from another storyboard.
I would add a Segue identifier when I present my modal to pass data from my main view controller to my modal view controller (with prepareForSegue), but I don't know how to do it.
I tried to use performSegue(withIdentifier:), but it doesn't present the modal in the same way.
#IBAction func displayBorneDetailsAction(_ sender: Any) {
// open the modal of a borne
let storyboard : UIStoryboard = UIStoryboard(name: "Borne", bundle: nil)
let vc: BorneVC = storyboard.instantiateViewController(withIdentifier: "BorneVC") as! BorneVC
let navigationController = UINavigationController(rootViewController: vc)
navigationController.modalPresentationStyle = UIModalPresentationStyle.overFullScreen
navigationController.edgesForExtendedLayout = []
self.present(navigationController, animated: true, completion: nil)
}
You cannot add an identifier to a programmatical segue, since you are able to access the instance of the controller that is being presented. Simply do anything you want with the controller in the function where you present it.
In my app, I have a UINavigationController that i'm pushing and popping ViewControllers from. At some point, I want to show a VC modally (showing the previous controller "underneath"). I can get it to work by setting up a segue in a storyboard, however I'm in a spot where I need to do it programmatically, and I can't seem to find the right magic incantation to make it work.
I saw a couple of similar questions but they seemed to be showing the UINavigationController modally, not showing one of the VC's on the UINavigationController stack modally.
(I put up a test application here: https://github.com/SuperTango/ModalNavController, and that's where this code and images come from)
The "Manual" code does:
#IBAction func goToVC2Tapped(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewController(withIdentifier: "VC2ViewController") as! VC2ViewController
destinationViewController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
self.navigationController?.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
self.navigationController?.pushViewController(destinationViewController, animated: true)
}
but it's not working (see the second transition in the gif below).
The segue that works is setup like this:
This gif is from the test app and shows how it works with the segue, but not manually.
Any ideas? Thanks!
To present modally you need to use:
present(destinationViewController, animated: true, completion: { })
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewController(withIdentifier: "YourVC") as! YourVC
destinationViewController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
destinationViewController.modalTransitionStyle = .crossDissolve
self.present(destinationViewController, animated: false, completion: nil)
I think this helps show your ViewController in a modal way of presenting. Write the above code under outlet actions or where you want to present your ViewController.
i wanted to ask, if i got one uitableviewcell, i need to do checking to meet the condition then only perform to different segue to different view, how to do it? Example like:
if subCat[indexPath.row] == ""{
performSegueWithIdentifier("A", sender: self)
}else{
performSegueWithIdentifier("B", sender: self)
}
How to do it? And how should i connect it with segue at the storyboard?
Even you can do by giving storyboard identifier "A" to viewController 1 and "B" to viewController 2 and push you controller if using navigation otherwise simply present the viewcontroller.
if subCat[indexPath.row] == "" {
let viewController1 = storyboard.instantiateViewControllerWithIdentifier("A") as! ViewController1
// if navigation controller used.
self.navigationController?.pushViewController(viewController1, animated: true)
// if navigation controller not used.
// self.presentViewController(viewController1, animated: true, completion: nil)
}else{
let viewController2 = storyboard.instantiateViewControllerWithIdentifier("B") as! ViewController2
// if navigation controller used.
self.navigationController?.pushViewController(viewController2, animated: true)
// if navigation controller not used.
// self.presentViewController(viewController2, animated: true, completion: nil)
}
I currently have a navigation controller setup like this:
and my prepareForSegue, that passes data between the initial view (Login View Controller) and the Navigation controller looks like such:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
let navVc = segue.destinationViewController as! UINavigationController // 1
let chatVc = navVc.viewControllers.first as! ChatViewController // 2
chatVc.senderId = userID // 3
chatVc.senderDisplayName = "" // 4
}
However, when I try to embed in a Tab Bar controller (to add more pages/functionality to my app) like this...
...and run my application, my program crashes at the line let navVc = segue.destinationViewController as! UINavigationController
I know that the problem is that after my initial view, it goes to the tab bar which is type UITabBarController rather than UINavigationController however if I change it, my data does not go to the view that I want it to go to...it is kind of confusing.
Please let me know if you have any ideas how to implement this, or if you have any questions feel free to ask me for clarification.
Thanks!
P.s. The error that I am receiving in the console is:
Could not cast value of type 'UITabBarController' (0x10b8e48b0) to 'UINavigationController' (0x10b8e4860).
Try this:
Start by casting destinationViewController to UITabBarController and then using the viewControllers property to access the first viewController in the tabBarController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let tabVc = segue.destinationViewController as! UITabBarController
let navVc = tabVc.viewControllers!.first as! UINavigationController
let chatVc = navVc.viewControllers.first as! ChatViewController
chatVc.senderId = userID
chatVc.senderDisplayName = ""
}
In the CS193p class, Paul Hegarty shows the uses of extensions to deal with Navigation Controller segues (Lecture 8: 23'). The UIViewController extension introduces a new computed property: contentViewController available to all UIViewControllers (and subclasses).
The code I posted below was adapted to work with TabBarViewControllers as well.
When you are attempting to cast your navigation controller as your ChatViewController, the segue.destinationController.contentViewController will recursively return the ChatViewController.
class LoginViewController: UIViewController {
/* ... */
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let chatVc = segue.destinationViewController.contentViewController as? ChatViewController {
chatVc.senderId = userID
chatVc.senderDisplayName = ""
}
}
}
extension UIViewController {
var contentViewController: UIViewController {
if let navcon = self as? UINavigationController {
return navcon.visibleViewController ?? self
} else if let tabcon = self as? UITabBarController {
return tabcon.selectedViewController ?? self
} else {
return self
}
}
}
A clean way of doing this would be that right after you log in, you use something like:
let controller = self.storyboard?.instantiateViewControllerWithIdentifier("setthisisyourstoryboard") as! UITabBarController // This would instantiate the TabBarController.
let navInstance = controller[0] as! UINavigationController // This would instantiate the navigationController, that is placed at 0th index in the array of all view controllers that are child to TabBarController, since you only have one child, you can use 0 as index
if navInstance.viewControllers[0] is YourClassName { // YouClassName is the name of the class right next to the navigation view controller, and it is also the only child of navigation Controller(0 index)
// You can also send some data here (for example the sender id)
(navInstance.viewControllers[0] as! YourClassName).someProperty = Value
}
// This line would present the tabBar controller, that would ultimately reach the end of the stack. I have used this approach in many apps, it works great!
self.presentViewController(controller, animated: true, completion: nil)
You can delete the segue after this and set storyboard id for the tabbarcontroller.
I like to do it this way because it is more natural. What I mean is the UITabBarController is mostly a parent to View Controllers, I don't like the idea of setting a UITabBarController as a child to UIView Controller.
Maybe there is nothing wrong with it, but I don't prefer it.
Here. it's working pretty fine for me:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let tabVC = segue.destination as? UITabBarController {
if let navVC = tabVC.viewControllers!.first as? UINavigationController {
if let nextVC = navVC.viewControllers.first as? NextVC {
nextVC.varName = "works like a charm"
}
}
}
}
NextVC is your target VC which you want to send your variable into.