dismissViewControllerAnimated completion block presenter view and modal view flow - objective-c

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.

Related

How to return an object using delegate and protocol

I'm working on this project where I have a User class, my main view controller, and my SetUp control viewer (second control viewer). The User class has all the User properties, the main view controller creates the username/password then calls the SetUp view controller, and the SetUp view controller allows the user to enter additional information. I created a protocol in my SetUp view controller that calls a delegate method in my main view controller, after the user enters the additional information in the second view controller. The delegate methods returns a User object, but this is where I get lost. Where is the User object being returned? Its definitely not going where I want it to.
/**Main View Controller Implementation**/
#import "BWViewController.h"
#import "User.h"
#interface BWViewController ()
#end
#implementation BWViewController
#synthesize holdPassword,holdUserName,holdZone,holdArea;
-(IBAction)signUpButton
{
firstUser = [[User alloc] init];
firstUser.userName = userNameField.text;
firstUser.password = passwordField.text;
SetUp *setUpView = [[SetUp alloc] initWithNibName:Nil bundle:Nil];
setUpView.delegate = self;
User * bufferUser;
bufferUser = [self presentViewController:setUpView animated:YES completion:^{ }]; /**This is where I want the User object from the delegate method return to**/
}
/**Delegate method in Main view controller**/
-(User*) sendUserInfoBack: (SetUp *) SetUpController didFinishWithZoneInfo:(NSString*)zoneInfo didFinishWithAreaInfo:(NSString*) areaInfo
{
User*holdUser;
holdZone = zoneInfo;
holdArea = areaInfo;
holdUser.userZone = holdZone;
holdUser.userArea = holdArea;
return holdUser; /**return this to bufferUser object in method above**/
}
/**Second View controller Interface**/
#Import "User.h"
#class SetUp;
#protocol SetUpControllerDelegate <NSObject>
-(User*) sendUserInfoBack: (SetUp *) SetUpController didFinishWithZoneInfo:(NSString*)zoneInfo didFinishWithAreaInfo:(NSString*) areaInfo;
#property (weak,nonatomic) id<SetUpControllerDelegate>delegate;
#end
/**Second View controller implementation**/
-(IBAction)goToMainView:(id)sender
{
NSString * neededZoneStore = Zone.text;
NSString * areaStore = Area.text;
User * user = [[User alloc] init];
user.userZone = neededZoneStore;
user.userArea = neededAreaStore;
[self.delegate sendUserInfoBack:self didFinishWithZoneInfo:neededZoneStore didFinishWithAreaInfo:neededAreaStore];
[self dismissViewControllerAnimated:YES completion:NULL];
}
[self.delegate sendUserInfoBack:self didFinishWithZoneInfo:neededZoneStore
didFinishWithAreaInfo:neededAreaStore];
So, you probably never need to send self back to the delegate. The delegate already knows about self since the delegate instantiated an object.
You need to change your protocol to this:
-(void)sendUserInfoBack:(User*)user;
Then when you're ready to send this info back, you just call it like this:
[self.delegate sendUserInfoBack:user];
Now in the delegate class, you need to implement the method from the protocol:
-(void)sendUserInfoBack:(User*)user {
//do stuff with user
}
For example, you have a holdZone and holdArea property, so you might write the method to look like this:
-(void)sendUserInfoBack:(User*)user {
self.holdZone = user.userZone;
self.holdArea = user.userArea;
//then perhaps perform a segue, or whatever.
}
As I spend some time looking at your question, I think the problem is becoming clearer.
This line:
bufferUser = [self presentViewController:setUpView animated:YES completion:^{ }];
is wrong. You get your information back from the other class in the delegate method, sendUserInfoBack. And you shouldn't be calling this method except from the other class.
Your -(IBAction)signUpButton method should launch the view, and then that should be the end of the method. Now this view controller is just kind of in standby while the other view (which the first view controller is delegating) waits for its response.
When the newly launched view is finished, it closes itself out, and calls sendUserInfoBack on the original view. That's how you know that modal view is cleared and the original view can carry on performing functions.
So, anything you want to do after the other view is finished, that code should be placed in the sendUserInfoBack method.
It's important to keep in mind that although the delegate implements the delegate methods, the delegate is not the one calling the methods and can not do anything with the return values. A return value on a delegate method is to be used by the object calling the method.
For example, consider when you're implementing a UITableView. The cellForRowAtIndexPath has a return type: UITableViewCell. But where do you receive this return value? You don't. You override this delegate method so that you can customize how the cell is set up. The UITableView class, for which your view controller is a delegate overrides this method... but UITableView calls the method on its delegate, then uses the return value (the cell it just set up) to do more work on its own.
You would only want your sendUserInfoBack to have a return value if you needed to do more work in the SetUpControllerDelegate class after that value returned.
For example, if we did:
- (BOOL)sendUserInfoBack:(User*)user;
Now the delegate that overrides this method can put some logic in to return either YES or NO depending on whatever... and we would then use it something like this in SetUpControllerDelegate:
if([self.delegate sendUserInfoBack:user]) {
// everything's fine, we can dismiss view
[self dismissViewControllerAnimated:YES completion:nil];
} else {
// everything wasn't fine, we need to try again
// pop up a UIAlertView perhaps try to get better info
}
This is just an example.

Remove view controller from another view controller

I am very new to iPhone app development.
I am developing one example application for iPhone emulator using Objective-C++ and std CPP.
I have two views in my application, on some events from CPP code i am displaying second view using following code from the first view controller.
// Defined in .h file
secondViewScreenController *mSecondViewScreen;
// .mm file Code gets called based on event from CPP (common interface function between Objective-C++ and CPP code)
mSecondViewScreen = [[secondViewScreenController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:mSecondViewScreen animated:YES];
I am able to see second view coming on screen, but problem is that i am unable to end/remove second view controller from first view controller.
How can i remove second view controller from first view controller using second view controller's pointer or using any other method.
To remove second view i have following code in second view controller file, which gets called on button click event of second view.
// In .mm of second view controller.
- (IBAction)onEndBtnClicked:(UIButton *)sender
{
[self dismissModalViewControllerAnimated:NO];
[self.navigationController popViewControllerAnimated:YES];
}
Above code works perfectly, when i click on the seconds view's end button it removes the second view controller from the screen and navigets to first view, how can i use same code to remove second view from the first view controller.
I tied to use NSNotificationCenter to send event from first view to second view to call the function onEndBtnClicked but it is not working.
What is the proper way of doing it?
OSX version: 10.5.8 and Xcode version: 3.1.3
In the secondViewController create a protocol like:
#protocol SecondViewScreenControllerDelegate <NSObject>
- (void)secondViewScreenControllerDidPressCancelButton:(UIViewController *)viewController sender:(id)sender;
// Any other button possibilities
#end
Now you have to add a property in the secondViewController class:
#property (weak, nonatomic) id<SecondViewScreenControllerDelegate> delegate;
You sinthesize it in the secondViewController implementation:
#synthesize delegate = _delegate;
Finally all you have to do is implement the protocol in your firstViewController and set the secondViewController properly prior presenting it:
#interface firstViewController : UIViewController <SecondViewScreenControllerDelegate>
...
#implementation firstViewController
- (void)secondViewScreenControllerDidPressCancelButton:(UIViewController *)viewController sender:(id)sender
{
// Do something with the sender if needed
[viewController dismissViewControllerAnimated:YES completion:NULL];
}
Then when presenting the secondViewController from the first:
UIViewController *sec = [[SecondViewController alloc] init]; // If you don't need any nib don't call the method, use init instead
sec.delegate = self;
[self presentViewController:sec animated:YES completion:NULL];
And ready. Whenever you want to dismiss the secondViewController from the first, just call: (inside the secondViewController implementation)
[self.delegate secondViewScreenControllerDidPressCancelButton:self sender:nil]; // Use nil or any other object to send as a sender
All that happens is that you send a pointer of the secondViewController that you can use from the first. Then you can work with it without problem. No C++ needed. In Cocoa you won't need C++. Almost everything can be done with Objective-C, and it's more dynamic.
If there are only two views in your application then use
- (IBAction)onEndBtnClicked:(UIButton *)sender
{
[self dismissModalViewControllerAnimated:NO];
}
remove line below:
[self.navigationController popViewControllerAnimated:YES];
as it is you are dismissing second view then why you want to remove it from first view.

Dismissing modal view controller from app delegate

I am using the facebook SDK to login to my app. If the user is not logged in, the login VC modally appears. Once the user has tapped log in, it alerts the App Delegate if the login was successful. If it was, I want to dismiss the modal login VC. How do I do this from the app delegate?
You could try dismissing the presented ViewController, as something has to present the modal view controller
UINavigationController *navigationController = (id) self.window.rootViewController;
[[navigationController presentedViewController] dismissModalViewControllerAnimated:NO];
If you would want to check if a specific ViewController is being presented (i.e. only dismiss when a certain one is shown) then you could add in a check.
UIViewController *viewController = [navigationController presentedViewController];
if ([viewController isMemberOfClass:[YourViewController class]]) {
[viewController dismissModalViewControllerAnimated:NO];
}
The appDelegate needs some way to know who the hosting viewController is, so it can send the dismiss message. You need to figure out some way to make this happen. One way would be to define an ivar on the appDelegate "callDismissOnMeIfFaceBookFails", and set it when you are in this situation, otherwise its nil.
Note if its nil, the appDelegate can send the dismiss message with no overhead no problems! Use nil messaging to your advantage (I use it all the time). [Aside: I see so much code "if(obj) [obj message];" Don't do the if - just send the message - if obj is nil it has no effect and is handled efficiently!]
EDIT:
So you have a class AppDelegate. In the #interface define a property:
#property (nonatomic, strong) UIViewController *callDismissOnMeIfFaceBookFails;
and in the implementation you #synthesize it. Define a method:
- (void)dismissLoginView
{
[callDismissOnMeIfFaceBookFails dismissModalViewControllerAnimated:YES];
callDismissOnMeIfFaceBookFails = nil; // don't need it now, this unretains it
}
So, before the modal view controller is presented, the presenting object sets the appDelegate property "callDismissOnMeIfFaceBookFails" to itself.
When the user has successfully logged in, the login object sends the message to appDelegate telling it to dismissLoginView.

ipad detect when UIPopoverControllers are dismissed

I have several uiPopoverControllers in my universal iPad app. I now have a requirement to trigger a function once a certain popover has been dismissed. I can do this easily if the user clicks "close" inside the popover, but if they touch the screen to hide the popover, I cannot trigger my function.
I've been googling for some time and cannot seem to find any delegate methods which I might be able to use in my main view controller to capture them. I would love something like didDismissPopoverController - but my guess is it's not available.
IF not, I guess the only thing to do would be to detect the touches and trigger then? Basically I am highlighting a UITableView row and loading the popover. I need to deselect the row - so want to simply call [table reloaddata].
Thanks for any help on this one!
You need to assign a delegate to the UIPopoverController and then implement the - (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController method. For example:
#interface FooController : UIViewController <UIPopoverControllerDelegate> {
// ...
}
// ...
#end
When you instantiate the UIPopoverController (say, for this example, in FooController)...
UIPopoverController *popover = // ...
popover.delegate = self;
Then, you would implement the method:
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
// do something now that it's been dismissed
}
Granted, I haven't tested this but it seems like it should work...
Hope this helps!
You can use the popoverControllerDidDismissPopover delegate method after the following assignment:
self.popoverController.delegate = self;
Note that popoverControllerDidDismissPopover delegate method does not get called if you programmatically call [self.popoverController dismissPopoverAnimated:YES].

UIViewController parentViewController access properties

I know this question has been asked several times and I did read existing posts on this topic but I still need help.
I have 2 UIViewControllers - parent and child. I display the child UIViewController using the presentModalViewController as below:
ChildController *child =
[[ChildController alloc] initWithNibName:#"ChildView" bundle:nil];
[self presentModalViewController:child animated:YES];
[child release];
The child view has a UIPickerView. When user selects an item from UIPickerView and clicks done, I have to dismiss the modal view and display the selected item on a UITextField in the parent view.
In child's button click delegate, I do the following:
ParentController *parent =
(ParentController *)[self.navigationController parentViewController];
[parent.myTextField setText:selectedText];
[self dismissModalViewControllerAnimated:YES];
Everything works without errors. But I don't know how to load the parent view so that it displays the updated UITextField.
I tried
[parent reloadInputViews];
doesn' work. Please help.
Delegation is the way to go. I know some people that may be looking for an easier solution but trust me I have tried others and nothing works better than delegation. So anyone having the same problem, go read up on delegation and follow it step by step.
In your subviewcontroller.h - declare a protocol and declare delegate mthods in it.
#protocol myDelegate
-(void)clickedButton:(subviewcontroller *)subController;
#end
In your subviewcontroller.h, within #interface:
id<myDelegate> delegate;
#property (nonatomic, assign) id<myDelegate> delegate;
NSString *data;
-(NSString *)getData;
In your subviewcontroller.m, synthesize myDelegate. Add the following code to where you want to notify your parentviewcontroller that the subview is done doing whatever it is supposed to do:
[delegate clickedButton:self];
and then handle getData to return whatever data you want to send to your parentviewcontroller
In your parentviewcontroller.h, import subviewcontroller.h and use it's delegate
#import "subviewcontroller.h"
#interface parentviewcontroller : VUIViewController <myDelegate>
{}
In your parentviewcontroller.m, implement the delegate method
- (void)clickedButton:(subviewcontroller *)subcontroller
{
NSString *myData = [subcontroller getData];
[self dimissModalViewControllerAnimated:YES];
[self reloadInputViews];
}
Don't forget memory management!
If a low-memory warning comes in during your modal view's display, the parent's view will be unloaded. Then parent.myTextField is no longer referring to the right text field until the view is reloaded. You can force a reload of the view just by calling parent.view;
However, a better idea might be to have the parent view have a String property that can be set by the child view. Then, when the parent view reappears, put that data into the text field, inside viewWillAppear: for example. You'd want to have the value set to some default value for when the parent view initially shows up too.
-(void) viewWillAppear:(BOOL) animated doesn't get called for me either, exactly when it's a modal view controller. No idea why. Not incorrectly overridden anywhere in this app, and the same problem occurs on the other 2 apps I'm working on. I really don't think it works.
I've used the delegate approach before, but I think that following approach is pretty good as well.
I work around this by adding a private category to UIViewController, like so:
.h file:
#interface UIViewController(Extras)
// returns true if this view was presented via presentModalViewController:animated:, false otherwise.
#property(readonly) BOOL isModal;
// Just like the regular dismissModalViewController, but actually calls viewWillAppear: on the parent, which hasn't been working for me, ever, for modal dialogs.
- (void)dismissModal: (BOOL) animated;
#end
and .m file:
#implementation UIView(Extras)
-(BOOL) isModal
{
return self == self.parentViewController.modalViewController;
}
- (void)dismissModal: (BOOL) animated
{
[self.parentViewController viewWillAppear: animated];
[self dismissModalViewControllerAnimated: animated];
}
#end
which I can now call like this when I want to dismiss the dialog box:
// If presented as a modal view, dismiss yourself.
if(self.isModal)
[self dismissModal: YES];
and now viewWillAppear is correctly called.
And yes, I'm donating a bonus 'isModal' property, so that the modal view can tell how it was being presented, and dismiss itself appropriately.