I have an application with two segues. In one of the segues, the current view controller becomes a delegate and the other does not.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"MoreOptions"]) {
UINavigationController *navigationController = segue.destinationViewController;
MoreOptionsViewController *controller = (MoreOptionsViewController *)navigationController.topViewController;
controller.delegate = self;
} else if ([segue.identifier isEqualToString:#"FullStoryView"]) {
SingleStoryViewController *detailViewController = segue.destinationViewController;
detailViewController.urlObject = sender;
}
}
All of this is working fine, but I would like to try and understand the code better. What I don't understand is that I have to get a reference to the MoreOptionsViewController by grabbing it from navigationController.topViewController rather than simply getting it from segue.destinationViewController like I do in the second if condition. Is it because I'm setting the current view controller (self) as the delegate? Again, I'm not trying to solve a problem, just trying to get a better understanding of what's going on.
Take a look at your storyboard and it should be evident why this is the case. You have embedded MoreOptionsViewController in a UINavigationController and connected a segue to the navigation controller, thus making it the destinationViewController. This is fairly common.
The delegate is largely irrelevant in the context of your question.
Your first segue's destination is a navigation controller, which contains the view controller you are really interested in. Therefore to get to that view, you need to go through the navigation controller since that won't have any properties you are interested in setting.
Your second segue goes directly to a single view controller, so you can access it directly.
Related
In my app, I use segment to switch in three view. Every segmment represented with container view and they become hidden or appear according to segment. That is how my the design works, and it works well. You can find the picture below, so you can understand the structure more:
I'm having trouble with giving an instance (namely, user) created in the view controller to segment's container's view controller. I created user in the user and assign it the values. user instance has the values, I checked it. So, as usual to give with segue, I tried prepareForSegue:sender method to achieve my purpose. In the viewLoad method of the table views(you can find the table views: each of them are attached to its own container in the view controller that has segment. There are 3 containers.) user instances of table views are null. I can't give it, basically. Shouldn't the view controller pass the user value? It might being container view, but still I think it should be :) Anyway, please find below what I have written so far:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
DovizTableViewController *controller = (DovizTableViewController *)navController.topViewController;
controller.user = self.user;
}
You would remove the sender part of your code.
It should look like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue{
//your code
//check if the next view exists by checking for the id
if ([[segue identifier] isEqualToString:#"Segue_Name"])
{
DovizTableViewController *controller = [segue destinationViewController];
controller.user = self.user;
}
}
Try this.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"YOUR_SEGUE_NAME_HERE"])
{
// Get reference to the destination view controller
DovizTableViewController *controller = [segue destinationViewController];
// Pass any objects to the view controller here, like...
controller.user = self.user;
}
}
Hope it helps.
I have a project where I have set up a protocol to pass information back from one TableViewController to a ViewController. Everything worked fine and as expected, but I decided to embed in a Navigation Controller to the TableViewController so I could add a "DONE" barButtonItem to dismiss the Controller when the user is done. Since embedding in the navigation controller, the button works well, the TablieViewController looks identical, but none of its features and methods that use the Protocol and Delegate work, and if I remove the NavigationController everything works. Could someone explain how I can fix this issue? I am fairly new to iOS and objective c.
Here is the prepareForSegue method in the NoteViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationViewController isKindOfClass:[ToolTableViewController class]]) {
ToolTableViewController *targetVC = segue.destinationViewController;
targetVC.toolDelegate = self;
targetVC.autoCorrectIsOn = self.autoCorrectIsOn;
targetVC.undoAvailable = self.undoAvailable;
targetVC.redoAvailable = self.redoAvailable;
}
}
ToolTableViewController.h
#protocol ToolTableViewControllerDelegate <NSObject>
#property (weak, nonatomic) id <ToolTableViewControllerDelegate> toolDelegate;
ToolTableViewController.m - example of a method called
-(void)clearInputText{
// NSLog(#"Clear Method Selected");
[self.toolDelegate didClearInputText];
}
NoteViewController.m
-(void)didClearInputText{
self.noteTextView.text = #"";
[self dismissViewControllerAnimated:YES completion:nil];
}
Since your table view controller is embedded in a navigation controller, it's the navigation controller that will be the destination view controller of the segue. Also, it would be better to use the identifier of the segue for the if statement, rather than the class of the destination view controller (I'm using "SegueToTable" as the identifier, change that to whatever you put for the identifier). Therefore, prepareForSegue should look like this,
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"SegueToTable"]) {
UINavigationController *nav = segue.destinationViewController;
ToolTableViewController *targetVC = nav.topViewController;
targetVC.toolDelegate = self;
targetVC.autoCorrectIsOn = self.autoCorrectIsOn;
targetVC.undoAvailable = self.undoAvailable;
targetVC.redoAvailable = self.redoAvailable;
}
}
Your delegate methods are called just fine (based on the sample you pasted).
Since your controllers are embedded in a navigation controller now, you should use:
[self.navigationController popViewControllerAnimated:YES]
Before, you were presenting your controllers modally, that's why dismissViewController worked fine then but not now ( in the context of a nav controller).
I’m building an iOS app. I want pass array value from one view controller to another through segue.
While doing this i’m getting an error:
[UITabBarController setSports:]: unrecognized selector sent to
instance 0x7b80e2b0.
here is my code:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"sportsSelection"]) {
Play *play=[segue destinationViewController];
play.sports=selectedSports;//error break point is here.
//sports and selected sports are NSMutableArray
}
}
To make the situation a bit clearer, you are getting the error because you are trying to call a method (setSports:) on a class that does not implement this method, this is exactly what the error message tells you:
[UITabBarController setSports:]: unrecognized selector sent to
instance 0x7b80e2b0.
Your segue apparently has a UITabBarController as destination, so it's clear that it does not know about the method setSports:, since this one is actually implemented in your custom view controller (apparently called Play?!).
Then, as Eike pointed out in his answer, you need to get Play from the UITabBarController which it is embedded in. That's why he suggested to use: Play* p = ((UITabBarController*)segue.destinationViewController).viewControllers[0];, it means that you should get the view controller at index 0 from your UITabBarController.
According to your comment, the view controller at index 0 is a UINavigationController, which (naturally) also does not respond to setSports:, because just like UITabBarController it is a class provided by Apple and doesn't know about this method.
Now, you need to find out where in this UITabBarController your custom view controller Play is located. Either it is a direct part of UITabBarController and you can find it by using Eike's approach and just modify the index from 0 to n (where n is the number of view controllers that the UITabBarController has hold of), or another option is that it is embedded in the UINavigationController that you received at index 0, so in that case you'd have to access the UINavigationController's view controller stack (e.g. the array property viewControllers or just the one that is currently on top of the stack using topViewController).
Edit: I want to give you some extra information about what's going on in your code, especially related to Eike's answer:
From the information that you gave us in your question and in the commment to Eike's solution, we can assume the following code to be correct:
if ([[segue identifier] isEqualToString:#"sportsSelection"]) {
UITabBarController *tabBarController = [segue destinationViewController]; // the destination of the segue is your `UITabBarController`
UINavigationController *navigationController = tabBarController.viewControllers[0]; // gets the first of the view controllers contained in your UITabBarController
NSLog(#"view controllers in navigation controller: %#; top view controller: %#", navigationController.viewControllers, navigationController.topViewController); // print all view controllers managed by navigationController
}
EDIT 2: From your comment I can now assume the following code to be correct:
if ([[segue identifier] isEqualToString:#"sportsSelection"]) {
UITabBarController *tabBarController = [segue destinationViewController]; // the destination of the segue is your `UITabBarController`
UINavigationController *navigationController = tabBarController.viewControllers[0]; // gets the first of the view controllers contained in your UITabBarController
Play *controller = (Play *)[[navigationController viewControllers] objectAtIndex:0];
controller.sports=selectedSports;
play.sports = selectedSports;
}
You need to setSports: to your view controller not the UITabBarController.
Play* p = ((UITabBarController*)segue.destinationViewController).viewControllers[0];
p.sports = selectedSports;
First, a little background. I'm new to iOS development, I've been in .Net land for a long time, and that's probably why I'm even asking this question, but here goes.
The basic setup is this. You have a UINavigationController with a RootViewController we'll call MasterViewController. When some action happens on this MasterViewController, we want to drill into a DetailsViewController. However, we also want to pass some data to the DetailsViewController.
It is my understanding, that in previous versions of the SDK (prior to iOS 5) the approach was similiar to this:
#implementation MasterViewController
-(IBAction)someAction
{
DetailsViewController *dvc = [[DetailsViewController alloc]initWithNibName:#"DetailsView" bundle:nil];
dvc.someDataProp = [self getSomeDataSomeHow];
[[self navigationController] pushViewController:dvc animated:YES];
}
#end
Now however, in iOS 5, it seems that this is now done using the Storyboard and segues. In XCode you set up the segue from the MasterViewController to the DetailsViewController, and then in code you do something like this:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
[segue.destinationViewController setSomeDataProp:[self getSomeDataSomeHow]];
}
My question is basically this: The older approach somehow feels a lot cleaner to me. You're being very explicit about the type of ViewController you're pushing on to the navigation stack and you can set properties easily on it. In the new approach though, destinationViewController is of type id (for obvious reasons), and it just feels a lot less clean to me. Again, this could be my .Net side coming out, but is this common in iOS? Just use id and throw caution to the wind?
With Storyboards you can assign a named identifier to the segue,
Select the segue and in the Attribute inspector you can add a name to the segue Identifier.
And in the prepareForSegue method you should check for this Identifier and thus you will explicitly know which segue is about to be performed and what the destinationViewController will be.
if ([segue.identifier isEqualToString:#"My First Segue Identifier"])
{
DetailsViewController *dvc = (DetailsViewController *) segue.destinationViewController;
// Set the DVC's properties
}
In many cases, the destination view controller for a segue may be a UINavigationViewController, and in that case, the solution (a slight modification of Dennis Mathews' solution above) will need to use the message"topViewController":
if ([segue.identifier isEqualToString:#"My First Segue Identifier"])
{
NavitationViewController* navController = [segue destinationViewController];
DetailsViewController *dvc = (DetailsViewController*) [navController topViewController]
// Set the DVC's properties
}
I haven't been working with iOS that long, but I've seen a few examples where you don't have to cast because of objective-c's loose coupled messaging system.
Instead of checking the segue identifier or casting to a specific ViewController you can do this:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.destinationViewController respondsToSelector: #selector(setCompany:)]) {
[segue.destinationViewController performSelector: #selector(setCompany:) withObject: self.company];
}
}
In the first line I ask if the destinationViewController has a method setCompany (if you have a property named company this one would be generated for you). If it does, you can call that method/set that property with the second line of code.
So in this case you don't really have to know the destination ViewController and could easily replace it with a different one that supports handling Companies.
I was wondering how to properly use the storyboard to put up a view controller modally. Personally I prefer working with xibs, but it seems that the storyboard is gaining popularity and will be the way to go in the future.
The way I would normally put up a view controller modally would be like this: let's say we have ViewControllerA (A for short) and ViewControllerB (B for short).
I would then normally put a protocol in B.h specifying the delegate method when B wants to be dismissed and add the id<theProtocol> delegate field as an assign property. Assuming i'm busy in A and I want to present B modally, I would write:
B* b = [[B alloc] initWithNibName:#"B" bundle:nil];
b.delegate = self;
[self presentModalViewController:B animated:YES];
Using the storyboard, I know it's possible to put up a different view controller in a modal way by ctrl-dragging from a button to a viewcontroller and selecting modal as transition type. I'm just wondering though; where do I set the delegate of the new view controller? What's the correct practice of passing things to your modal view controller? I don't really know what the whole deal with Segues is...
Take a look at this tutorial
According to it, you should set the delegate as follows:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"AddPlayer"])
{
UINavigationController *navigationController =
segue.destinationViewController;
PlayerDetailsViewController
*playerDetailsViewController =
[[navigationController viewControllers]
objectAtIndex:0];
playerDetailsViewController.delegate = self;
}
}
Where #"AddPlayer" is the name of your 'modal' segue
Instead of using the navigation controller you could directly use the UIStoryboardSegue object passed in prepareForSegue. It has a property called destinationViewController which is the view controller that is being instantiated. I find that a lot cleaner.
This is an example.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"AddPlayer"])
{
PlayerDetailsViewController
*playerDetailsViewController =
(PlayerDetailsViewController *) segue.destinationViewController;
playerDetailsViewController.delegate = self;
}
}
IMO I think that storyboards are great because they function like a blueprint of your application. Also I've never liked nibs. =D