`UIViewController.present` completion handler being called immediately

3.8k Views Asked by At

I am using Xcode 10 Beta 6, so this might just be a bug.


I am trying to present a view controller (colorPickerController) as a popover. Within that view controller I will be able to set some properties, which I want to read once the popover is dismissed.

Here's the code:

Code

In lines 93...97 I define a completion handler.
In line 99 I present the colorPickerController modally, including the completion handler.


When running the code the color picker controller was successfully shown in a popover. But when I tapped outside of the popover (to dismiss it), the callback was not called.

I thought maybe a UIPopoverPresentationController does not dismiss "normally", so I tried manually dismissing the popover before it would do so itself, by calling dismiss in popoverPresentationControllerShouldDismissPopover (line 110).

Now this still didn't work, so I set a breakpoint as seen in the picture, to check if the delegate method is even being called.
That's when I noticed, that when running the app, the completion handler is called right when the popover appears, not when it dismisses.
I was logging Completion handler was called. in the console, before even reaching the breakpoint.

How is this possible?

2

There are 2 best solutions below

0
On BEST ANSWER

The way your code is in your question, your updateColor closure is only getting called when the presentation animation finishes, not when the view controller you are presenting is done with whatever it needs to do.

See the docs for UIViewController.present(_:animated:completion:): https://developer.apple.com/documentation/uikit/uiviewcontroller/1621380-present

completion

The block to execute after the presentation finishes. This block has no return value and takes no parameters. You may specify nil for this parameter.

Note the "after the presentation finishes" (emphasis mine). This means the closure will be executed literally right after the 0.2 seconds of time it takes to animate the presentation of the new view controller up from the bottom of the screen (or however long it takes and in whatever fashion if you're doing some fancy custom presentation animation).

To get a callback for when your new view controller is done doing whatever it needs to do, subclass UIViewController (call it, say, ColorPickerViewController), and use delegation of some sort to notify your current view controller to dismiss the color picker view controller (and, presumably, to tell it what color was picked).

2
On

@TylerTheCompiler is correct, the completion you pass runs after the animation completes. In order to accomplish what you want here's what I recommend:

1) Subclass the UIViewController and make a custom type.

2) Add a property to the new class of type:

var functionToFinish: (() -> Void)

3) Change update color's definition to be:

let updateColor: (() -> Void) = ...

3a) Insert the rest of your function for the ...

4) On line 98 write:

colorPickerController.functionToFinish = updateColor

5) Now, within the subclassed controller, you can call that function from the viewDidDisappear event