In summary
If a user takes a photo with an overlay on VC1 it triggers a notification. The corresponding observer gets removed by my method as a I present VC2.
If the user doesn't take a photo on VC1, the notification observer isn't trigger. The same removal method is called as VC2 is presented but the observer stays active and causes unwanted behaviours with VC2 photo capture.
Full explanation
I have a registration form where a user can take an optional photo of their face. I'm using a simple UIImagePickerController and presenting a 'passport' style overlay.
if sourceType == .camera {
imagePickerController.cameraDevice = .front
let overlay = PassportOverlayView(frame: imagePickerController.view.frame)
imagePickerController.cameraOverlayView = overlay
}
The overlay covers the 'retake' and 'choose' buttons of the UIImagePickerController so I observe the following NSNotifications to remove the overlay when a photo taken, and re-add it if required should the user wish to retake their photo.
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object:nil, queue:nil, using: { note in
self.imagePickerController.cameraOverlayView = nil
})
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidRejectItem"), object:nil, queue:nil, using: { note in
self.imagePickerController.cameraOverlayView = PassportOverlayView(frame: self.imagePickerController.view.frame)
})
Everything works as intended. I remove the notifications before the next UIViewController is pushed onto the stack.
func removeObservers(){
print("remove Observers")
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidRejectItem"), object: nil)
}
If the user has taken a photo and therefore called the observer adding a photo on the next UIViewController, which doesn't require an overlay, works fine.
If however, the user doesn't take a photo on the first UIViewController [which is perfectly fine for the purposes of my app] they run into an issue on the second UIViewController.
In both scenerios the removeObservers() function is called. "remove Observers" is printed to the console on both occasions. Yet when the users tries to take a photo from within the second UIViewController the app crashes as it Unexpectedly found nil while implicitly unwrapping an Optional value and points me to the self.imagePickerController.cameraOverlayView = nil line of the _UIImagePickerControllerUserDidCaptureItem notification.
I understand the error, it's trying to remove a cameraOverlayView that isn't there. What I can't understand is why the observer is still there when I believe it's already been removed. If the user takes the first photo with the cameraOverlayView and triggers the _UIImagePickerControllerUserDidCaptureItem notification there isn't an issue. The observer is removed and subsequent UIImagePickerControllers don't have the same issue.
Any help would be greatly appreciated.
I think I know what your problem is. A retain cycle is causing the observer to not be removed successfully. The reason why you have a retain cycle is because you are using a strong reference to self in the closure where you set your notifications. Here is how you fix it:
Again, you shouldn't have to worry about removing the observers, so try to run your code without removeObserver and it should work.
Hope this helps.