Keywords: iOS | UIViewController | Modal Dismissal Detection
Abstract: This article delves into how to effectively detect the dismissal event of a child view controller (VC2) after it is presented by a parent view controller (VC1) in iOS development. Addressing scenarios where VC2 acts as a "black box" without direct callbacks, it systematically analyzes various solutions, including using the isBeingDismissed property, overriding the dismissViewControllerAnimated method, leveraging closure properties, and the UIViewControllerTransitioningDelegate protocol. Focusing on the best practice—implementing decoupled communication via closure properties—the article explains its workings, code implementation, and advantages in detail, while comparing other methods' applicability and limitations, providing comprehensive technical guidance for developers.
In iOS app development, modal presentation and dismissal between view controllers (UIViewController) are common interaction patterns. However, after a parent view controller (hereafter VC1) presents a child view controller (hereafter VC2), VC1 often needs to perceive when VC2 is dismissed to execute subsequent logic, such as updating UI states or processing data. If VC2 is a "black box"—meaning its internal implementation is unknown or unmodifiable—detecting dismissal events becomes complex. Based on discussions from Stack Overflow, this article systematically outlines several effective detection methods, with an in-depth analysis centered on the closure property approach.
Problem Background and Challenges
Assume VC1 presents VC2 via the presentViewController:animated:completion: method, and VC2 contains a "cancel" button that triggers dismissViewControllerAnimated:completion: to close itself. Since VC2 is a black box, VC1 cannot directly receive its dismissal callbacks, making it difficult for VC1 to respond to dismissal events. Developers have attempted to override the dismissViewControllerAnimated:completion: method in VC1 but found it uncalled, as per iOS documentation, dismissal operations are handled by the presenting view controller; when VC2 calls dismissViewControllerAnimated:completion:, the system requests VC1 to process it, but overridden methods may fail due to call chain issues.
Solution 1: Using the isBeingDismissed Property
A straightforward method involves utilizing the isBeingDismissed boolean property of UIViewController. In VC1, one can override the viewWillDisappear method to check this property and determine if dismissal is occurring. Example code:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isBeingDismissed {
// Perform post-dismissal actions
}
}
This approach is suitable for scenarios where VC1 itself is being dismissed, but it cannot directly detect VC2's dismissal, as isBeingDismissed pertains to the current controller. Thus, it is more applicable when VC1 needs to sense its own dismissal rather than monitoring a child controller.
Solution 2: Overriding the dismissViewControllerAnimated Method
According to iOS mechanisms, dismissal operations should be handled by the presenting controller. Theoretically, overriding dismissViewControllerAnimated:completion: in VC1 can intercept dismissal requests. However, practice shows that when VC2 calls dismissViewControllerAnimated:completion:, the system may not directly trigger VC1's overridden method. A workaround is to modify VC2's code to call self.presentingViewController?.dismissViewControllerAnimated(animated:completion:), thereby invoking VC1's method directly. Yet, this requires VC2 to be modifiable, contradicting the black-box assumption, limiting its applicability.
Solution 3: Closure Property (Recommended Method)
The best practice is to implement decoupled communication using closure properties. This method does not rely on iOS internal mechanisms but uses custom callbacks to notify dismissal events. Specific steps include:
- Declare a closure property in VC2, e.g.,
onDoneBlock, to store callback functions. - When VC1 presents VC2, set this closure property to define actions post-dismissal.
- In VC2's "cancel" button action, invoke the closure to trigger the callback.
Example code:
// Declare closure property in VC2
var onDoneBlock: ((Bool) -> Void)?
// Set closure and present VC2 in VC1
let vc2 = VC2()
vc2.onDoneBlock = { result in
// Handle dismissal event, result can indicate status (e.g., cancelled, succeeded)
vc2.dismissViewControllerAnimated(true, completion: nil)
}
self.presentViewController(vc2, animated: true, completion: nil)
// Invoke closure in VC2's cancel button action
@IBAction func cancelButtonTapped(_ sender: Any) {
onDoneBlock?(true) // Pass true to indicate cancellation
}
This method's advantages include: it completely decouples VC1 and VC2, with VC2 unaware of VC1's specific implementation, merely notifying events via closures; additionally, closures can be extended with parameters to convey more information (e.g., action type or data). This aligns with object-oriented design principles, enhancing code maintainability and reusability.
Solution 4: Using UIViewControllerTransitioningDelegate
Another advanced solution leverages the UIViewControllerTransitioningDelegate protocol. By setting VC2's transitioningDelegate to VC1, one can implement the animationController(forDismissed:) method in VC1 to detect dismissal events. Example:
// Set delegate and implement method in VC1
vc2.transitioningDelegate = self
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if dismissed === vc2 {
print("VC2 was dismissed")
}
return nil
}
This approach is suitable for scenarios requiring custom transition animations but is more complex and may interfere with default system behavior, thus generally serving as a supplementary method.
Summary and Recommendations
Multiple methods exist for detecting modal view controller dismissal, with selection depending on specific needs: if VC2 is modifiable and the scenario simple, overriding dismissViewControllerAnimated or using isBeingDismissed may suffice; for black-box cases, closure properties are the most flexible and reliable solution, promoting code decoupling and extensibility; while UIViewControllerTransitioningDelegate suits advanced animation control. In practical development, it is recommended to prioritize closure properties, as they balance usability and functionality, adapting to various business logics. Through this article's analysis, developers can more effectively handle view controller interactions in iOS.