Send message to parentViewController after dismissing UIViewController - iphone

This should be simple for some of you.
I am dismissing a UIViewController that is presented modally by another UIViewController from within itself. I am doing this using
[self dismissViewControllerAnimated:YES
completion:^{
// Want to access presentingViewController here.
}];
I tried logging self's navigationController, parentViewController and presentingViewController, but they all return null.
Apple's doc says :
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.
According to this, the message is sent to the presentingViewController. I want to know how to catch this message. Is there a delegate call or how can I go about finding if the presentedViewController was dismissed.

There is no public API that would tell you this. You could listen to viewWillAppear: and figure out this way that the presented view controller is being dismissed, but that is messy. I will recommend what I always recommend and is considered best practice: never dismiss a view controller from itself! Create a delegate protocol for your presented view controller, set the delegate to the presenting view controller, and when there is a need to dismiss the presented from the presenting, call the delegate.
Why not put aside the presenting view controller, and access it inside the block after dismissal?
__weak id weakPresentingViewController = self.presentingViewController;
[self dismissViewControllerAnimated:YES completion: ^
{
id strongPresentingViewController = weakPresentingViewController;
if(strongPresentingViewController == nil) return;
//Do what you will with the presenting view controller.
}
This is the least error prone method, taking into account your current implementation. If the presenting view controller gets released before your block is called, you will not hit a bad access crash. Inside the block, you capture it strongly to make sure it is not released in the middle of your code.

Related

Accessing parentviewcontroller

I have a cancel button, when i press cancel button then i pop my current viewcontroller. Before popping my controller, i want to access one member (which is a class Student) of previous view controller. So i am doing this way:
StudentProfileViewController *controller = (StudentProfileViewController*)self.parentViewController;
NSLog(#"%#", controller.student);
My app crashes on line NSLog, error is this:
[UINavigationController student]: unrecognized selector sent to instance 0x6865180
Strange part is its says "[UINavigationController student]" but my controller is UIViewController.
Can anyone shed a light on this. I know some silly mistake is being done.
Thanks
The parentViewController would return the controller you are looking for only if you had presented modally from that view in the first place. It looks to be that you are trying to reference the previous controller in the stack, not the presenting view.
In your case, the parentViewController is the navigationController if that is how you presented. You are casting it to the controller class you wish it to be but that doesn't make it so.
More appropriate method would be to have passed the object you wish to reference in the init method or, most preferably, make a delegate method to tell the former view when this controller is complete, then let the former view react as intended.

opening and closing other UIViewControllers - any other approaches than to use protocol & delegate?

The normal way to open another screen from within a FirstVC screen, so one can close it again is like this:
SecondVC *secondVC = [[SecondVC alloc] initWithNibName:#"SecondVC" bundle:nil];
secondVC.delegate = self; //needed to dismiss
[self presentModalViewController: secondVC animated: YES];
while the SecondVC.m has to import a protocol that declares the method called to close the SecondVC
So I always have to create a protocol file SecondVCProtocol.h which basically looks like this:
#protocol SecondVCProtocol <NSObject>
-(void)secondVCDidFinish;
#end
Then in SecondVC.m I need to import this SecondVCProtocol.h file and now can finally call
[self.delegate secondVCDidFinish]
I have just completed another Android app and beeing back in the iOS world, I find this rather cumbersome. - needing to define such a protocol in a separate file & needing to use a delegate - all just to do the most normal task like closing a screen...
Isn't there an easier less complex way or is this just the way it has to be done?
for example like [self dismiss] in SecondVC - no delegate, no protocol - wouldn't his be really nice?
Many thanks!
You can just call
dismissViewControllerAnimated:completion:
on the presented viewcontroller, although it is not exactly best practice.
From Apple's documentation:
The presenting view controller is responsible for dismissing the view
controller it presented. If you call this method on the presented view
controller itself, it automatically forwards the message to the
presenting view controller.
Also from Apple's documentation though (http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html)
When it comes time to dismiss a presented view controller, the
preferred approach is to let the presenting view controller dismiss
it. In other words, whenever possible, the same view controller that
presented the view controller should also take responsibility for
dismissing it. Although there are several techniques for notifying the
presenting view controller that its presented view controller should
be dismissed, the preferred technique is delegation.
What you describe is not the easiest pattern. Actually you should do something very similar to what you suggested would be nice. When SecondVC is ready to be dismissed it just calls, for example:
[self dismissViewControllerAnimated:YES completion:NULL];
From the UIViewController documentation:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.

How can I check and determine which view controller in storyboard is currently showing?

If the application was previously in the background, when applicationDidBecomeActive is called,how can I optionally refresh the user interface ?
I expect the current view controller is always the initial view controller in storyboard.I can't get the pointer to storyboard in my main AppDelegate.m .
How can I check and determine which view controller is currently showing ?
If you just want a pointer to your storyboard then you can do this:
UIStoryboard *sb = [[[self window] rootViewController] storyboard];
where rootViewController is actually the initial view controller of your storyboard. If you expect this controller to be the current controller (as you say) then you're good to go, but if you want to actually update your UI, then I guess you could post a custom notification from the delegate (inside applicationDidBecomeActive), and register each controller that may be interested to catch it so it can update its UI (or just add self as observer for UIApplicationDidBecomeActiveNotification to catch the same one that your delegate catches...).
PS. If your rootViewController is a UINavigationController you can get the currently showing controller like this:
id currentController = [[[self window] rootViewController] visibleViewController];

How dismiss a viewController with storyboard from a push segue?

I don't know why dismissViewControllerAnimated:completion:. I just want to do it.
I start with a
[self performSegueWithIdentifier:#"my_segue" sender:self];
But is I call the dismiss than nothing happens. I can create another segue, but it create a new view controller.
My question is: How dismiss the performSegueWithIdentifier:sender:?
Do you have a navigationBar in the viewController that's calling:
[self performSegueWithIdentifier:#"my_segue" sender:self];
If so, you'll need to use:
[self.navigationController popViewControllerAnimated:YES];
to pop the view off the stack. There's one segue call, but the framework seems to call:
presentViewController:animated:completion:
or:
pushViewController:animated:
as appropriate.
Ray
You could just call
[self dismissViewControllerAnimated:YES completion:nil];
from the view controller since the view controller was pushed by segue.
[my_segue_view_controller dismissModalViewControllerAnimated: YES] ?
(not sure, but it works in my practice)
performSegueWithIdentifier:sender: itself isn't dismissed, that's just the method that's called to initiate a named segue. What happens in the segue is of more interest.
You're right that you should call dismissViewControllerAnimated:completion:, and it should be called by the presenting view controller, which has previously called the presented view controller using presentViewController:animated:completion:. For more info, see the UIViewcontroller docs.
Use the below code for Swift 4 version
self.navigationController?.popViewController(animated: true)

How is a delegate object called?

my protocol:
#protocol ElectricalSystemEngineDelegate
-(void)didRequestMainMenu:(id)sender;
#end
I designed this protocol in order to handle dismissal of a modal View Controller inside my rootView controller. My rootView controller adopts this protocol and is declared as follows:
#import "ElectricalSystemEngineDelegate.h"
#interface RootViewController: UIViewController <ElectricalSystemEngineDelegate>
//other ivars & methods including instantiation of my modalViewController.
I use an out-of-the-box:
-(IBAction)displayElectricalViewController
-to display the modal controller... which works fine. I am, however, confused on how to proceed further with the implementation of this protocol to handle dismissal of the controller..
//The implementation of my root view controller.
-(void)didRequestMainMenu:(id)sender {
[self dismissModalViewControllerAnimated: YES];
}
Obviously, I correctly implemented the protocol by using the prescribed method. I want the method to dismiss my view controller when called. I also want it to be called by the tapping of a back button in the modalViewController.
As the apple docs say, "In some cases, an object might be willing to notify other objects of its actions so that they can take whatever collateral measures might be required." My goal is for my ElecticalViewController to notify its parent (the RootViewController) that it should be dismissed. That dismissal should be triggered by tapping the back button. How does the object accomplish this notification?
You need to add id <ElectricalSystemEngineDelegate> delegate property to your ElectricalViewController.
Then you need to assign self (RootViewController) to that delegate after you created ElectricalViewController.
Then you call [delegate didRequestMainMenu] when you dispose ElectricalViewController.
Then you need to create a method didRequestMainMenu to your RootViewController.