I have two view controllers: RootViewController and DetailViewController
I define a Push Segue to DetailViewController from RootViewController in Storyboard, and I set its ID to ShowDetailView. I can then set up various variables in DetailViewController using the method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"ShowDetailView"]) {
}
}
My Question: Is there a way to call a method (performed in RootViewController) when I resign from DetailViewController back to RootViewController? I'm using the following code to resign:
[self.navigationController popViewControllerAnimated: YES];
By default? No. You can build a delegate for this.
But instead, I think you can perform the code you want inside -(void)viewWillAppear:(BOOL)animated of your RootViewController.
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self myMethodHere];
}
The viewWillAppear method will be called exactly after your DetailViewController will be resigned.
EDIT:
Like a said, no easy way to do this. Maybe the best way its really with a delegate. It really has to be AFTER the resign? If it can be before, you can get the reference of your rootViewController and execute your method BEFORE the resign, like this:
RootViewController *rootViewController = (RootViewController*) [[self.navigationController viewControllers] objectAtIndex:0];
[rootViewController someMethod];
[self.navigationController popViewControllerAnimated: YES];
If you really want to be after, than you can create a flag and set to yes with the code above, and check this flag with an if inside viewWillAppear to execute your method, otherwise you cannot run from delegate.
Related
I started using storyboards but am noticing one very significant difference: The storyboard appears to be instantiating a new ViewController each time I navigate back and forth.
Example: I create two new Xcode projects based on the Master-Detail template. In Case 1, I use the Storyboard and in Case 2 I use the .xib.
Normally I would expect these to behave identically, but they don't!
In both of the DetailViewController.m I add the following method:
-(void)viewDidAppear:(BOOL)animated{
if (xposition ==0) {
xposition=50;
}else{
xposition = xposition+50;
}
NSLog(#"xposition update %d", xposition);
}
(I have also declared the xposition as an "int" instance variable in the header):
When I run the Storyboard version and tap the "+" and navigate in and out of the DetailViewController then my NSLog statement keeps giving me "xposition update 50".
By contrast, for the .xib version I get my expected behavior where each time I navigate in and out of the DetailViewController that the "position" increments by 50: so 50, 100, 150 etc.
How do I fix Storyboard to make it behave in the same way as the .xib based version. Specifically, I want to only instantiate the DetailViewController once.
EDIT: Answering my own question. I got some help on this and wanted to post the answer that worked for me.
When you first perform the segue store the destination viewcontroller in a property (see method "PrepareForSegue". My VC is called MyViewController)
Then create the delegate method called "shouldPerformSegueWithIdentifier" and use this to intercept the segue and manually present the stored ViewController for all subsequent segues.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
UIViewController *destination = segue.destinationViewController;
NSLog(#"identifier = %#", [segue identifier]);
if([[segue identifier] isEqualToString:#"mySegue"]) {
self.myViewController = (MyViewController*)destination;
NSLog(#"Saving myViewController for later use.");
}}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if([identifier isEqualToString:#"mySegue"]) {
if(self.myViewController != nil) {
NSLog(#"Using the saved myViewController.");
[self.navigationController pushViewController:self.myViewController animated:YES];
return NO;
}else {
return YES;
}
}
return YES;}
When you navigate back and forth your storyboard pops off your DetailViewController. Because it is not retained by anything else it will be released, this is normal behavior.
If you want to keep the instance you'll have to retain it in the ViewController you are calling it from and use it later on again. Check this question for an example
Edit:
I think you solved the problem but here is an example:
Create a property for you viewcontroller in your interface, say myViewController
Retain the viewcontroller in the prepareForSegue method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
[self setMyViewController:[segue destinationViewController]];
}
This it not leaking memory, your example could leak in some cases. Check out this guide here.
The next time the seque will be performed check if the property is already set and if so use it:
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if([self myViewController] != nil){
[[self navigationController] pushViewController:[self myViewController] animated:YES];
return NO;
}else{
return YES;
}
}
I am able to pass data from one view to other view without any problem. But here is my situation i am facing problem.
Let's say
-> Passing data to other view
ViewA->ViewB This working perfectly fine
ViewB->ViewC This working perfectly fine
ViewC->ViewB Here is the problem.
I tried using push segue ,it goes to ViewB but when i press back button it goes to ViewC->back button->ViewB->back button->View A. From ViewB when i press back button it must goes to ViewA.
Tried using modal segue, it goes to ViewB but then i can't go any where.
As i am new to iOs I am really haven't got any idea about how to achieve this?
How to pass data back to ViewB from ViewC?
I think you guys can help me.
Edit
In View A i am calling ViewB like this
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"RemixScreen"])
{
if([self.recipeFrom isEqualToString:#"ViewB"])
{
ViewB *reciepe = [segue destinationViewController];
//somedata i will pass here
}
}
}
In View B i am calling like this
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"ViewA"])
{
ViewA *data = [segue destinationViewController];
//some data
}
}
Thanks for your help guys.
The correct way to do this is to use delegates. You use properties in prepareForSegue to pass data forward and delegates to pass data back.
The way it works is to have a delegate property in ViewC that is set by ViewB in prepareForSegue. That way ViewC can communicate with ViewB via a protocol you set up.
EDIT: Adding code to demonstrate:
ViewControllerBInterface:
#protocol ViewBProtocol
- (void)setData:(NSData *)data;
#end
#interface ViewBController: UIViewController <ViewBProtocol>
...
#end
Here we make ViewBController follow our protocol that ViewCController will communicate to.
Next up is ViewCController interface:
#interface ViewCController: UIViewController
#property (nonatomic, weak) id<ViewBProtocol> delegate;
...
#end
Now we look at ViewBController's prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController* controller = [segue destinationViewController];
if ([controller isKindOfClass:[ViewCController class]])
{
ViewCController* viewCController = (ViewCController *)controller;
viewCController.delegate = self;
}
}
As you can see we link the ViewC controller to the ViewB one via the delegate property. Now we do something in ViewC:
- (void)sendData:(NSData *)data
{
[self.delegate setData:data];
}
And you can use that in your viewWillDisappear method of ViewCController if you wanted to.
The way I have gotten around this problem in a recent project is the following;
View A is the parentViewController, so can be accessed from View B and View C at any time like this;
ViewAClass *parentView = (ViewAClass *)self.parentViewController;
You can then read and write to a property of the View A, like;
NSString *string = parentView.someStringProperty;
or
parentView.someStringProperty = #"Hello World";
EDIT - "To go back from view B to ViewA with out back button"
[parentView popViewControllerAnimated:YES];
Are you using navigation controller? If yes, you can easily pass data with Singleton, which is my favorite way to do that and also its easy to use. Otherwise, if you're trying to navigate through views with buttons or something, try to set a identifier to your segue, then call the method "performSegueWithIdentifier"
I am learning iOS and I have written a simple iPhone app using iOS 5. The app shows a UITableView populated with Speakers' names, when I select one of the names its supposed to go to a UIViewController and show details about that person (name, address, etc), so its really two ViewControllers a UITableViewController and UIViewController (both subclassed).
So, in MICSpeakersTableViewController : UITableViewController I have this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MICSpeakerDetailViewController *detailViewController = [[MICSpeakerDetailViewController alloc] initWithNibName:#"Detail" bundle:nil];
[detailViewController setSpeaker:[[self getSpeakers] objectAtIndex:indexPath.row]];
[self.navigationController pushViewController:detailViewController animated:YES];
}
which gets called when I select it and populates the speaker (in that its not nil and description matches).
Then I have this in the same implementation:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [ self.tableView indexPathForCell:sender];
if ([segue.identifier isEqualToString:#"Detail"])
[segue.destinationViewController setSpeaker:[[self getSpeakers] objectAtIndex:indexPath.row]];
}
Which also gets called and the segue.identifier is Detail and the destinationViewController's speaker is set correctly (is not nil, description matches). I am not quite sure why I have to set that again since I am setting it in didSelectRowAtIndexPath but I set it again and it seems harmless.
Finally, in the MICSpeakerDetailViewController, the initWithNibName method is called and the self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; returns an instance.
However, the segue never happens and viewDidLoad is never called.
Its probably something small but I can't figure it out... any advice?
Edit: Here is a screenshot of the storyboard showing the segue and the controllers:
You have to embed the view controllers in a navigation controller for push segues to work. I don't know why it lets you define them without this in place.
To do this, click on your table view controller, then choose Editor --> Embed In --> Navigation Controller. If you should also be within a tab bar controller, then the navigation controller is embedded in that in a similar fashion. You should see the following:
Your segue will now work.
I'm very new to iPhone development and Objective-C. Today, I figured out how to open a new ViewController and how to return to the previous one.
Here's how I currently do this:
// In the main view controller I have a method called openSecondView that is defined like this:
- (void) openSecondView:(id)sender {
SecondViewController *secondView = [[SecondViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:secondView animated:YES];
}
// In the SecondViewController I have a back button that calls a method called closeView that is defined like this:
- (void)closeView:(id)sender {
[self dismissModalViewControllerAnimated:YES];
}
My question is, how do you do properly accomplish this?
Should I call [secondView release] after calling presentModalViewController or is this done some what behind the scenes? I ask this because when I was debugging I noticed that presentModalViewController doesn't seem to be a blocking code, the next few lines of code I added seem to execute immediately, without calling dismissModalViewControllerAnimated. Are there any consequences of calling [secondView release] after presentModalViewController?
Any help/advise would be much appreciated.
Just call [secondView release] after calling presentModalViewController. The view controller will be retained until it is dismissed.
I'd like to use a modal UITableView at startup to ask users for password, etc. if they are not already configured. However, the command to call the uitableview doesn't seem to work inside viewDidLoad.
startup code:
- (void)viewDidLoad {
rootViewController = [[SettingsController alloc]
initWithStyle:UITableViewStyleGrouped];
navigationController = [[UINavigationController alloc]
initWithRootViewController:rootViewController];
// place where code doesn't work
//[self presentModalViewController:navigationController animated:YES];
}
However, the same code works fine when called later by a button:
- (IBAction)settingsPressed:(id)sender{
[self presentModalViewController:navigationController animated:YES];
}
Related question: how do I sense (at the upper level) when the UITableView has used the command to quit?
[self.parentViewController dismissModalViewControllerAnimated:YES];
You can place the presentModalViewController:animated: call elsewhere in code - it should work in the viewWillAppear method of the view controller, or in the applicationDidFinishLaunching method in the app delegate (this is where I place my on-launch modal controllers).
As for knowing when the view controller disappears, you can define a method on the parent view controller and override the implementation of dismissModalViewControllerAnimated on the child controller to call the method. Something like this:
// Parent view controller, of class ParentController
- (void)modalViewControllerWasDismissed {
NSLog(#"dismissed!");
}
// Modal (child) view controller
- (void)dismissModalViewControllerAnimated:(BOOL)animated {
ParentController *parent = (ParentController *)(self.parentViewController);
[parent modalViewControllerWasDismissed];
[super dismissModalViewControllerAnimated:animated];
}
I had quite the same problem. I know the topic is old but maybe my solution could help someone else...
You just have to move your modal definition in a method:
// ModalViewController initialization
- (void) presentStartUpModal
{
ModalStartupViewController *startUpModal = [[ModalStartupViewController alloc] initWithNibName:#"StartUpModalView" bundle:nil];
startUpModal.delegate = self;
[self presentModalViewController:startUpModal animated:YES];
[startUpModal release];
}
Next, in viewDidLoad, call your modal definition method in a performSelector:withObject:afterDelay: with 0 as delay value. Like this:
- (void)viewDidLoad
{
[super viewDidLoad];
//[self presentStartUpModal]; // <== This line don't seems to work but the next one is fine.
[self performSelector:#selector(presentStartUpModal) withObject:nil afterDelay:0.0];
}
I still don't understand why the 'standard' way doesn't work.
If you are going to do it like that then you are going to have to declare your own protocol to be able to tell when the UITableView dismissed the parentViewController, so you declare a protocol that has a method like
-(void)MyTableViewDidDismiss
then in your parent class you can implement this protocol and after you dismissModalView in tableView you can call MyTableViewDidDismiss on the delegate (whihc is the parent view controller).