104

I've just upgraded to XCode 4.5 to update my iOS app to run on the 4 inch display for the iPhone 5, but I'm getting a build error saying dismissModalViewControllerAnimated:' is deprecated on the line:

[self dismissModalViewControllerAnimated:NO];

I've tried updating to the recommended overload with a completion handler (but set to NULL) like this:

[self dismissModalViewControllerAnimated:NO completion:NULL];

But then this line throws two errors:

warning: 'TabBarController' may not respond to '-presentModalViewController:animated:completion:'
Instance method '-presentModalViewController:animated:completion:' not found (return type defaults to 'id')

Thanks!

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
Mick Byrne
  • 14,394
  • 16
  • 76
  • 91

6 Answers6

306

The new method is:

[self dismissViewControllerAnimated:NO completion:nil];

The word modal has been removed; As it has been for the presenting API call:

[self presentViewController:vc animated:NO completion:nil];

The reasons were discussed in the 2012 WWDC Session 236 - The Evolution of View Controllers on iOS Video. Essentially, view controllers presented by this API are no longer always modal, and since they were adding a completion handler it was a good time to rename it.

In response to comment from Marc:

What's the best way to support all devices 4.3 and above? The new method doesn't work in iOS4, yet the old method is deprecated in iOS6.

I realize that this is almost a separate question, but I think it's worth a mention since not everyone has the money to upgrade all their devices every 3 years so many of us have some older (pre 5.0) devices. Still, as much as it pains me to say it, you need to consider if it is worth targeting below 5.0. There are many new and cool APIs not available below 5.0. And Apple is continually making it harder to target them; armv6 support is dropped from Xcode 4.5, for example.

To target below 5.0 (as long as the completion block is nil) just use the handy respondsToSelector: method.

if ([self respondsToSelector:@selector(presentViewController:animated:completion:)]){
    [self presentViewController:test animated:YES completion:nil];
} else {
    [self presentModalViewController:test animated:YES];
}

In response to another comment from Marc:

That could be quite a lot of If statements in my application!...I was thinking of creating a category that encapsulated this code, would creating a category on UIViewControler get me rejected?

and one from Full Decent:

...is there a way to manually cause that to not present a compiler warning?

Firstly, no, creating a category on UIViewController in and of itself will not get your app rejected; unless that category method called private APIs or something similar.

A category method is an exceedingly good place for such code. Also, since there would be only one call to the deprecated API, there would be only one compiler warning.

To address Full Decent's comment(question), yes you can suppress compiler warnings manually. Here is a link to an answer on SO on that very subject. A category method is also a great place to suppress a compiler warning, since you're only suppressing the warning in one place. You certainly don't want to go around silencing the compiler willy-nilly.

If I was to write a simple category method for this it might be something like this:

@implementation UIViewController (NJ_ModalPresentation)
-(void)nj_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion{
    NSAssert(completion == nil, @"You called %@ with a non-nil completion. Don't do that!",NSStringFromSelector(_cmd));
    if ([self respondsToSelector:@selector(presentViewController:animated:completion:)]){
        [self presentViewController:viewControllerToPresent animated:flag completion:completion];
    } else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        [self presentModalViewController:viewControllerToPresent animated:flag];
#pragma clang diagnostic pop
    }
}
@end
Community
  • 1
  • 1
NJones
  • 27,139
  • 8
  • 70
  • 88
  • 2
    What's the best way to support all devices 4.3 and above? The new method doesn't work in iOS4, yet the old method is deprecated in iOS6. Rock and a hard place? – Marc Sep 24 '12 at 16:30
  • @Marc I added to my answer to address your concern. – NJones Sep 25 '12 at 23:14
  • Thanks. That could be quite a lot of If statements in my application! I guess the same approach could work when using the 'modalViewController' property. I was thinking of creating a category that encapsulated this code, would creating a category on UIViewControler get me rejected? – Marc Sep 26 '12 at 08:44
  • For the code `if ([self respondsToSelector:@selector(presentViewController:animated:completion:)]){ [self presentViewController:test animated:YES completion:nil]; } else { [self presentModalViewController:test animated:YES]; }` is there a way to manually cause that to not present a compiler warning? – William Entriken May 20 '13 at 22:11
  • @FullDecent Yes you can. I edited my answer with some information on that. – NJones May 22 '13 at 14:08
  • Should the receiver change depending on where this is called? That is, might you use `presentingViewController`? – Jason McCreary Sep 04 '13 at 15:41
  • @JasonMcCreary Generally you can just use `[self dismiss…` and it will automatically forward to the `presentingViewController`. The exception is if you have multiple view controllers presented (in the fashion formerly known as modal); if this is the case you target the `dismiss…` call to the view controller whose view you want to remain after the dismissal, all the views above will be dismissed at once. – NJones Sep 22 '13 at 15:03
6

Now in iOS 6 and above, you can use:

[[Picker presentingViewController] dismissViewControllerAnimated:YES completion:nil];

Instead of:

[[Picker parentViewControl] dismissModalViewControllerAnimated:YES];

...And you can use:

[self presentViewController:picker animated:YES completion:nil];

Instead of

[self presentModalViewController:picker animated:YES];    
Dipang
  • 1,111
  • 12
  • 12
4

[self dismissModalViewControllerAnimated:NO]; has been deprecated.

Use [self dismissViewControllerAnimated:NO completion:nil]; instead.

Fahim Parkar
  • 30,974
  • 45
  • 160
  • 276
Jayprakash Dubey
  • 35,723
  • 18
  • 170
  • 177
4

Use

[self dismissViewControllerAnimated:NO completion:nil];
Mak083
  • 1,160
  • 1
  • 13
  • 33
3

The warning is still there. In order to get rid of it I put it into a selector like this:

if ([self respondsToSelector:@selector(dismissModalViewControllerAnimated:)]) {
    [self performSelector:@selector(dismissModalViewControllerAnimated:) withObject:[NSNumber numberWithBool:YES]];
} else {
    [self dismissViewControllerAnimated:YES completion:nil];
}

It benefits people with OCD like myself ;)

kjoelbro
  • 6,296
  • 4
  • 21
  • 18
  • You should switch the if statement because I believe that a deprecated method will not cause `respondsToSelector` to return false. Thus, the new `dismissViewControllerAnimated:` will not ever be called until a future update where they might possibly remove `dismissModalViewControllerAnimated:` altogether. – Jsdodgers Jul 27 '13 at 22:25
0

Here is the corresponding presentViewController version that I used if it helps other newbies like myself:

if ([self respondsToSelector:@selector(presentModalViewController:animated:)]) {
    [self performSelector:@selector(presentModalViewController:animated:) withObject:testView afterDelay:0];
} else {
    [self presentViewController:configView animated:YES completion:nil];
}
[testView.testFrame setImage:info]; //this doesn't work for performSelector
[testView.testText setHidden:YES];

I had used a ViewController 'generically' and was able to get the modal View to appear differently depending what it was called to do (using setHidden and setImage). and things were working nicely before, but performSelector ignores 'set' stuff, so in the end it seems to be a poor solution if you try to be efficient like I tried to be...

Walter
  • 1