I have multiple buttons in ViewA. When a button is clicked, it is redirected to ViewB. In ViewB, the user does some interactions, and then clicks the back button when he is done. How do I run a method and pass in a parameter from ViewB into ViewA and then continue working in ViewA?
Using the back button is strongly required, but I am keen to learn if there are other ways.
My idea was to get the ViewA from the stack, and when I am done with ViewB, just call upon it and redirect, but I couldn't figure out how to do it.
Thank you.
You want to define a delegate in ViewB and implement it in ViewA. At the appropriate time (e.g., when the back button is tapped) ViewB call the delegate method, passing the value as a parameter.
Something like this:
ViewB.h
// Define a delegate protocol allowing
#protocol ViewBDelegate
// Method of delegate called when user taps the 'back' button
-(void) backButtonSelected:(id) object;
#end
#interface ViewB : UIViewController
// property to hold a reference to the delegate
#property (weak)id<ViewBDelegate> delegate;
-(IBAction)backButtonSelected:(id)sender;
#end
ViewB.m:
#implementation ViewB
...
-(IBAction)backButtonSelected:(id)sender{
NSObject *someObjectOrValue = nil;
// Invoke the delegates backButtonSelected method,
// passing the value/object of interest
[self.delegate backButtonSelected:someObjectOrValue];
}
#end
ViewA.h:
#import "ViewB.h"
// The identifier in pointy brackets (e.g., <>) defines the protocol(s)
// ViewA implements
#interface ViewA : UIViewController <ViewBDelegate>
...
-(IBAction)someButtonSelected:(id)sender;
#end
ViewA.m:
-(IBAction) someButtonSelected:id
#implementation ViewA
...
// Called when user taps some button on ViewA (assumes it is "hooked up"
// in the storyboard or IB
-(IBAction)someButtonSelected:(id)sender{
// Create/get reference to ViewB. May be an alloc/init, getting from storyboard, etc.
ViewB *viewB = // ViewB initialization code
// Set `ViewB`'s delegate property to this instance of `ViewA`
viewB.delegate = self;
...
}
// The delegate method (defined in ViewB.h) this class implements
-(void)backButtonSelected:(id)object{
// Use the object pass as appropriate
}
#end
In iOS6 you can now use Unwind Segues. In Storyboard you might have noticed the new Exit buttons that can be used for this.
Unwind Segues will allow you to transition back from viewControllerB to viewControllerA and to provide info back through the prepareForSegue method.
Currently documentation is rather limited, but there is a useful WWDC2012 video on how to do this (Session 236)
If you don't want to implement protocol then other way is, you can create property in ViewB of type id like this
#property (nonatomic, unsafe_unretained) id* parentView;
And set it to ViewA when pushing ViewB like this
ViewB.parentView = ViewA;
//push ViewB
Then you can call ViewA's methods directly using this property.
You can define a protocol in ViewControllerA, example name ViewControllerADelegate, create a property delegate in ViewControllerB and in ViewControllerB implement the protocol defined in ViewControllerA.
When you are working in viewControllerB you can notify all your changes to viewControllerA calling methods defined in ViewControllerADelegate, when you will leave viewControllerB you will have viewControllerA updated.
If your ViewA is creating ViewB, you can create a protocol on your ViewB.h and set ViewA as ViewB's delegate. So whenever you change something in ViewB, you can notify ViewA directly.
Depending on your model, you can have a class that holds whichever settings you modify in ViewB. When you create ViewB, you pass the reference normally, and since it's an object, the instance will be shared, so when you go back to ViewA, all the changes are already applied.
viewB.settings = self.settings; // or whatever model object you have
Another option is to create a custom back button (you will lose the pointy look), have it call your custom method that notifies your ViewA and then does a [self.navigationController popViewControllerAnimated:YES];
Related
I am new to story boards and I am trying to move between table view controllers.
I understand how to set up a segue and how to send data to the new story board but my question is returning data back. If I use the push style segue it gives me an automatic back button. I want a page that will create a "job" and save it if they hit save (a bar button I created on the other side of the title) When I set up the segue to go back to the main page from the save button it made that main table view controller a child (instead of simply going back to it's original state). The work around I was thinking was saving it to a file when they hit save and whenever they load the main table view it loads from that file. Is this a common and correct way to do this or should I be trying to return that object and save it in the main table view controller?
A common approach is to use delegation. Since ViewControllers should be as independent as it is possible, we have to minimize dependencies between them. And actually your idea with the file, if I understand it correctly, does it as well. But using files to organize a communication between ViewControllers is not very convinient.
Usually you declare a #protocol SecondViewControllerDelegate for your second ViewController (where you click a "Save" button). With methods like:
#protocol YourSecondViewControllerDelegate <NSObject>
-(void)yourSecondViewControllerDidCancel:(YourSecondViewController*)controller; //if you have a cancel button
-(void)yourSecondViewControllerDidFinish:(YourSecondViewController*)controller yourDataToReturn:(SomeData*)data andSomeMoreData:(AnotherDataType*)data2;
#end
Then you add a property like this to your SecondViewController:
#property (nonatomic, assign) id<YourSecondViewControllerDelegate> delegate; //Do not retain it!
Then you adopt your protocol in your MainViewController.
#interface MainViewController()<YourSecondViewControllerDelegate> //you can do it in the private category to keep the class interface clear.
#end
#implementation
//don't forget to implement those methods here :)
#end
And when you trigger your segue from the MainViewController, you can set your MainViewController as a delegate for the SecondViewController:
SecondViewController *destinationController = [[SecondViewController alloc] init]; //Just for example.
destinationController.delegate = self;
//trigger a segue :)
When the user presses the Save button in the SecondViewController, you call the delegate's yourSecondViewControllerDidFinish:
[self.delegate yourSecondViewControllerDidFinish: self yourDataToReturn: someData andSomeMoreDate: someMoreData];
This way MainController's yourSecondViewControllerDidFinish method will be called. And you can pick someData and someMoreData up there.
You can get more details about it in this tutorial:
Apple's your second iOS app tutorial
If you're making a segue to go "back" to a controller, then that segue needs to be an unwind segue. An unwind segue goes back to the same instance of the controller that you came from originally. If you need to send data back to that controller, you can do it in prepareForSegue.
I have a simple app that uses a UITabBar controller for navigation. Let's say I have ViewA and ViewB on the tabBar. Everything works fine. However I also want to add a UIButton to ViewA that will present the user with ViewB when pressed.
In other words there are two ways to get to ViewB from ViewA. The first is from the tabBar and the second is by pressing the button in ViewA.
What would be the best way of accomplishing this. Thanks.
Ok then I would define a protocol in ViewA.h
#protocol ViewADelegate
-(void)viewAPressButton;
#end
and add a property to ViewA :
#property (nonatomic, assign) id<ViewADelegate> delegate
don't forget to set this property when instantiate this controller.
call this method when the button is pressed in ViewA.m
-(IBAction)buttonPressed:(id)sender {
[delegate viewAPressButton];
}
then implement in the correct place (maybe AppDelegate in your case ?)
Assuming ViewB is at index 1
-(void)ViewAPressButton {
[self.tabBarController setSelectedIndex:1];
}
I did find an answer to this title and I did do a little research but I'm still not getting the flow. Here is what I want to happen:
1) click a button on the presenter view to open a modal view. 2) retrieve some value and click a button to close the modal view....sending the value to the presentor view and execute a method.
I get that this works like a callback but I still can't figure out where to put the callback stuff.
So, how exactly do I do this? A) In the presentViewController completion block, should I include the presenter view method to execute when modal view is completed?
Or: B) In the modal view's dismissViewControllerAnimated completion block, should I include the presenter view method to execute when modal view is completed?
Can somebody help me with some sample code? Or at least help me get the flow of which block to put the code in?
Thank you, P
You talk about completion blocks so I am assuming you do not want to use delegates.
In the viewController that will be presented modally you need to provide a public completion handler, that will be called when it is dismissed.
#interface PresentedViewController : UIViewController
#property (nonatomic, strong) void (^onCompletion)(id result);
#end
Then in the implementation you need to call this completion block on dismissal. Here I assume the viewController is dismissed on a button click
- (IBAction)done:(id)sender
{
if (self.onCompletion) {
self.onCompletion(self.someRetrievedValue);
}
}
Now back in the viewController that presented the modal you need to provide the actual completion block - normally when you create the viewController
- (IBAction)showModal;
{
PresentedViewController *controller = [[PresentedViewController alloc] init];
controller.onCompletion = ^(id result) {
[self doSomethingWithTheResult:result]
[self dismissViewControllerAnimated:YES completion:nil];
}
[self presentViewController:controller animated:YES completion:nil];
}
This will create the new viewController to be presented modally and define what needs to happen on completion.
You can do this with delegates, that's the way Apple seems to recommend, but that seems like overkill to me. You have a reference to the presenter with the presentingViewController property, so you can just set the value of a property in the presenter from the presented controller in the button click method:
self.presentingViewController.someProp = self.theValueToPass;
[self dismissViewControllerAnimated:YES];
Using delegates is a good way to handle this:
In your PresentedViewController.h
#protocol PresentedViewControllerDelegate <NSObject>
-(void) viewWillDismiss;
#end
#property (nonatomic, weak) id <PresentedViewController> delegate;
Then in your PresentingViewController.h, you would subscribe to this delegate
#interface PresentingViewController : UIViewController <PresentedViewControllerDelegate>
in the .m you must implement the delegate method
- (void) viewWillDismiss {
}
and before you present the view controller set the delegate property you made as self.
presentingViewController.delegate = self;
Obviously not every implementation detail has been done here, but this should get you started.
What I've done so far is working but I would like to know whether this is the proper way or not.
I have a map that shows an annotation when this is pressed shows a callout.
The next view shown is a table view. This table has a button to remove that annotation.
I created one property in the table View of type MKMapView. After this view is initialized when the callOut accessory is tapped, I set the MKMapView property.
When the button is pressed in the table view, I delete the annotation through the map property.
Is this the right way?
Rather than the detail view directly manipulating the parent (map) controller view's controls, a more "right" approach might be to use delegate+protocol.
Define a protocol with the methods that the map controller needs to implement (eg. deleteAnnotation, detailViewDone, etc).
The detail view will have a delegate property for that protocol and call the protocol methods via the delegate property instead of directly accessing and modifying another view's controls.
The map controller would set itself as the delegate of the detail view and actually implement the protocol methods.
This way, each controller/class doesn't have to know the internal details of how the others work and let's you more easily change how each one works internally without affecting code in the others (as long as the protocol doesn't change). It improves encapsulation and reusability.
For example, in the detail view .h, define the protocol and declare the delegate property:
#protocol DetailViewControllerDelegate <NSObject>
-(void)deleteAnnotation:(id<MKAnnotation>)annotation;
-(void)detailViewDone;
//could have more methods or change/add parameters as needed
#end
#interface DetailViewController : UIViewController
#property (nonatomic, assign) id<DetailViewControllerDelegate> delegate;
#end
In the detail view .m, wherever you handle the delete button, call the delegate method instead:
if ([delegate respondsToSelector:#selector(deleteAnnotation:)])
{
[delegate deleteAnnotation:annotation];
}
In the map controller .h, declare that it implements the protocol and declare the methods:
#interface MapViewController : UIViewController<DetailViewControllerDelegate>
-(void)deleteAnnotation:(id<MKAnnotation>)annotation;
-(void)detailViewDone;
#end
In the map controller .m, in calloutAccessoryControlTapped where you create the detail view, set the delegate property instead of the map view property:
DetailViewController *dvc = [[DetailViewController alloc] init...
dvc.annotation = view.annotation;
dvc.delegate = self;
[self presentModalViewController:dvc animated:YES];
Finally, also in the map controller .m, implement the delegate method:
-(void)deleteAnnotation:(id<MKAnnotation>)annotation
{
[mapView removeAnnotation:annotation];
//dismiss the detail view (if that's what you want)...
[self dismissModalViewControllerAnimated:YES];
}
From the documentation, the articles Delegates and Data Sources and Using Delegation to Communicate with Other Controllers may be useful as well.
In my navigation based app, there is a button that if pressed, will change the view to a detailViewController. Here the user can set several options. One of those options is a bool value. When I return from the detailViewController how can I see what this bool value is?
Create a protocol (DetailViewDelegate?) and create a delegate property in your DetailViewController. When you instantiate your view controller, set the delegate property to self and use that property to send messages back to your master view controller. The only tricky part is that you need to declare the delegate property as "assign" so that you don't create a retain loop between your detail view and master view.
DetailViewController.h:
#class DetailViewController; // Forward Declaration.
#protocol DetailViewDelegate
- (void)detailViewController:(DetailViewController *)controller didChangeBool:(BOOL)theBool;
#end
#interface DetailViewController : UIViewController {
id <DetailViewDelegate> delegate;
}
#property (assign) id <DetailViewDelegate> delegate;
#end
That's just the interface, but it should get you most of the way there. Set the delegate property of the detail view and implement a detailViewController:didChangeBool: method in your master view and that's about it.
To answer the questions in your comment:
Yes. Before you push the detail view controller set it's delegate property to self.
You need to declare that your master view controller implements the DetailViewDelegate protocol. Learn how to do that by reading Apple's Documentation.
After you declare that your master view controller will implement the protocol, you need to actually implement it. Add a detailViewController:didChangeBool: method to your master view controller.