I have IOS Swift program, using Storyboards with NavigationController.
There are two Views, lets call them mainView, secondView.
From the mainView I have BarButtonItem to go to secondView. When pressing that button, it triggers prepareForSegue function in the mainView with segue.identifier = "secondView"
When I have opend e.g. the secondView, I have two BarButtonItems for Cancel and Save. When pressing either of them the prepareForSegue function in that view is triggered, but now the segue.identifier = nil.
I would have expected to have the segue.identifier = "cancel" or "save" depended on the button pressed in that view.
Am I misunderstanding the segue functionality? Can anyone try to enlight me about this, as this looks like a very important and useful part of storyboards and navigation - but somehow I am not getting it right.
Have you created actions for the cancel and save buttons on your second view?
Right click and drag from your storyboard to the view controller code and select action from the dropdown.
Then in the action method, perform your segue.
#Garret, #rdelmar, #syed-tariq - thank you for pointing me into the right direction.
It turned out that Unwind Segue got me on track: Xcode Swift Go back to previous viewController (TableViewController)
But I also found one error I was doing in my storyboard, as I had Navigation Controller on all views (yes, I know - stupid when you know better): How do I segue values when my ViewController is embedded in an UINavigationController?
The final puzzle was to learn about the Protocols and Deligates to get this all to work.
Putting this together, then in short:
I created a protocol in my second view (above the class)
protocol MyDelegate: class {
func getMyList(sender: MySecondView) -> [NSManagedObject] //returns a CoreData list
}
Then I created a delegate variable in my second view
weak var datasource: MyDelegate!
In my first view I implemented the protocol, which was just one simple function returning a list that I needed in my second view
In my first view I have prepareForSegue where I catch the correct segue.identifier and there I set the delegate by going through segue.destinationViewController, like this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
if segue.identifier == "mySegue" {
let vc = segue.destinationViewController as! MySecondView
vc.delegate = self
}
}
and that was about it - now magically the flow is correct, segues happening, deligates passing correctly, and all good :)
Related
I want to make an Flashcards App and the behaviour is that on the CoursesVC, the user can add courses and click on them. Then he gets the list with flashcards. There he can add more flashcards. The storage is managed by CoreData. When its clicked on the cell, I pass the data to the flashcards list with prepareForSegue. To add the flashcard, I had the same idea in mind, but it was not possible because the variable from the second view controller wasn't initialised, when prepareForSegue was created. Question: How can I pass a NSManagedObject from the first ViewController to the third ViewController in an appropriate way? (ugly way would be to let the view render before creating prepareForSegue)
The difference to questions like "how to pass data between ViewControllers" is that I have three ViewControllers. It won't work with using prepareForSegue at the first and at the second view controller, because when the prepareForSegue is created, the variable in the second VC is not defined yet, because the view is not initialised yet! Keep in mind that the segue from the second to the third view controller is "Present Modally" as "Page Sheet"!
This is the solution basically: Swift : prepareForSegue with navigation controller
The problem was that the third view controller is embedded as a navigation controller. That is the reason why the prepareForSegue is different.
Solution is to use following prepareForSegue in the second VC:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let navigationVC = segue.destination as? UINavigationController, let myViewController = navigationVC.topViewController as? DViewController {
myViewController.currentCourse = self.currentCourse
}
}
I'm trying to make a communication between tableview and it's detail view
Please can you check my way and give me some advices
So, I embedded Navigation Controller to Table view
and I didn't use tableview(_:didSelectedRowAt) method.
Some answers in Stackoverflow, they said override prepare(:) method and write theperformSegue(withIdentifier:) method in the tableview(_:didSelectedRowAt)
but if i write the code like above two screens were shown.
(I think because segue action are triggered twice)
I just drag and drop the segue action(push) to Detail View from table view cell (Friends Name Cell)
By using this segue action, i can pass the data by prepare(:segue) method for editing selected friend name at the Detail View
and if i edit friend name from detail view's text field, there is edit button which trigger the unwind segue
so I override prepare(:segue) method in Detail View Controller
and wrote code below at Table View's ViewController
#IBAction func getEditedNameFromDetailView(_ sender:UIStoryboardSegue){
if sender.source is DetailViewController {
if let senderVC = sender.source as? DetailViewController {
data[(self.someTableView.indexPathForSelectedRow?.row)!] = senderVC.editedData!
}
someTableView.reloadData()
}
}
is this a proper way to communicate table view and its detail view?
From your description it is possible that you have 2 segues. one in the StoryBoard and one in your code.
The prepare method is not performing the segue. it simply gives you a chance to perform actions that are related to the segue, for example pass data to the destinations controller. do not call perform segue if you already created one in the StoryBoard and vice versa.
a common usage of prepare will look like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//get the destination controller and cast it to your detail class
if let destinationController = segue.destination as? YourDetailClassHere {
//set the properties you want
destinationController.someProperty = someValue
}
}
Few notes -
you can use the sender object to pass data from the perform call.
common mistake is to assume that the destination controller is always your detail controller. it can be the navigation controller and then you will have to extract the detail from the navigation controller.
My issue is that I have two view controllers, and I need to transfer a string from one view controller to the next. I already have that down from a segue. The actual issue is that I need a label to change in the next view controller to make it match the currentTitle of the button in the previous view. And yes, there are multiple buttons.
My code from view 1 is this:
#IBAction func whichButtonWasClicked(sender: AnyObject) {
clickedButton = sender.currentTitle!!
}
// Segue - Pass the Data of which lesson was chosen
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let DestViewController : Lesson = segue.destinationViewController as! Lesson
DestViewController.lessonPlan = clickedButton
}
I connected all my 7 buttons to that same one action.
The problem is that when I transfer view controllers, and do...
print(lessonPlan)
It does not give me the title of the button from the action, because I guess that action is after the segue. And yes I tested it, and it IS after the segue. SO it does work, it's just a bit delayed, and I need it to be a part of the segue.
Since your IBAction is being called after prepareForSegue, you can use programmatic segues.
Right now, you're probably dragging a segue from your button to the next view controller. Instead, you can drag a segue from the current view controller to the next one (see this answer for a visual guide). This will create a programmatic segue that you can trigger whenever you'd like from code. Remember to give the segue an identifier in Xcode.
Then in whichButtonWasClicked, call self.performSegueWithIdentifier("identifier you setin Xcode", sender: sender) after you set clickedButton in order to trigger the segue. By doing so, you'll ensure that your button action is called before the segue starts.
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 explain my question: in my iOS app, written in Swift, I have a mapview, using mkmapkit, where i added my mkpoint annotations, seven in total. When i tap on each annotation callout, it opens a new view controller using performSegueWithIdentifier.
My question is: Do I need to use seven different view controllers, one for each annotation callout tap, or can I use just one view controller? Because the view controller design must be the same for all annotation callout taps, but with different content data for each one.
Sorry for my bad english. I hope to figure out to this problem
When you perform the segue via performSegueWithIdentifier, prepareForSegue is called. Override this method to pass data to the destination ViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "mySegueIdentifier" {
let destinationViewController = segue.destinationViewController as MyDestinationViewController
// pass data to destinationViewController
destinationViewController.myProperty = ...
}
}
Use prepareForSegue to pass individual information to the same view.
Of course you should use only one ViewController. You just need to pass data from your MapViewController into ViewController through performSegue. And display it.
This pattern called Master-Detail and you can find more here:
An iOS 8 Swift Split View Master-Detail Example