The requirement I want to realize is very simple and common but I cannot get it to work. My example code is as brief as possible and shall achieve the following:
- Start the main view.
- Register a touch event on the main view. This shall launch the subview. (This call has a function defined which should start after completion.)
- The sub view opens. This subview has a return button.
- When this button is pressed, the control shall go back to the main view.
- In the main process, the completion function shall start.
- In the main process, the statement after the subview call (2) shall be executed.
But the execution sequence I get is this:
- Intended step 1 (at main view start)
- Intended step 2 (after tap in main)
- Intended step 3 (at sub view start)
- Intended step 6 (after statement calling subview)
- Intended step 5 (in function of completion parameter)
- Intended step 4 (after return Button is pressed)
Step 3 and Step 4 are realized with self.present… and self.dismiss… because I need to achieve these steps programmatically in my project (which is more complex than this demo version). These methods do work. My problem is the execution order only.
In case the answer to this issue is await and async: Unfortunately I cannot use this since I am limited to Xcode 10 and Swift 4. But I expect that the issue above must be resolvable with older methods as well. Most likely I just lack a fundamental understanding of this process since I am new to this development platform.
Now my Swift code.
Code of main view controller:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("Intended step 1 (at main view start)")
}
@IBAction func gotoSubView(_ sender: UITapGestureRecognizer) {
print("Intended step 2 (after tap in main)")
let nextViewController =
self.storyboard?.instantiateViewController(withIdentifier:
"SubViewController") as! SubViewController
self.present(nextViewController, animated:true,
completion:IntendedAfterReturn)
print("Intended step 6 (after statement calling subview)")
}
func IntendedAfterReturn() {
print("Intended step 5 (in function of completion. parameter)")
}
}
Code of sub view controller:
import UIKit
class SubViewController: UIViewController {
@IBOutlet weak var btnReturn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
print("Intended step 3 (at sub view start)")
}
@IBAction func backToMain(_ sender: UIButton) {
print("Intended step 4 (after return Button is pressed)")
self.dismiss(animated: true, completion: nil)
}
}
2
Answers
Duncan's advice to add a delegate property is the correct answer which brought me to the solution. I would like to share here the code from the question above enhanced with the delegate mechanism in case it can help somebody with a similar problem. In order to understand the delegate/protocol mechanism, I found a great explanation video from Sean Allen: https://www.youtube.com/watch?v=DBWu6TnhLeY
Now the code again. Code of main view controller:
Code of sub view controller:
Now, when i run the program, the output is as follows:
The function
present(_:animated:completion:)
does not let you provide a completion handler that runs when the user dismisses the modal. It calls the completion handler when the animation that displays the modal finishes (when the presented modal is fully on screen.)You either need to add a handler to your first view controller’s viewDidAppear method (plus logic to distinguish between getting called for the first time and the user returning from the sub view controller)
Or you need to add a delegate property to your sub view controller that tells the caller that the user has dismissed the sub view controller. (I recommend this approach. Using viewDidAppear is fragile.)