I have two controllers named RootViewController and CountrySelectionActivityViewController. I have written a prepareForSegue function to pass data from first to the second view. I have a navigation controller in which the view is embedded in. So, here is my code:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "next") {
let mynavigationController = segue.destinationViewController as UINavigationController
let vc = mynavigationController.topViewController as CountrySelectionActivityViewController
vc.countries = countries
}
}
If I comment out this code, my app runs and moves to the next viewcontroller. Though, the data is not passed if this function is not implemented. What is wrong with my implementation?
I wanted to achieve the same objetive as you, passing variables between controllers when performing a segue.
My solution was to create two variables in each controller and related them through a custom segue.
In order to define the custom segue a new class is creaded: CustomPushSegue.m. This file has the implementation of -(void)perfom. This class is of type UIStoryboardSegue and has origin and destination variables of type UIViewController.
Then the idea is to cast each viewController of destination and origin as the custom controllers and pass the variables:
#import "OriginController.h"
#import "DestinationController.h"
-(void) perform {
UIViewController *destinationController = self.setinationViewController;
UIViewController *originController = self.sourceViewController;
if ([[self identifier] isEqualToString : #"Segue"]){
(DestinationController*) destinationController.var1 = (OriginController*) originController.var1;
}
//Stuff related to the performance of the Segue goes here
}
If you require further information, please ask. I can send you some sample files.
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 just looking for a way to detect which Segue Identifier activated the current viewController. I'm in need of doing this as I have conditions that might not be met, but can be referenced from another viewController, which then I'd like to highlight a few labels using that specific segue ID. Has anyone needed to do this before? How did you approach this?
Probably you should create on your "current view controller" a property to store the name of the segue and then on the controller which uses the segue to instantiate your "current view controller" you assign it before fire the segue execution:
ObjectiveC:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([[segue identifier] isEqualToString:#"YourSegueName"]) {
// Get destination view
CurrentVC *vc = [segue destinationViewController];
// Get button tag number (or do whatever you need to do here, based on your object
vc.segueName = #"YourSegueNam";
}}
Swift 3:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "YourSegueName"
{
if let destinationVC = segue.destination {
destinationVC.segueName = segue.identifier
}
}
}
Is that what you need? Let me know.
Anyway I still do not know why you want to do that.
This is not possible. You cannot simply get the "current Segue" at any point in the app. Because Segues Exist only at specific points in time during your Apps Life cycle. That being when your app is preparing for the transition from one view controller to another and also during the actual visual transition.
For more info follow https://developer.apple.com/documentation/uikit/uistoryboardsegue
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 :)
I try to build an rss reader. On the "adding feed" page, if I tap the "add" button, I hope to check if the feed is successfully added. If it is added, then trigger the unwind segue, and back to the main page. If it is not added, stay in the current page.
I know I can build an IBAction on the "add" button, and check if the feed is added. However there are two requirements I need to meet in order to add a feed.
First, after I parse the url, I need to know if the parse results can generate a feed. To parse the url, I need to use the method defined in the mainViewController.
Second, I need to check if the feed already exists. If this feed already exists, don't add it. To check this, I need to get the feed data from mainViewController.
Currently I use prepareForSegue to pass the data from main viewController to this view. But for the conditional unwind segue, I don't know how to pass the data and check if the feed already exists. Because prepareForSegue is used only when the segue is going to be triggered. If the segue is not triggered, I can't check the condition.
Besides through segue, is there any other ways to pass data from other view?
I don't know objective-C, so it would be better if you can give me some solutions in swift. :)
Like Schemetrical said, using a delegate is an easy way to access the methods in your MainViewController.
Since you tagged this as Swift, I'll also give you a small example of a delegate in Swift.
First you create a protocol:
protocol NameOfDelegate: class { // ":class" isn't mandatory, but it is when you want to set the delegate property to weak
func someFunction() -> String // this function has to be implemented in your MainViewController so it can access the properties and other methods in there
}
In your MainViewController you have to add:
class MainViewController: UIViewController, NameOfDelegate {
// your code
#IBAction func button(sender: UIButton) {
performSegueWithIdentifier("toOtherViewSegue", sender: self)
}
fun someFunction() -> String {
// access the other methods and return it
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toOtherViewSegue" {
let destination = segue.destinationViewController as! OtherViewController
destination.delegate = self
}
}
}
And the last step, you'll have to add a property of the delegate, so you can "talk" to it. Personally I imagine this property to be a gate of some sort, between the two view controllers so they can talk to each other.
class OtherViewController: UIViewController {
weak var delegate: NameOfDelegate?
#IBAction func button(sender: UIButton) {
if delegate != nil {
let someString = delegate.someFunction()
}
}
}
I assumed you used a segue to access your other ViewController since you mentioned it in your post. This way, you can just "talk" to your MainViewController.
EDIT:
As for the unwind. This also can be done through a segue.
add: #IBAction func unwindToConfigMenu(sender: UIStoryboardSegue) { } to your MainViewController.
In your storyboard there are 3 icons at the top of your OtherViewController. Click on the round yellow with a square inside to make sure the ViewController is selected and not some elements inside.
Control drag (or right mouse drag) from the same round yellow with a square inside to the most right red square icon. Doing so pops up a menu where you can select the unwind segue.
Click on the new segue you just created. Give it an identifier like "backToMain"
Add something similar as the code below to OtherViewController
It appears i can't post any code anymore? :o will add it later.
You can always use delegates.
Set up a delegate in your add feed page and get mainViewController to conform to the delegate. Add a delegate method (- (BOOL)canGenerateFeed:(NSURL *)url) and a delegate property (#property (weak, nonatomic) id <AddFeedControllerDelegate> delegate).
When your add feed page calls [self.delegate canGenerateFeed:url] and your mainViewController conforms to the delegate, the method in mainViewController is called (that should reply BOOL as stated in the method declaration). Then you can reply YES or NO accordingly, which will be sent back through to the add feed page.
- (UIViewController*)viewControllerForStoryboardName:(NSString*)storyboardName class:(id)class
{
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
NSString* className = nil;
if ([class isKindOfClass:[NSString class]])
className = [NSString stringWithFormat:#"%#", class];
else
className = [NSString stringWithFormat:#"%s", class_getName([class class])];
UIViewController* viewController = [storyboard instantiateViewControllerWithIdentifier:[NSString stringWithFormat:#"%#", className]];
return viewController;
}
// get the view controller
ViewController* viewController = (ViewController*)[self viewControllerForStoryboardName:#"MyStoryboard" class:[OtherViewController class]];
// Pass data here
viewController.data = myData;
// or you can push it
[self.navigationController pushViewController:viewController animated:YES];
Based on the Stanford iOS course I am playing with modal view controllers. In the demo they have a button that would launch a modal view and when it is clicked the function prepareForSegue is called. I mimicked the code and implementation into my project with the only difference is that my demo is on an iPhone storyboard and theirs is on the iPad.
I noticed that while my modal view controller is coming up, it does not call prepareForSegue prior to that. I searched the Stanford project to see where they may register any segue behavior before prepareForSegue is called but there is no evidence. Can anyone shed some light on this. I searched stack overflow and all I found were that users were missing the call implementation of performSegueWithIdentifier. However, in the Stanford demo they never do that.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier hasPrefix:#"Create Label"]) {
AskerViewController *asker = (AskerViewController *)segue.destinationViewController;
asker.question = #"What do you want your label to say?";
asker.answer = #"Label Text";
asker.delegate = self;
}
}
Here is an example of there storyboard:
Here is an example of my storyboard:
In the debugger when I stop in the Stanford Demo code the call stack shows that the storyboard is performing a segue action, what do I need to configure in my storyboard to achieve the same result?
Well, as it turns out, my view controller where button calls the modal view did not have the right class where prepareForSegue is implemented. It was the default UIViewController instead of my custom class.
The way I figured it out was by putting a break point in viewDidLoad and even that was not breaking and thus I suspected that in the storyboard I did not have the right class associated with the view where the button is implemented.
For others with this problem, if you are now using Swift 3 having the following function will not throw errors as it is the correct syntax but it will not work because it is the Swift 2 function:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// code
}
You must update to Swift 3 "prepare" function for it to work for segues:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// code
}
When hooking up an automatic segue for a table view, there is, in addition to Amro's answer (not assigning your corresponding subclass), another two cases where prepareForSegue might not be called. Ensure you've:
hooked up the segue from the table view prototype cell, not the table view controller.
used a segue from under the "Selection Segue" group in the segue connection pop-up menu, not one under "Accessory Action".
[Click image to enlarge]
Whether its an Modal or Push Segue below code will always be called
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Create Label"]) {
SignUpViewController *asker = segue.destinationViewController;
}
}
I had a similar problem with UICollectionViewCell as the source of segue.
It seems that for the storyboard segue to work (without performSegueWithIdentifier) it's required that you have a custom subclass of UICollectionViewCell, and use it as a Class for the CollectionViewCell on the story board.
I had a similar issue.
The thought process was:
make sure you have the correct method signature. It has changed in Swift 3.
Then make sure the way you have hooked up the button (or whatever that triggers the segue) matches with the way you have hooked the segue in storyboard. Sometimes you call a button, but haven't properly hooked up the segue from that button to the destination viewcontroller.
Be sure the identifier of the segue is correct. Though this isn't the reason the prepareForSegue doesn't get called, this only the reason that a specific segue isn't called.
In my case, it ocured because my controller was extending another controller (Eureka Form View Controller = FormViewController) witch has implemented the performSegue function like this:
open override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// code
}
My function was implemented like this:
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// code
}
To solve this, i just added override before:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// code
}
Voila!
In my case, I did not set the module under the name of the class in the storyboard of the view controller that contains the segue. It was set to none and once I clicked in the module field it set to the correct project / module and resolved the issue.
In my case, I have a base class for several view controllers in my project. That base class has an implementation of prepareForSegue() which wasn't getting called in every case. The problem was that in one of my view controllers that inherits from the base class and overrides its prepareForSegue() implementation, I forgot to call super.prepareForSegue().
Firstly you have to select on your button + ctrl drag item to you view controller choose selection segue .Later, you have to name segue identifier properly.
Don't connect to one view controller to other view controller.
import UIKit.framework for this
Then this method will get called.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"identifierName"])
{
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
ExampleViewController *destViewController = segue.destinationViewController;
}
}