Here I'd my code:
.h
IBOutlet SecController *SecContr;
.m
[self presentModalViewController:SecContr animated:YES completion:NULL];
The view controller is presented, but its view is not shown. How can I fix this? By the way I typed this on my phone. Also the iboutlet is definitely connected.
Why are you even creating an IBOutlet? You can use this code:
(IBAction)presentNextView:(id)sender
{
SecController *secCont = [[SecController alloc]init];
secCont.ModalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:secCont animated:YES];
[secCont release];
}
and then hook this up to a button, and you're good to go!
There is no presentmodalviewcontroller:animated:completion:.
You're thinking either of:
- (void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated;
or it's replacement:
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0);
The view controller is presented, but its view is not shown
Then your whole question is irrelevantly titled and incorrectly explained. This has nothing whatever to do with how you present the view controller. It has to do with how the view controller obtains its view. You need to set up the relationship between the SecController class and its view. If a view controller can't find its view, it puts up an empty view; that sounds like what you're seeing.
Related
I have already looked through related questions but nothing has solved my problem.
I am attempting to use dismissViewControllerAnimated:animated:completion and presentViewControllerAnimated:animated:completion in succession. Using a storyboard, I am modally presenting InfoController via a partial curl animation.
The partial curl reveals a button on InfoController that I want to initiate the MFMailComposeViewController. Because the partial curl partially hides the MFMailComposeViewController, I first want to dismiss InfoController by un-animating the partial curl. Then I want the MFMailComposeViewController to animate in.
At present, when I try this, the partial curl un-animates, but the MFMailComposeViewController doesn't get presented. I also have a warning of:
Warning: Attempt to present MFMailComposeViewController: on
InfoController: whose view is not in the window hierarchy!
InfoController.h:
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#import <MessageUI/MFMailComposeViewController.h>
#interface InfoController : UIViewController <MFMailComposeViewControllerDelegate>
#property (weak, nonatomic) IBOutlet UIButton *emailMeButton;
-(IBAction)emailMe:(id)sender;
#end
InfoController.m
#import "InfoController.h"
#interface InfoController ()
#end
#implementation InfoController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)emailMe:(id)sender {
[self dismissViewControllerAnimated:YES completion:^{
[self sendMeMail];
}];
}
- (void)sendMeMail {
MFMailComposeViewController *mailController = [[MFMailComposeViewController alloc] init];
if([MFMailComposeViewController canSendMail]){
if(mailController)
{
NSLog(#"%#", self); // This returns InfoController
mailController.mailComposeDelegate = self;
[mailController setSubject:#"I have an issue"];
[mailController setMessageBody:#"My issue is ...." isHTML:YES];
[self presentViewController:mailController animated:YES completion:nil];
}
}
}
- (void)mailComposeController:(MFMailComposeViewController*)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error;
{
if (result == MFMailComposeResultSent) {
NSLog(#"It's sent!");
}
[self dismissViewControllerAnimated:YES completion:nil];
}
Also, if I comment out the [self dismissViewControllerAnimated:YES completion:^{}]; in (IBAction)emailMe, the MFMailComposeViewController animates in but it is partially hidden behind the partial curl. How can I first dismiss the curl and then animate in the MFMailComposeViewController?
Thanks very much!
Edit: Below image of what the view looks like if I comment out [self dismissViewControllerAnimated:YES completion:^{}];
It's a communications issue between view-controllers resulting out of an unclear parent-child view-controller relationship... Without using a protocol and delegation, this won't work properly.
The rule of thumb is:
Parents know about their children, but children don't need to know about their parents.
(Sounds heartless, but it makes sense, if you think about it).
Translated to ViewController relationships: Presenting view controllers need to know about their child view controllers, but child view controllers must not know about their parent (presenting) view controllers: child view controllers use their delegates to send messages back to their (unknown) parents.
You know that something is wrong if you have to add #Class declarations in your headers to fix chained #import compiler warnings. Cross-references are always a bad thing (btw, that's also the reason why delegates should always be (assign) and never (strong), as this would result in a cross-reference-loop and a group of Zombies)
So, let's look at these relationships for your project:
As you didn't say, I assume the calling controller is named MainController. So we'll have:
A MainController, the parent, owning and presenting the InfoController
An InfoController (revealed partially below MainController), owning and presenting a:
MailComposer, which cannot be presented because it would be displayed below the MainController.
So you want to have this:
A MainController, the parent, owning and presenting the InfoController & MFMailController
An InfoController (revealed partially below MainController)
an "Email-Button" in the InfoController's view. On click it will inform the MainController (it's unknown delegate) that it should dismiss the InfoController (itself) and present the MailComposer
an MailComposer that will be owned (presented & dismissed) by the MainController and not by the InfoController
1. InfoController: Defines a #protocol InfoControllerDelegate:
The child controller defines a protocol and has a delegate of unspecified type which complies to its protocol (in other words: the delegate can be any object, but it must have this one method)
#protocol InfoControllerDelegate
- (void)returnAndSendMail;
#end
#interface InfoControllerDelegate : UIViewController // …
#property (assign) id<InfoControllerDelegate> delegate
// ...
#end
2. MainController owns and creates both InfoController and MFMailController
...and the MainController adopts both the InfoControllerDelegate and the MFMailComposeDelegate protocol, so it can dismiss the MFMailComposer again (Note, that doesn't and probably shouldn't need to be strong properties, just showing this here to make it clear)
#interface MainController <InfoControllerDelegate, MFMailComposeViewControllerDelegate>
#property (strong) InfoController *infoController;
#property (strong) MFMailComposeViewController *mailComposer;
3. MainController presents its InfoViewController and sets itself as the delegate
// however you get the reference to InfoController, just assuming it's there
infoController.delegate = self;
[self presentViewController:infoController animated:YES completion:nil];
The 'infoController.delegate = self' is the crucial step. This gives the infoController a possibility to send a message back to the MainController without knowing it ObjectType (Class). No #import required. All it knows, it that it's an object that has the method -returnAndSendMail; and that's all we need to know.
Typically you would create your viewController with alloc/init and let it load its xib lazily.
Or, if you're working with Storyboards and Segues, you probably want to intercept the segue (in MainController) in order to set the delegate programmatically:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// hook in the segue to set the delegate of the target
if([segue.identifier isEqualToString:#"infoControllerSegue"]) {
InfoController *infoController = (InfoController*)segue.destinationViewController;
infoController.delegate = self;
}
}
4. In InfoController, the eMail button is pressed:
When the eMail button is pressed, the delegate (MainController) is called. Note that it's not relevant that self.delegate is the MainController, it's just relevant that it has this method -returnAndSendMail
- (IBAction)sendEmailButtonPressed:(id)sender {
// this method dismisses ourself and sends an eMail
[self.delegate returnAndSendMail];
}
...and here (in MainController!), you'll dismiss the InfoController (clean up because it's the responsibility of the MainController) and present the MFMailController:
- (void)returnAndSendMail {
// dismiss the InfoController (close revealing page)
[self dismissViewControllerAnimated:YES completion:^{
// and present MFMailController from MainController
self.mailComposer.delegate = self;
[self presentViewController:self.mailComposer animated:YES completion:nil];
}];
}
so, what you're doing with the MFMailController is practically the same as with the InfoController. Both have their unknown delegate, so they can message back and if they do, you can dismiss them and proceed with whatever you should to do.
Notes
-dismissViewControllerAnimated:completion: should not be called from the child view controller. In the docs, it says: "The presenting view controller is responsible for dismissing the view controller it presented.". That's why we still need delegation. And it's useful, because the relationships and responsibilities of parents are important! Indeed. You can't create something and then just leave it be. Well, you can, but you shouldn't.
if you wouldn't use a revealing view controller animation, you could chain these Parent (adopting Child Protocol) - Child (defining protocol for parent and adopting protocol for grandchild) - Grandchild (defining protocol for ...
Again: a design where one MainController is owning and presenting all the child viewController is really a bad design. So the solution presented is about protocols and communication and not about putting everything in one MainController
I don't think that blocks as a coding technology free us from the need to define relationships and declare Protocols
Hope that helps
When you present a viewController, the viewController you are presenting from needs to be in the view hierarchy. The two VC's hold pointers to each other in their properties presentingViewController and presentedViewController, so both controllers need to be in memory.
By dismissing then running presenting code from the being-dismissed view controller, you are breaking this relationship.
Instead, you should be doing the presenting of your mailController from the viewController that presented InfoController, after the infoController has been dismissed.
The trad way to do this is via a delegate callback to the underlying viewController which then handles the two steps of dismissing and presenting. But now we use blocks..
Move your sendMeMail method into the VC that presented infoController
Then - in infoController - you can call it in the completion block…
- (IBAction)emailMe:(id)sender {
UIViewController* presentingVC = self.presentingViewController;
[presentingVC dismissViewControllerAnimated:YES completion:^{
if ([presentingVC respondsToSelector:#selector(sendMeMail)])
[presentingVC performSelector:#selector(sendMeMail)
withObject:nil];
}];
}
(you need to get a local pointer to self.presentingViewController because you cannot refer to that property after the controller has been dismissed)
Alternatively keep all of the code in infoController by putting the sendMeMail code in the completion block:
- (IBAction)emailMe:(id)sender {
UIViewController* presentingVC = self.presentingViewController;
[presentingVC dismissViewControllerAnimated:YES completion:^{
MFMailComposeViewController *mailController =
[[MFMailComposeViewController alloc] init];
if([MFMailComposeViewController canSendMail]){
if(mailController) {
NSLog(#"%#", self); // This returns InfoController
mailController.mailComposeDelegate = presentingVC; //edited
[mailController setSubject:#"I have an issue"];
[mailController setMessageBody:#"My issue is ...." isHTML:YES];
[presentingVC presentViewController:mailController
animated:YES
completion:nil];
}
}
}];
}
update/edit
If you put all of the code in the completion block, you should set mailController's mailComposeDelegate to presentingVC, not self. Then handle the delegate method in the presenting viewController.
update 2
#Auro has provided a detailed solution using a delegate method, and in his comments points out that this best expresses the separation of roles. The traditionalist in me agrees, and I do regard dismissViewController:animated:completion as a kludgy and easily misunderstood piece of API.
Apple's docs have this to say:
Dismissing a Presented View Controller
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. For more information, see “Using Delegation to Communicate with Other Controllers.”
Notice that they don't even mention dismissViewController:animated:completion: here, as if they don't have much respect for their own API.
But delegation seems to be an issue that people often struggle with: and can require a lengthy answer... I think this is one of the reasons Apple pushes blocks so hard. In cases where code only needs to execute in one place, the delegate pattern is often regarded by the uninitiated as an overly complex solution to an apparently simple problem.
I suppose the best answer, if you are learning this stuff, is to implement it both ways. Then you will really get a grip on the design patterns in play.
About this,
- (IBAction)emailMe:(id)sender {
[self dismissViewControllerAnimated:YES completion:^{
[self sendMeMail];
}];
}
After dismissing self viewController, you cannot present view controllers from self.
Then what you can do ?
1) Change the button press method,
- (IBAction)emailMe:(id)sender {
[self sendMeMail];
}
2) You can dismiss the self viewController, when the mailViewController is dismissed.
- (void)mailComposeController:(MFMailComposeViewController*)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error;
{
if (result == MFMailComposeResultSent) {
NSLog(#"It's sent!");
}
[controller dismissViewControllerAnimated:NO completion:^ {
[self dismissViewControllerAnimated:YES completion:nil];
}];
}
If you're using storyboards, try checking the transition types you're using on your segues. You will have issues dismissing layers of viewcontrollers you transition to modally. This may be the source of your problems. Try switching them to push instead. Although you can write delegates to accomplish dismissing viewcontrollers it really shouldn't be needed. This is an overly complicated solution. Image if you have a viewcontroller that transitions to dozens of different storyboards, are you going to have dozens of delegates controlling dismissal? It seems suboptimal.
Use this code
[[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:composer animated:YES completion:nil];
Instead of
[self presentViewController:picker animated:YES completion:NULL];
I know there's like 3-5 similar questions here, but non of the answers solves my problem.
I have a ViewController that opens a modal (table)view controller, which opens another one. Both modal view controllers are in fact table view controllers. I'm trying to dismiss both of them from the second one. I tried every accepted answer on similar question, none of them worked for me.
I tried
[self dismissModalViewControllerAnimated:true]
[self.parentViewController dismissModalViewControllerAnimated:true]
[self.parentViewController.parentViewController dismissModalViewControllerAnimated:true]
[self.presentingViewController dismissModalViewControllerAnimated:true]
[self.presentingViewController.presentingViewController dismissModalViewControllerAnimated:true]
When I try options 2, 3 and 5, nothing happens at all. When I use options 1, and 4, I see dismiss modal view animation and the underlying view itself for a moment, and then everything goes back to the second modal view (this time without animation).
I'm starting to think that this have something with the fact that I use tableViewControllers for modal views.
Btw, I'm dismissing modal views in didSelectRowAtIndexPath.
Try this:-
When you dismiss your SecondView set a BOOL flag variable in app delegate file and check that variable in your FirstView's viewWillAppear method whether SecondView was open and close or not. If so, then [self dismissModalViewControllerAnimated:true]
typical model view controller behavior would suggest that you dismiss the modal view controller from the calling view controller rather than from self. not a hard and fast rule, but good practice.
to accomplish this, create a protocol:
#protocol MyModalViewControllerDelegate
- (void)modalViewControllerDidFinish;
#end
and make both the parentViewController and FirstModalViewController be implemntors of this protocol.
#interface FirstModalViewController <MyModalViewControllerDelegate>
then in both FirstModalViewController.h and SecondModalViewController.h, add:
#property id<MyModalViewControllerDelegate> modalViewControllerDelegate
in both parentViewController and FirstModalViewController, right before calling presentModalViewController:... , set the following:
modalViewControllerAboutToAppear.modalViewControllerDelegate = self;
[self presentModalViewController:modalViewControllerAboutToAppear animated:YES];
next, in the SecondModalViewController, in the code where you determine that the item needs to be dismissed, call
[self.modalViewControllerDelegate modalViewControllerDidFinish];
now, in FirstModalViewController, implement the following:
- (void)modalViewControllerDidFinish:(MyModalViewController*)controller {
[self dismissModalViewControllerAnimated:YES]
[self.modalViewControllerDelegate modalViewControllerDidFinish];
}
and finally, in the parent view controller, you should be able to perform:
- (void)modalViewControllerDidFinish:(MyModalViewController*)controller {
[self dismissModalViewControllerAnimated:YES]
}
Since I don't use delegate files, I did the following:
To FirstView add field
BOOL mClose;
To FirstView add method
- (void)close
{
mClose = YES;
}
To FirstView method viewDidAppear add
if (mClose)
{
[self dismissModalViewControllerAnimated:YES];
}
To FirstView method which opens SecondView add
[secondView closeWhenDone:self];
To SecondView add field
FirstView *mParent;
To SecondView add method
- (void)closeWhenDone:(FirstView*)parent
{
mParent = parent;
}
To SecondView method which closes it add
[mParent close];
I have a View-based app. The first view that is loaded has a button which loads another view using this code:
AddPost *addView = [[AddPost alloc] initWithNibName:#"AddPost" bundle:nil];
addView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:addView animated:YES];
What I want to do is provide a button on the view (AddPost) that will let me close it and go back to the original view. How can I do this?
It seems a little strange, but you can actually have addView call:
[self dismissModalViewControllerAnimated:YES];
From the docs:
"The parent view controller is responsible for dismissing the modal view controller it presented using the presentModalViewController:animated: method. If you call this method on the modal view controller itself, however, the modal view controller automatically forwards the message to its parent view controller."
The answer Conrad gave will work perfectly well. In the name of slightly better encapsulation you could put a delegate protocol on addView and have your first view implement this.
So in the header file for your addView controller:
#protocol addViewDelegate <NSObject>
- (void)addViewRequestDismissal;
#end
You will also need an external properly on the addView controller:
#property (assign) id<addViewDelegate> delegate;
Then make your first view controller implement this, so in it's .h file you should have
#interface firstView : NSObject <addViewDelegate> {
}
When you instantiate your addView remember to set the delegate:
addView.delegate = self;
In the addView controller when your button is pressed call back:
- (void)buttonPressed {
[self.delegate addViewRequestDismissal];
}
Finally in your first view remember to implement this method
- (void)addViewRequestDismissal {
[self dismissModalViewControllerAnimated:YES];
}
Hope all goes well with this. Post back if you have any further problems :)
to show a modal uiview out of my mainView I use:
[self presentModalViewController:myController animated:YES];
and in MyController I close that view with:
[self dismissModalViewControllerAnimated:YES];
But how can I know in the mainView that the modal was finished (to redraw my table)?
Currently I set a local variable to YES in my mainView after starting the modal view an react on viewWillAppear:
[self presentModalViewController:myController animated:YES];
_reloadTableData = YES;
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (_reloadTableData) {
_reloadTableData = NO;
[_tableView reloadData];
}
}
Is there a better way to do so ?
Generally speaking, it's not appropriate to dismiss the modal view by the modal view itself.
Instead, you should set your main view as the delegate of the modal view. When you modal view finishes its task, it can let its delegate know and let its delegate dismiss it. This is the very common so-called delegate design pattern in Objective-C.
btw, you may want to consult with some code samples to gain a better understanding of this delegate pattern. I suggest you take a look at one of Xcode's default templates - the Utility Application template. It has a very succinct and simple and straightforward delegate structure built inside.
Can any one help me,
when i am using my UITabBarController delegate it is not working..
I called a delegate method like this..
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
[self.navigationController popToRootViewControllerAnimated:NO];
}
If what you're doing is subclassing a UITabBarController, then... oddly enough... you can get it working by setting itself as a delegate:
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
}
Then the didSelectViewController action will fire normally:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSLog(#"View Changed");
}
Don't forget to add your UITabBarControllerDelegate class to your .h file:
#interface MyTabBarController : UITabBarController <UITabBarControllerDelegate>
#end
If you are using tab bar customizing by extending UITabBarController and trying to change tab bar selected index programmatically then it will not call delegates.
Please see the note inside "UITabBarDelegate":
// Note: called when a new view is selected by the user (but not programmatically)
This might help you
-(void)applicationDidFinishLaunching:(UIApplication *)application {
tabBarController.delegate=self;
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
}
specify
UITabbarcontrollerDelegate in .h file
then
-(void)applicationDidFinishLaunching:(UIApplication *)application {
tabBarController.delegate=self;
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
}
Read the documents to get a deeper understanding of the relationships between navigation controllers, tabBar controllers, and the view and navigation hierarchy.
Then review the code you've provided. Which view/controller is the container? You are popping the navigationController of self, which is not the same as the tabBarController. I don't think you actually need this method if you are looking to switch between tabs.
Try commenting out this method. It is an optional method in the UITabBarController delegate protocol. If you comment it out, you should get the default behavior of the tab controller, which should be to select the appropriate viewController and switch to the new view.
You typically only need to use this method if you want some action taken as you switch between view controllers.