What Completion Handlers Should I Write?

2019-07-23 15:09发布

问题:

Having just learned about how to create completion handlers, I understand them in principle, but I don't know how to put the ideas into practice to accomplish what I need.

I have the following general code and storyboard structure: sessionVC (a UIViewController) and its UIView hold a container view with an embed segue to animationVC (also a UIViewController) and its SKView.

From sessionVC I want to run a series of animations in animationVC’s SKView. I’d like to prepare each animation as soon as possible (e.g., while each prior animation is still running), and I’d like each animation to wait for the last one to finish before it begins. See the code, below.

My question is, what do I put in place of the ???s in my code to accomplish the effects that I want (mentioned above, as well as after each *** in the code comments)?

// TO DELEGATE ANIMATION TO animationVC
protocol AnimateContentDelegate: AnyObject {

    prepareContent(_ Content, contentWasPrepared: ???)
    animateContent(animationDidComplete: ???)
    playAnimation()
    pauseAnimation()
}

// CONTROL THE OVERALL SESSION
class sessionVC: UIViewController {

    // INITIALIZE contentArray

    weak var delegate: AnimateContentDelegate?

    override func viewDidLoad {
        super.viewDidLoad()

        delegate = self.childViewControllers[0] as? AnimateContentDelegate

        runSession(contentArray)
    }

    func runSession(_ contentArray) {

        for content in contentArray {

            delegate?.prepareContent(content, contentWasPrepared: ???)

            // ***DON’T START THE NEXT ANIMATION UNTIL contentWasPrepared
            //    DO CONTINUE THE CURRENT ANIMATION, AND ALLOW INTERACTIONS

            delegate?.animateContent(animationDidComplete: ???)

            // ***DON’T START THE NEXT ANIMATION UNTIL animationDidComplete
            //    DO CONTINUE THE CURRENT ANIMATION, AND ALLOW INTERACTIONS
       }
    }

    @IBAction func playOrPause(_ sender: UILongPressGestureRecognizer) {

         if sender == .possible || sender.state == .ended {

            delegate?.playAnimation()

        } else if sender.state == .began {

            delegate?.pauseAnimation()
        }
    }
}

// PREPARE AND ANIMATE CURRENT CONTENT
class animationVC: UIViewController, AnimateContentDelegate {

    // SET UP SKVIEW

    func prepareContent(_ content: Content, prepCompleteHandler: ???) {
       // PREPARE THE CONTENT
       // ***REPORT WHEN IT IS FINISHED
    }

    func animateContent(animationCompleteHandler: ???) {
        // ANIMATE THE CONTENT
        // ***REPORT IF IT RAN TO COMPLETION
   }

    func playAnimation() {
        skView?.scene?.isPaused = false
    }

    func pauseAnimation() {
        skView?.scene?.isPaused = true
    }
}

回答1:

Following the usual conventions, you would declare the functions with a slightly different wording:

prepareContent(_ Content, completionHandler: { () -> Void })
animateContent(completionHandler: { () -> Void })

Then, below SET UP SKVIEW, you would call the delegate functions like this:

delegate?.prepareContent(content, completionHandler: {
    self.delegate.animateContent(completionHandler: {
        // do whatever needs to be done upon completion
    })
})

(You could as well use the trailing closure syntax.) By nesting the delegate calls like this you make sure animation is performed only after preparation has been finished.

The function bodies would look like this:

func prepareContent(_ content: Content, completionHandler: (() -> Void)) {
    // ...
    completionHandler()
}

func animateContent(completionHandler: (() -> Void)) {
    // ...
    completionHandler()
}

And if you prefer to have optional completion handlers, i. e. those which can be nil, change the function like this:

func prepareContent(_ content: Content, completionHandler: (() -> Void)?) {
    // ...
    completionHandler?()
}


回答2:

It should look something like :

func prepareContent(_ content: Content, completionHandler: @escaping (Bool, Any?) -> Void) -> Void {

   var finished : Bool = false
   var output : Any? = nil

   // prepare the content
   // possibly update the output

   //when ready : 
   finished = true
   completionHandler(finished, output)

}

Then you use it :

prepareContent(content: content, completionHandler: { (f, o) in 
   if f {
      if o != nil {
          //do something
      }
      //else handle error
   }
   //else handle error
})

You can adapt it to your needs, adding more or less outputs if needs, error logs, and so on.



回答3:

Do you want something like that ?

protocol AnimateContentDelegate: AnyObject {
    func prepareContent(content : Content, contentWasPrepared: ((Bool) -> Void)?)
    func animateContent(animationDidComplete: ((Bool) -> Void)?)
    func playAnimation()
    func pauseAnimation()
}

class YourObject : AnimateContentDelegate {

func prepareContent(content: Content, contentWasPrepared: ((Bool) -> Void)?) {
    // prepare content
}

func animateContent(animationDidComplete: ((Bool) -> Void)?) {
    // animate content
}

func playAnimation() {

}

func pauseAnimation() {

}

}

Use it like that :

let yourObject = YourObject()
yourObject.animateContent(animationDidComplete: { finished in
    // animation did complete
})