[iphone] Dismissing a Presented View Controller

I have a theoretic question. Now I'm reading Apple's ViewController guide.

They wrote:

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.

But I can't explain, why I have to create a protocol in presented VC and add delegate varible, create delegate method in presenting VC for dismissing presented VC, instead of a simple call in presented view controller method

[self dismissViewControllerAnimated:NO completion:nil]?

Why is the first choice better? Why does Apple recommend it?

This question is related to iphone ios objective-c cocoa-touch uiviewcontroller

The answer is


Updated for Swift 3

I came here just wanting to dismiss the current (presented) View Controller. I'm making this answer for anyone coming here with the same purpose.

Navigation Controller

If you are using a navigation controller, then it is quite easy.

Go back to the previous view controller:

// Swift
self.navigationController?.popViewController(animated: true)

// Objective-C
[self.navigationController popViewControllerAnimated:YES];

Go back to the root view controller:

// Swift
self.navigationController?.popToRootViewController(animated: true)

// Objective-C
[self.navigationController popToRootViewControllerAnimated:YES];

(Thanks to this answer for the Objective-C.)

Modal View Controller

When a View Controller is presented modally, you can dismiss it (from the second view controller) by calling

// Swift
self.dismiss(animated: true, completion: nil)

// Objective-C
[self dismissViewControllerAnimated:YES completion:nil];

The documentation 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, UIKit asks the presenting view controller to handle the dismissal.

So it works for the presented view controller to call it on itself. Here is a full example.

Delegates

The OP's question was about the complexity of using delegates to dismiss a view.

To this point I have not needed to use delegates since I usually have a navigation controller or modal view controllers, but if I do need to use the delegate pattern in the future, I will add an update.


I think Apple are covering their backs a little here for a potentially kludgy piece of API.

  [self dismissViewControllerAnimated:NO completion:nil]

Is actually a bit of a fiddle. Although you can - legitimately - call this on the presented view controller, all it does is forward the message on to the presenting view controller. If you want to do anything over and above just dismissing the VC, you will need to know this, and you need to treat it much the same way as a delegate method - as that's pretty much what it is, a baked-in somewhat inflexible delegate method.

Perhaps they've come across loads of bad code by people not really understanding how this is put together, hence their caution.

But of course, if all you need to do is dismiss the thing, go ahead.

My own approach is a compromise, at least it reminds me what is going on:

  [[self presentingViewController] dismissViewControllerAnimated:NO completion:nil]

[Swift]

  self.presentingViewController?.dismiss(animated: false, completion:nil)

Swift

let rootViewController:UIViewController = (UIApplication.shared.keyWindow?.rootViewController)!

        if (rootViewController.presentedViewController != nil) {
            rootViewController.dismiss(animated: true, completion: {
                //completion block.
            })
        }

I like this one:

        (viewController.navigationController?.presentingViewController
            ?? viewController.presentingViewController
            ?? viewController).dismiss(animated: true)

Quote from View Controller Programming Guide, "How View Controllers Present Other View Controllers".

Each view controller in a chain of presented view controllers has pointers to the other objects surrounding it in the chain. In other words, a presented view controller that presents another view controller has valid objects in both its presentingViewController and presentedViewController properties. You can use these relationships to trace through the chain of view controllers as needed. For example, if the user cancels the current operation, you can remove all objects in the chain by dismissing the first presented view controller. Dismissing a view controller dismisses not only that view controller but also any view controllers it presented.

So on one hand it makes for a nice balanced design, good de-coupling, etc... But on the other hand it's very practical, because you can quickly get back to a certain point in navigation.

Although, I personally would rather use unwinding segues than try to traverse backwards the presenting view controllers tree, which is what Apple talks about in this chapter where the quote is from.


try this:

[self dismissViewControllerAnimated:true completion:nil];

This is a lot of baloney. Delegation is fine when it is needed but if it makes the code more complex -- and it does -- then there needs to be a reason for it.

I'm sure Apple has its reasons. But it is clearer and more concise to simply have the presented VC do the dismiss unless there is a true reason for doing otherwise and no one here as of today has presented one that I can see.

Protocols are excellent when they're needed but object oriented design was never about having modules communicating unnecessarily with each other.

Tom Love (co-developer of Objective C) once commented that Objective C was "elegant", "small", "crisp" and "well-defined" (when comparing with C++). Easy for him to say. Delegation is a useful feature that seems to have been over-used "just because", and while I like working in the language, I dread the idea of felling compelled to use unnecessary syntax to make things more complex than they have to be.


This is for view controller reusability.

Your view controller shouldn't care if it is being presented as a modal, pushed on a navigation controller, or whatever. If your view controller dismisses itself, then you're assuming it is being presented modally. You won't be able to push that view controller onto a navigation controller.

By implementing a protocol, you let the parent view controller decide how it should be presented/pushed and dismissed/popped.


One point is that this is a good coding approach. It satisfies many OOP principles, eg., SRP, Separation of concerns etc.

So, the view controller presenting the view should be the one dismissing it.

Like, a real estate company who gives a house on rent should be the authority to take it back.


In my experience, it comes in handy when you need to dismiss it from any ViewController you want and perform different tasks for each viewcontroller that dismisses it. Any viewController that adopts the protocol can dismiss the view in it's own way. (ipad vs iphone, or passing different data when dismissing from different views, calling different methods when dismissing, etc..)

Edit:

So, to clarify, if all you ever want to do is dismiss the view, I see no need to setup the delegate protocol. If you need to do different things after you dismiss it from different presenting view controllers, It would be your best way to go using the delegate.


You can Close your super view window

self.view.superview?.window?.close()


If you are using modal use view dismiss.

[self dismissViewControllerAnimated:NO completion:nil];

In addition to Michael Enriquez's answer, I can think of one other reason why this may be a good way to protect yourself from an undetermined state:

Say ViewControllerA presents ViewControllerB modally. But, since you may not have written the code for ViewControllerA you aren't aware of the lifecycle of ViewControllerA. It may dismiss 5 seconds (say) after presenting your view controller, ViewControllerB.

In this case, if you were simply using dismissViewController from ViewControllerB to dismiss itself, you would end up in an undefined state--perhaps not a crash or a black screen but an undefined state from your point of view.

If, instead, you were using the delegate pattern, you would be aware of the state of ViewControllerB and you can program for a case like the one I described.


Swift 3.0 //Dismiss View Controller in swift

self.navigationController?.popViewController(animated: true)
dismiss(animated: true, completion: nil)

Examples related to iphone

Detect if the device is iPhone X Xcode 8 shows error that provisioning profile doesn't include signing certificate Access files in /var/mobile/Containers/Data/Application without jailbreaking iPhone Certificate has either expired or has been revoked Missing Compliance in Status when I add built for internal testing in Test Flight.How to solve? cordova run with ios error .. Error code 65 for command: xcodebuild with args: "Could not find Developer Disk Image" Reason: no suitable image found iPad Multitasking support requires these orientations How to insert new cell into UITableView in Swift

Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to objective-c

Adding a UISegmentedControl to UITableView Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Warp \ bend effect on a UIView? Use NSInteger as array index Detect if the device is iPhone X Linker Command failed with exit code 1 (use -v to see invocation), Xcode 8, Swift 3 ITSAppUsesNonExemptEncryption export compliance while internal testing? How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem? Change status bar text color to light in iOS 9 with Objective-C

Examples related to cocoa-touch

Include of non-modular header inside framework module Move textfield when keyboard appears swift Create space at the beginning of a UITextField Navigation bar with UIImage for title Generate a UUID on iOS from Swift How do I write a custom init for a UIView subclass in Swift? creating custom tableview cells in swift How would I create a UIAlertView in Swift? Get current NSDate in timestamp format How do you add an in-app purchase to an iOS application?

Examples related to uiviewcontroller

How to set Status Bar Style in Swift 3 UIView touch event in controller How to lock orientation of one view controller to portrait mode only in Swift Programmatically navigate to another view controller/scene Adding a view controller as a subview in another view controller Changing the Status Bar Color for specific ViewControllers using Swift in iOS8 Get top most UIViewController Instantiate and Present a viewController in Swift How to check if a view controller is presented modally or pushed on a navigation stack? Add a UIView above all, even the navigation bar