You press the button, the camera opens, the photo is taken, and the overlay image on your main view is supposed to vanish at the instant the camera screen switches back. If you wrote the hide line directly after the dismiss call, it runs at the wrong moment, because an animated dismissal is asynchronous. This guide shows where the dismissal actually ends, how the completion block hands you that exact instant, and how the same ordering rule keeps overlay and panel state honest around smart cameras and vision controllers.
Why does my overlay hide before the camera view closes?
Because the line you placed after the dismiss call runs at once: an animated dismissal is asynchronous, so the next statement executes while the camera screen is still on screen, a full slide duration before the transition finishes.
Presenting and dismissing a modal view controller starts an animation on the main run loop and returns immediately. The delegate callback that hands you the photo fires while the picker is still frontmost, and every statement you write after the dismiss call executes in that same pass, before the first frame of the slide has rendered. The overlay is therefore already hidden long before the main view returns, so the disappearance lands under the camera screen instead of at the switch you wanted. Capture paths have several such 'when is it done' moments; the gap between a trigger call and the exposure itself is another one, and each stage needs its own completion event (trigger call versus exposure time).
What does the completion block wait for?
The completion block waits for the end of the dismissal animation: UIKit runs it once the camera view controller is fully gone, one default-slide duration after the call, and essentially immediately when you pass false.
UIKit's reference documentation defines the parameter as 'the block to execute after the view controller is dismissed'; it takes no arguments, returns nothing, and nil is a legal value. The block runs on the main thread after the transition has finished, which makes it the one hook that coincides with the camera screen having switched away — exactly the event your overlay change needs. On the default vertical slide that landing point sits the full transition duration after the call; the verification steps below show how to measure it on your own device.
How do I hide the overlay at the moment the camera screen closes?
Move the hide into the completion closure: pass { [unowned self] in self.yagi.hidden = true } as the completion argument of the dismiss call, and the overlay state changes in the same event as the end of the transition.
- Delete the standalone
yagi.hidden = trueline that sits after the dismiss call. - Pass that same line as the completion argument of the dismiss call, as in the listing below.
- Leave the image save call where it is; saving does not need to wait for the transition.
- Group any other changes that must coincide with the close into the same block, so they land in one event.
// BEFORE — the hide runs immediately, a slide-duration early
func imagePickerController(picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
self.dismissViewControllerAnimated(true, completion: nil)
yagi.hidden = true // executes while the camera view is still up
}
// AFTER — the hide runs when the camera view is gone
func imagePickerController(picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
self.dismissViewControllerAnimated(true, completion: { [unowned self] in
self.yagi.hidden = true // executes at the end of the transition
})
}
yagi is the UIImageView outlet from the original code; every other name in the listing is UIKit API, and the class conforms to UIImagePickerControllerDelegate and UINavigationControllerDelegate. On current Swift the same call is spelled dismiss(animated:completion:), and the completion argument behaves identically.
What runs when in the capture callback?
The delegate callback fires at t = 0 ms, the inline hide line runs in the same run-loop turn, and the completion block lands only when the slide has finished — one transition duration in which the camera view is still animating away.
| Time from callback | What happens | Code path |
|---|---|---|
| t = 0 ms |
imagePickerController(didFinishPickingMediaWithInfo:) fires, picker still on screen |
delegate method |
| t ≈ 0 ms | photo saved with UIImageWriteToSavedPhotosAlbum
|
inline |
| t ≈ 0 ms |
dismissViewControllerAnimated(true, completion:) called, slide starts |
inline |
| t ≈ 0 ms | stray hide line executes — the bug | inline |
| t ≈ 300 ms | slide ends, camera view gone, completion closure runs | completion block |
The arithmetic is a single subtraction: the gap between the wrong line and the right one equals the transition duration, which the last table row shows as measured from the callback. That figure is the platform's default slide; other transition styles differ.
Does the same rule apply to a machine-vision HMI or smart camera viewer?
Yes: any animated close of a capture window has the same structure, so overlay changes that must coincide with the window being gone belong in that window's close-completed callback, whether it is a phone camera screen, a smart camera viewer or your own HMI panel.
The pattern appears whenever an operator app drives inspection hardware. You present a capture or setup screen on top of the main panel, close it when the operator confirms, and only then clear the result overlay and re-enable the controls. Driving a smart camera from your own interface is the usual camera-side case; the shop finder lists the range by resolution, interface and shutter. Panel HMIs meet the same sequencing question one step earlier in the chain, when an event sequence such as a screenshot has actually finished; the HMI event sequence and screenshot completion timing reference works that case through in detail.
Everyday case on the camera side: you present the viewer of a compact smart camera from the operator app and reset the panel in the close event.
- Interface
- Fast Ethernet
- Resolution
- Up to 3 MP
- Sensor format
- 1/2.9"
- Shutter
- Global
When the inspection itself runs on a vision controller, your HMI opens and closes capture dialogs around it, and the same close-completed event clears overlay state on the panel.
- LAN ports
- 3-4 LAN ports
- USB 3 ports
- 2 USB 3 ports
- Light outputs
- 4 built-in channels
- Memory
- 8 GB
How do I verify the overlay changes exactly when the transition ends?
Log a timestamp immediately before the dismiss call and another inside the completion block, then read the difference: it should equal the transition duration on the default slide, while a stray inline hide logs at about 0 ms and exposes the bug.
- Log the current system time on the line directly before the dismiss call.
- Log it again as the first statement of the completion closure.
- Run one capture-and-dismiss cycle and subtract the readings; expect the default slide duration.
- Move the hide back inline, run again, and confirm the hide log lands near 0 ms — that is the failure you are guarding against.
- Record the screen or slow the animation down: the overlay must flip when the main view is back in front, not while the modal still covers it.
What else breaks when code follows an animated dismissal?
Three failures recur in practice: a new presentation started inline during the dismissal is dropped with a console warning, view changes made inline race the returning screen's appearance callbacks, and a closure capturing self strongly holds the view controller across the whole slide animation.
- Chained screens: requesting the next present while the dismissal animates is rejected. Move it into the completion block.
- Appearance callbacks: the returning screen's own callbacks fire during the transition, so inline changes can be overwritten by state restoration that runs after them. The completion block lands after all of that.
- Capture semantics: the completion block runs once and is released immediately, so a strong self does not leak; the unowned form still states the intent cleanly, and weak is safer if the screen may already be gone.
- Main thread: the completion runs on the main thread, so heavy decode or file writes there stall the UI. Move fetch and decode off it, as in GUI freezes during image capture.
When should a state change not wait for the completion block?
Separate the model from the pixels: data updates such as clearing the captured image variable or recording the result can run inline at once, while changes the operator must see synchronized with the close — hiding an overlay, enabling buttons — wait for the completion block.
Writing the model early keeps the data consistent even if the transition is interrupted, and it costs nothing because nothing renders from it yet. Keep the capture control disabled until the completion block runs, so a fast second tap cannot fire during the outgoing animation. Device-side state follows its own, different rule: reset camera settings before the next capture begins, as in resetting camera settings programmatically before capture, rather than pegging the reset to a UI close event. When you stop a capture stream instead of closing a view, trust the stop only once it is confirmed — the pattern in stopping a camera stream without closing your app.
Time the overlay to the close-completed event and the panel will always show what the operator expects. To build the station around it, start from the system configurator, compare camera and controller options in the shop finders, or bring the timing question to the contact page — free expert support with every order. More capture-path timing fixes are collected on the systems topic page.
Questions
Does the completion block still run when animated is false?
Yes. The dismissal still completes through the same call, and the block runs afterwards, essentially immediately because there is no animation to wait for. The guarantee that matters survives either value: anything placed in the block executes after the camera view controller has been dismissed, never during it. Only the gap changes — the default slide takes a short, platform-fixed time, while passing false shrinks it to near zero.
Why does the image picker delegate method fire while the camera view is still showing?
That callback is a data event: the photo exists and its info dictionary is ready, but the picker controller is still presented on screen. Dismissal is a separate animated step that you start from inside the callback. That split is exactly why the hide line cannot sit directly after the dismiss call — the callback marks the capture, not the end of the transition.
Do I need [unowned self] in the completion closure?
Not strictly. The completion block runs once and is released right after, so a strong capture does not create a retain cycle. The unowned form keeps the intent explicit and avoids holding the view controller across the transition; weak is the safer choice if the screen might already be deallocated when the block fires.
Where should I put code that must run before the camera opens?
In the completion block of the present call, not after it. Presenting is animated and asynchronous exactly like dismissing, so a line placed after present runs before the camera screen has finished appearing. Anything that must coincide with the camera being ready — enabling a shutter control, starting a timer — belongs in that completion argument, and the slide takes the same default duration there as it does on dismiss.
What changes when the close event comes from a device SDK instead of UIKit?
Machine-vision SDKs and HMI frameworks deliver their own close-completed or stream-stopped events, sometimes on a worker thread. The rule is unchanged — do the overlay change inside that event — with one addition: marshal the call back to the UI thread before touching any view state, because view updates from a background thread are unsafe or rejected.

