As a general rule... viewWillAppear()
is not the same things as "view will become visible again."
viewWillAppear()
is part of the view controller lifecycle. Most likely, one would be executing different (or additional) code there, as opposed to when a presented controller is dismissed.
One thing you could try is to have your presenting controller conform to UIViewControllerTransitioningDelegate
, and implement:
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
Here's some example code (please note: it's Example Code Only, not "Production Ready"):
class PresentTestViewController: UIViewController, UIViewControllerTransitioningDelegate {
let infoLabel: UILabel = {
let v = UILabel()
v.backgroundColor = .yellow
v.numberOfLines = 0
return v
}()
let presentButton: UIButton = {
let v = UIButton()
v.setTitle("Test Present", for: [])
v.setTitleColor(.white, for: .normal)
v.setTitleColor(.lightGray, for: .highlighted)
v.backgroundColor = .systemRed
return v
}()
var presentCount: Int = 0
var dismissCount: Int = 0
var dismissReason: String = ""
override func viewDidLoad() {
super.viewDidLoad()
[infoLabel, presentButton].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// put the button at the top
presentButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
presentButton.centerXAnchor.constraint(equalTo: g.centerXAnchor),
presentButton.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.7),
// put the info label below the present button
infoLabel.topAnchor.constraint(equalTo: presentButton.bottomAnchor, constant: 20.0),
infoLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
infoLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
presentButton.addTarget(self, action: #selector(doPresent(_:)), for: .touchUpInside)
view.backgroundColor = .white
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// we're probably doing some view setup tasks here
// that we don't want to ALSO do when a
// presented VC is dismissed
// call UI update func
myViewWillAppear()
}
func myViewWillAppear(fromDismiss: Bool = false) -> Void {
if !fromDismiss {
infoLabel.text = "Info Label"
} else {
var str = infoLabel.text ?? ""
str += "\n" + "Dismiss Count: \(dismissCount) Reason: \(dismissReason)"
infoLabel.text = str
}
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
dismissCount += 1
if let vc = dismissed as? PresentMeViewController {
self.dismissReason = vc.dismissReason
}
myViewWillAppear(fromDismiss: true)
return nil
}
@IBAction func doPresent(_ sender: Any) {
presentCount += 1
var str = infoLabel.text ?? ""
str += "\n" + "Present Count: \(presentCount)"
infoLabel.text = str
let vc = PresentMeViewController()
vc.modalPresentationStyle = .automatic
// set transitioningDelegate
vc.transitioningDelegate = self
present(vc, animated: true, completion: nil)
}
}
class PresentMeViewController: UIViewController {
private let containerView: UIView = {
let v = UIView()
v.backgroundColor = .white
v.layer.borderColor = UIColor.blue.cgColor
v.layer.borderWidth = 2
v.layer.cornerRadius = 16
return v
}()
private let stackView: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 80
return v
}()
private let testLabel: UILabel = {
let v = UILabel()
v.backgroundColor = .green
v.textAlignment = .center
v.numberOfLines = 0
v.text = "This is a label in a stack view in the view controller that will be presented."
return v
}()
private let dismissButton: UIButton = {
let v = UIButton()
v.setTitle("Dismiss Me", for: [])
v.setTitleColor(.white, for: .normal)
v.setTitleColor(.lightGray, for: .highlighted)
v.backgroundColor = .systemBlue
return v
}()
private var timer: Timer!
public var dismissReason: String = ""
override func viewDidLoad() {
super.viewDidLoad()
[stackView, containerView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
stackView.addArrangedSubview(testLabel)
stackView.addArrangedSubview(dismissButton)
containerView.addSubview(stackView)
view.addSubview(containerView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
containerView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
containerView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
containerView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.7),
stackView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 20.0),
stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),
stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -20.0),
])
// dismiss if no action after 5 seconds
timer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(gotTimeout), userInfo: nil, repeats: false)
// dismiss on button tap
dismissButton.addTarget(self, action: #selector(doDismiss(_:)), for: .touchUpInside)
// transparent / translucent background
if self.presentingViewController != nil {
view.backgroundColor = UIColor.gray.withAlphaComponent(0.25)
} else {
view.backgroundColor = .systemYellow
}
// this will change if Timer times-out or
// Dismiss button is tapped
dismissReason = "Dragged"
}
@objc func gotTimeout() {
dismissReason = "Timeout"
dismiss(animated: true, completion: nil)
}
@objc func doDismiss(_ sender: Any) {
dismissReason = "Button Tap"
dismiss(animated: true, completion: nil)
}
// if the timer is still valid (i.e. has not "timed out")
// cancel the timer
override func viewWillDisappear(_ animated: Bool) {
if timer != nil {
timer.invalidate()
}
super.viewWillDisappear(animated)
}
}
The PresentTestViewController
starts like this:

Each time we tap the "Test Present" button, our presentCount
will be incremented, the "Info Label" will be updated, we'll create an instance of our PresentMeViewController
, set its .transitioningDelegate
, and present it:

If we "drag down", or tap the button, or the 5-second Timer times-out, we'll update the dismissReason
var and dismiss the VC.
Back in PresentTestViewController
, our implementation of animationController(forDismissed dismissed: UIViewController)
will increment dismissCount
, get the reason for the dismissal, and update the UI by calling myViewWillAppear(fromDismiss: true)
:
