I have a simple app I am building. A list of items in 1 view controller with a table view and embedded navigation controller. When you select a row it brings you to the details screen (no problem).
Push from List View to List Item
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "ListItemDetailsVC", sender: nil)
}
Pop back from list item to List View
#IBAction func backToInboxTapped(_ sender: Any) {
navigationController?.popToRootViewController(animated: true)
}
This works fine!
My issue is that I have another view controller (add item) that presents modally when add button is clicked. The idea is that when it saves it brings you to the list detail vc.
#IBAction func saveItem(_ sender: Any) {
performSegue(withIdentifier: "DetailsFromAdd", sender: nil)
}
Opening up the details view controller from the add new controller works fine but once I open the details view I want to be able to go back to the rootViewController. The button does not work anymore
In your case you should not need to use segue. You have to clear some concepts.
You can't popToRootViewController or VC if you have pushed or show view controller from modally presented vc!!
Now if you want to achieve this then you have to make some changes that I am trying to mention below :
take one global variable or property (objective c concept) in your add item VC. now when you come to add item VC from lust VC set this global variable or property to self something like,
AddListItemVC *advc = [self.storyboard instantiateViewControllerWithIdentifier:#"addListItem"]; // addListItem is storyboard id for viewcontroller
[self presentViewController:advc animated:NO completion:^{
advc.vc = self; // here vc is the propery of type `UIVIewController` declare in AddListItemVC
}];
Now your code for going to details VC from add list VC should be like,
DetailListViewController *dvc = [self.storyboard instantiateViewControllerWithIdentifier:#"detailViewScreen"]; // detailViewScreen is storyboard id which you can set from identity inspector
[self dismissViewControllerAnimated:NO completion:^{
[self.vc.navigationController pushViewController:dvc animated:YES]; // here self.vc is global variable or property that contains reference of first VC (i.e. list view controller)
}];
no need to use performsegue in this case. You can use performsegue to go in detailvc directly from firstvc i mean from list vc.
Hope you got understand concept and i have written objective c snippet because of lake of time!! hope you can easily convert to swift!!
Firstly, As you say that addVC is modally presenting so make sure addVC have some navigation otherwise you'll not able to push listItemVC from addVC.
Now second thing, when you're modally presenting addVC then push listItemVC pushed from addVC, now addVC navigation controller have two view controllers. When you call navigationController?.popToRootViewController(animated: true) this will bring you to addVC because is being presented thats's why you will not able to see listVC.
To solve this you've to add some dismiss presented view controller whenever you'll arrive at addVC.
Related
I'm coding a simple app with swift and I'm stuck at the following point, I have two Controllers that lead to another one, and when I click on the cancel button, it always lead to the root Controller, no matter from where I come.
I have a first controller (UIViewController), that go to the Navigation Controller of my target Controller (the one from which I would like to go back to the right calling Controller).
I have a second controller (UITableViewController), which go directly to my target Controller.
Here's the code of my Cancel button:
// MARK: - Navigation
#IBAction func lendingCancelButton(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways
let isPresentingInAddLendingMode = presentingViewController is UINavigationController
if isPresentingInAddLendingMode {
dismiss(animated: true, completion: nil)
} else if let owningNavigationController = navigationController {
owningNavigationController.popViewController(animated: true)
} else {
fatalError("the LendingViewController is not inside a navigation controller.")
}
}
If I correctly understood (you could then correct me if I'm wrong, I would learn something), it's testing if the ViewController that's presenting my target ViewController is a NavigationController.
So maybe that, as the second Controller (my UITableViewController) is not going through a NavigationController, so the last one calling my target view with a NavigationController is always the UIViewController.
Don't hesitate to tell me if it's not clear enough (too many times the word "Controller" in my post) or if you need additional code.
Try something like this
if let navigationController = presentingViewController as UINavigationController {
navigationController.popViewController(animated: true)
} else if let viewController = presentingViewController as UIViewController {
dismiss(animated: true, completion: nil)
} else {
fatalError("the LendingViewController is not inside a navigation controller.")
}
If i understood you want to use dismiss when you find a UIViewController and to pop the navigation when you find a UINavigationController right?
Ok so I finally found a way to make it working.
My tableViewController was embedded into a NavigationController. I removed it (since I could do without it, according to my need). From this View Controller, I draw a segue that "Show" my target view.
From my other ViewController (this one is embedded into a NavigationController), I draw a segue put that present modally my target view.
With the code provided in my initial post, it's working.
The only thing I didn't understand is why the NavigationController from my TableViewController was likely to cause it not working properly.
I'm working on a project with has a search functionality.I've 3 view controllers i.e. HomeViewController, SearchResultViewController and DetailViewController.
On the main page, I've a search bar (using UISearchController) within the navigationBar along with some dummy content. Important thing here is that I'm using separate controller (in my case it is SearchResultController) to show the results. This SearchResultController is actually subclass of TableViewController which displays the result in tableview. Everything is working fine till here.
Problem
Now, when I’m selecting any row from the result ( i.e. at SearchResultViewController) I want to open another view controller (DetailViewController) where I’ll be showing the details about this selected item. It is a very simple thing but I'm stuck here .
Okay, continuing with the problem I’ve implemented didSelectRowAtIndexPath method and from that I’m calling performSegueWithIdentifier and I’m getting error “Receiver has no segue with identifier” because the storyboard property is nil (It is mentioned in the documentation).
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("storyboard property is \(self.storyboard)")
performSegueWithIdentifier(segueIdentifier, sender: indexPath.row)
}
Note:
segueIdentifier is same as it on the storyboard.
I'm getting that storyboard property is nil.
I guess
When I tap on the search bar, iOS internally creates and loads the instance of the SearchResultController to display the result and therefore its storyboard property is nil and because of that I'm getting the above error.
Can somebody give solution to this problem or any pointer to the right direction.
If you are creating viewcontroller programmatically then you should push in on navigation controller like,
UIViewController *vc = [[UIViewController alloc]init]; //your viewcontroller here
[self.navigationController pushViewController:vc animated:YES];
//or if you want to present it then
[self presentViewController:vc animated:YES completion:nil];
You can't perform segue because there is no segue without storyboard
Something like this in swift
let vc: UIViewController = UIViewController()
//your viewcontroller here
self.navigationController!.pushViewController(vc, animated: true)
//or if you want to present it then
self.presentViewController(vc, animated: true, completion: { _ in })
Avoid any mistake in swift!!!
hope this will help :)
I use a push segue to transition from a uisearchcontroller located within my root view controller, to a second view controller. When I try to use an unwind segue method to transition back to the root view controller from my second view controller, my app does not transition unless the button connected to the unwind method is pressed twice. The unwind method is called both times, however the transition only occurs upon the second call. I do not know why this occurs. Any help is appreciated. Thanks!
Unwind segue method
#IBAction func saveWordAndDefinition(segue:UIStoryboardSegue) {
self.searchController.active = false
if let definitionViewController = segue.sourceViewController as? DefinitionViewController {
saveWordToCoreData(definitionViewController.word)
}
tableView.reloadData()
}
How I linked my segue
Unwind segue
While what you're doing is permissible, it seems to be against best practice. The functionality of presenting a view controller, UITableViewController in this case, entering information, then later dismissing it with a button in the upper-right hand corner is generally associated with a modal view. In a push segue you'll get the back button in the upper-left corner for free, which will enable to you to pop the view controller off the stack without writing extra code.
Here's another Stack Overflow question that describe: What is the difference between Modal and Push segue in Storyboards?
To answer your question specifically, here are a couple links that should help:
[self.navigationController popViewControllerAnimated:YES]; is probably what you're looking for.
Dismiss pushed view from within Navigation Controller
How can I dismiss a pushViewController in iPhone/iPad?
So here's how I finally got this to work:
In my FirstViewController (the vc i'm unwinding to):
Here is my unwind segue method.
#IBAction func saveWordAndDefinition(segue:UIStoryboardSegue) {
self.navigationController?.popViewControllerAnimated(false)
}
Then I gave my unwind segue the identifier "unwind" in Storyboard.
In my SecondViewController (the vc i'm unwinding from):
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "unwind" {
if let destination = segue.destinationViewController as? VocabListViewController {
destination.saveWordToCoreData(word)
destination.tableView.reloadData()
}
}
}
I took care of passing data in the prepareForSegue method of my SecondViewController. Thanks to #Lory Huz for the suggestion. I finally figured out what you meant by it.
Works without any errors!
I am new to iOS development and XCode and am trying to figure out how to implement the following logic:
1) User starts app - user is directed to the LoginViewController that has a login form
2) Upon successful user login - go to the next view controller - let's call it LoginViewController
How do I programatically do that in the action logic for the login button in the view of LoginViewController e.g.
#IBAction func loginButtonPressed(button: UIButton) {
//do login logic
//what to put here to segue to OtherViewController
}
And once the user is in OtherViewController, it is possible their auth token will expire. If that happens I would need to segue back to LoginViewController. I come from an Android background where you would use Intents to make something like this happen - what is the equivalent in iOS?
An answer in Swift is preferred since I am more familiar with that, but I am decent at deciphering Objective C code as well.
As a side note- should I be controlling segues in code like this or should I be using storyboards? I tried creating segues in the storyboard but I do not understand how to hook those up to application code.
One way:
self.performSegueWithIdentifier("name2", sender: self)
As an example:
#IBAction func button(sender: AnyObject) {
self.performSegueWithIdentifier("newView", sender: self)
}
Here i created a modal segue from viewController1 to ViewController2 in the storyboard. I did this by control-click on the yellow ViewController icon at the top of the view and drag the blue line to the second view. Then release. in the popup menu select modal segue. Then in the utilities panel on the right - after highlighting the segue in the storyboard, click on the attributes inspector and give the identifier a name. Then paste that name in the code example where I have "newView".
Actually you can init you next controller from storyboard:
UIStoryboard * storyboard = self.storyboard;
OtherViewController * otherVC = [storyboard instantiateViewControllerWithIdentifier: # "xxx * Name Desire"];
If you use navigation controller:
[self.navigationController pushViewController: otherVC animated: YES];
Else:
[self presentViewController:otherVC animated:YES completion:nil];
Im using StoryBoard and i am not so sure how to instantiate a ViewController or how to reference one.
The thing is i have two view controllers and i have one with a button. I want to go to the other view controller when i pressed the button from the first view controller.
I have tried something like that:
let secondViewController:UIViewController = UIViewController()
self.presentViewController(secondViewController, animated: true, completion: nil)
Does anyone knows how to explain it to me? Thanks!
There are couple of ways of navigating between view controllers. Here's how you do it in code without segues if you're going that way.
Presenting a view controller modally.
Say you have 2 view controller scenes in the storyboard, FirstViewController and SecondViewController. You want to transit from FirstViewController to SecondViewController and there is no segue between them.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = storyboard.instantiateViewControllerWithIdentifier("SecondVC") as UIViewController
presentViewController(secondViewController, animated: true, completion: nil)
The important part is the that you have to assign an identifier to the view controller you want to go. In this case its the SecondViewController. The way you assign an identifier is you select the view controller, open up the right panel and in it, go to the Identity Inspector (the third one from the left) and under the Identity, assign an identifier to the Storyboard ID field. I put mine as SecondVC as you can see from the code snippet above.
Push to another view controller.
If you want to push on to another view controller instead of presenting it, all you have to do is embed the FirstViewController in a UINavigationController and change the code to this.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = storyboard.instantiateViewControllerWithIdentifier("SecondVC") as UIViewController
navigationController?.pushViewController(secondViewController, animated: true)
In a comment of yours I saw you want to go to the next view controller based on a condition. Well all you have to do is check your condition in a if else statement and execute either of those above code.
Using Segues.
If you're going to use segues instead of code, here's how you do it.
In the storyboard first you select the button and Ctrl drag to the other view controller. You'll be prompted to choose between different segues. Select show for push or show detail for modal transition (I've explained what these are below). And that's it! If you run it and tap the button, you'd be taken to the other view controller.
But if you want more control over this, you have to do a little more work.. Instead of creating a segue directly from the button, select your view controller and select that little yellow icon on top (This is in Xcode 6. In older Xcode versions its under the view controller scene). Ctrl drag from that icon to the other view controller you want to transit to. You can see a connection appears between those two controllers. Select the segue and open up the right panel and go to the Attributes Inspector (The forth one from the left). Give a name to the field identifier. I gave ToSecond.
Now create a normal action from the button. And you have to call a method called performSegueWithIdentifier passing that identifier. What it does is basically execute a segue with the identifier we give.
#IBAction func segueButtonPressed(sender: UIButton) {
performSegueWithIdentifier("ToSecond", sender: nil)
}
And this would work. You can do the conditions checking here inside a if else and if the conditions are met, call performSegueWithIdentifier.
One other thing you're likely to face is having multiple buttons in a view controller and segueing to different view controller when you tap each of them. A method called prepareForSegue fires each time a segue happens. And inside it, you can check for current segue identifier and execute it. The below code snippet will make this clearer.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ToSecond" {
let secondViewController = segue.destinationViewController as SecondViewController
}
}
Another thing I'd like to mention is that presentViewController and pushViewController are deprecated from iOS8. Instead you can use showDetailViewController for modal and showViewController to push.
showDetailViewController(secondViewController, sender: nil)
navigationController?.showViewController(secondViewController, sender: nil)
I'm not sure if these are backwards compatible. Meaning using show and show detail will work if you're developing for iOS 7 as well. If you are then just stick with the older methods.
First set seques of uistoryboardviewcontroller and try this
self.performSegueWithIdentifier("push", sender: self)
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if segue.identifier == "push" {
}
In the storyboard, select VC1 and then select the button and press control and while holding down the left button or touchpad drag across to your VC2. Then a menu should pop up. Select modal.
Run and Test. It should now perform a transition.
To transition back, the easiest way is to embed VC1 in a Navigation Controller. To do this, zoom out, select VC1 and go to the top of the screen and select:
Editor > Embed > Navigation View Controller.
Now test and run. You should have the option to go back.
If you are binding view controller programmatically you need to follow this step if you are creating storyboard based application.
It is similar what we don in Objective-c just the syntax has changed.
According to your question what you need to do is go to Main.storyboard and need to select identity inspector.
There you will be able to view identity which contains two fields
1.) Storyboard ID
2.) Restoration ID
Give them the name of view controller you have binded with class in storyboard id and select check box below restoration id. It will automatically copy storyboard ID in restoration ID.
You need to do same for all your view controllers.
let secondView = self.storyboard?.instantiateViewControllerWithIdentifier("SecondViewController") as SecondViewController
self.presentViewController(secondView, animated: true, completion: nil)
you need to write the name you have entered in Storyboard ID for self.storyboard?.instantiateViewControllerWithIdentifier("/* Storyboard ID */")
You can also visit the link:
Instantiate and Present a viewController in Swift
okay,
in interface builder control click on your button and drag the blue line that appears to the second view controller. The second view controller will highlight blue. You can release and the button is connected. In the popup menu select "modal segue". No code necessary. XCODE handles it.
Watch this video for a demo.