Search code examples
iosuiimagepickercontroller

Calling UIImagePickerController provides black screen


I'm trying to get photo from gallery for sending it to api. When I call AttachNewFileClick(), black screen appears.

@IBAction func AttachNewFileClick(_ sender: Any) {
    if checkPhotoAccess()==true {
        let imagePicker = UIImagePickerController(rootViewController: self)
        imagePicker.delegate = self
        imagePicker.sourceType = .photoLibrary
        imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
        present(imagePicker, animated: true, completion: nil)
    }
}

func checkPhotoAccess() -> Bool {
    let status:PHAuthorizationStatus = PHPhotoLibrary.authorizationStatus();
    switch status {
    case PHAuthorizationStatus.notDetermined:
        PHPhotoLibrary.requestAuthorization({status in
            return
        })
        return checkPhotoAccess()
    case .denied:
        return false
    case .restricted:
        let permissionRequestAlert:UIAlertView = UIAlertView(title: "Доступ к галлерее заблокирован", message: "Приложение не может получить доступ к вашей галлерее для прикрепления фото. Вероятно ранее вы заблокировали эту возможность", delegate: self, cancelButtonTitle: "Отмена", otherButtonTitles: "Открыть настройки")
        permissionRequestAlert.tag = PERMISSION_REQUEST_ALERT_TAG
        permissionRequestAlert.show()
        return false
    case .authorized:
        return true
    }
}

Then I added breakpoint to all exception and got this piece of code highlighted before exception being thrown

libobjc.A.dylib`objc_exception_throw: -> 0x1159b0f11 <+0>: pushq %rbp

and exception in log:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSDictionaryM setObject:forKey:]: key cannot be nil'

I run app in simulator iPhone SE (11.2 Xcode 9.2). Project min iOS version is 8.2. UIImagePickerController is called in NavigationViewController that was showed modally.

How this problem can be solved?


Solution

  • The reason you are crashing is this line:

    let imagePicker = UIImagePickerController(rootViewController: self)
    

    That is not how you create a UIImagePickerController. Just create it! It already has a root view controller of its own; do not attempt to change it. Just say:

    let imagePicker = UIImagePickerController()
    

    Also, your checkPhotoAccess code is fatally flawed:

    func checkPhotoAccess() -> Bool {
        let status:PHAuthorizationStatus = PHPhotoLibrary.authorizationStatus();
        switch status {
        case PHAuthorizationStatus.notDetermined:
            PHPhotoLibrary.requestAuthorization({status in
                return
            })
            return checkPhotoAccess()
        case .denied:
            return false
        case .restricted:
            let permissionRequestAlert:UIAlertView = UIAlertView(title: "Доступ к галлерее заблокирован", message: "Приложение не может получить доступ к вашей галлерее для прикрепления фото. Вероятно ранее вы заблокировали эту возможность", delegate: self, cancelButtonTitle: "Отмена", otherButtonTitles: "Открыть настройки")
            permissionRequestAlert.tag = PERMISSION_REQUEST_ALERT_TAG
            permissionRequestAlert.show()
            return false
        case .authorized:
            return true
        }
    }
    

    To see why, let's eliminate everything except the notDetermined case:

    func checkPhotoAccess() -> Bool {
        PHPhotoLibrary.requestAuthorization({status in
            return
        })
        return checkPhotoAccess()
    }
    

    Now think about the order in which those lines will execute. requestAuthorization is asynchronous. Therefore the order of execution is like this:

    func checkPhotoAccess() -> Bool {
        PHPhotoLibrary.requestAuthorization({status in
            return // 2
        })
        return checkPhotoAccess() // 1
    }
    

    Okay, but then 2 will never execute, because 1 returns first. So we are left with this:

    func checkPhotoAccess() -> Bool {
        return checkPhotoAccess()
    }
    

    But that is clearly insane. We have an infinite recursion.

    The takeaway is that you cannot return a value from checkPhotoAccess, because you need to call requestAuthorization which is asynchronous. The correct way to implement this sort of thing is either to return false in the .notDetermined case, or else (better) to write a checkPhotoAccess method that receives a completion function and that can then call that function if it discovers that we have authorization.


    Also you need to stop using UIAlertView immediately and convert to UIAlertController. UIAlertView was deprecated long ago. The compiler is warning you about this, so listen to what it's telling you!