skip to Main Content

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:

  1. Start the main view.
  2. Register a touch event on the main view. This shall launch the subview. (This call has a function defined which should start after completion.)
  3. The sub view opens. This subview has a return button.
  4. When this button is pressed, the control shall go back to the main view.
  5. In the main process, the completion function shall start.
  6. 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


  1. Chosen as BEST ANSWER

    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:

    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
            nextViewController.selectionDelegate = self
            self.present(nextViewController, animated:true, completion:nil)
        }
    }
    
    extension ViewController: SubViewSelectionDelegate {
        func IntendedAfterReturn(text: String) {
            print("Intended step 5 (Text from subview is: (text))")
        }
        
        func IntendedAfterReturnAlso(text: String) {
            print("Intended step 6  (Text from subview is: (text))")
        }
    }
    

    Code of sub view controller:

        import UIKit
    
    protocol SubViewSelectionDelegate {
        // Since we have only a primitive example here with 1 button, the function parameter would even not be necessary. But we include a parameter for demonstration.
        func IntendedAfterReturn(text: String)
        func IntendedAfterReturnAlso(text: String)
    }
    
    class SubViewController: UIViewController {
     
        var selectionDelegate: SubViewSelectionDelegate!
        
        
        @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)")
            selectionDelegate.IntendedAfterReturn(text: "For first action after return button tap")
            selectionDelegate.IntendedAfterReturnAlso(text: "For second action after return button tap")
            self.dismiss(animated: true, completion: nil)
        }
        
    }
    

    Now, when i run the program, the output is as follows:

    • Intended step 1 (at main view start)
    • Intended step 2 (after tap in main)
    • Intended step 3 (at sub view start)
    • Intended step 4 (after return Button is pressed)
    • Intended step 5 (Text from subview is: For first action after return button tap)
    • Intended step 6 (Text from subview is: For second action after return button tap)

  2. 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.)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search