PHImageManager Error

The function is called from the previous viewController, the code compiles succesfully. When I run it and click the tableView row that calls this function, I receive "Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value", underlining the "targetSize" causing the error. After I set a debug breakpoint on the "targetSize" and step over the error, it takes me back to the "photoImageView" outlet(which is properly connected to the storyboardy) then back to the "targetSize"


Any help please ?


public func chooseLastImage(_ sender: Any) {


let scale = UIScreen.main.scale

let targetSize: CGSize? = CGSize(width: photoImageView.bounds.width * scale, height: photoImageView.bounds.height * scale)


let options = PHImageRequestOptions()

options.isNetworkAccessAllowed = true

options.deliveryMode = .highQualityFormat



let imgManager = PHImageManager.default()

let fetchOptions = PHFetchOptions()

fetchOptions.sortDescriptors = [NSSortDescriptor(key:"lastCreationDate", ascending: true)]

let fetchResult: PHFetchResult<PHAsset> = PHAsset.fetchAssets(with: (.image), options: fetchOptions)


imgManager.requestImage(for: fetchResult.lastObject!, targetSize: targetSize!, contentMode: .default, options: options) { (result, info) in

guard let result = result else { return }

DispatchQueue.main.async(execute: {

self.photoImageView.image = result

})


}


}

}

Replies

Question is: why is targetSize nil ?

In addition, why do you make it optional ?


Could you instrument your code, as proposed below:



public func chooseLastImage(_ sender: Any) {

    let scale = UIScreen.main.scale
    let targetSize: CGSize? = CGSize(width: photoImageView.bounds.width * scale, height: photoImageView.bounds.height * scale)
    print("targetSize", targetSize)          // Is it nil ? If not, what is its value

    let options = PHImageRequestOptions()
    options.isNetworkAccessAllowed = true
    options.deliveryMode = .highQualityFormat

    let imgManager = PHImageManager.default()
    let fetchOptions = PHFetchOptions()
    fetchOptions.sortDescriptors = [NSSortDescriptor(key:"lastCreationDate", ascending: true)]
    let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: (.image), options: fetchOptions)

    print("targetSize for request", targetSize)          //What is its value 
    imgManager.requestImage(for: fetchResult.lastObject!, targetSize: targetSize!, contentMode: .default, options: options) { (result, info) in 
             
          guard let result = result else { return }

          DispatchQueue.main.async(execute: {
            self.photoImageView.image = result
          })

    }
  }
}


Please confirm error is on line 17.


And tell what you get as print log.

I appreciate your revert Claudia, I set targetSize constant as an optional to solve the error but it didn't work. I ran your proposed code, I got the same error message on line 04 "targetSize". Then, I set a breakpoint on line 04 "targetSize" and after I stepped into it, the error pointed out the "photoImageView" outlet.

Btw., I set the constraints of "photoImageView" on the storyboard. The "targetSize" constant can be revoked but as far as I see, the error is caused by the photoImageView.

As error is line 4, please instrument more, adding lines 04 and 05.

Please report exactly the logs you get




public func chooseLastImage(_ sender: Any) {

    let scale = UIScreen.main.scale
    print("photoImageView", photoImageView)
    print("photoImageView.bounds", photoImageView.bounds)

    let targetSize: CGSize? = CGSize(width: photoImageView.bounds.width * scale, height: photoImageView.bounds.height * scale)
    print("targetSize", targetSize)          // Is it nil ? If not, what is its value

    let options = PHImageRequestOptions()
    options.isNetworkAccessAllowed = true
    options.deliveryMode = .highQualityFormat

    let imgManager = PHImageManager.default()
    let fetchOptions = PHFetchOptions()
    fetchOptions.sortDescriptors = [NSSortDescriptor(key:"lastCreationDate", ascending: true)]
    let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: (.image), options: fetchOptions)

    print("targetSize for request", targetSize)          //What is its value
    imgManager.requestImage(for: fetchResult.lastObject!, targetSize: targetSize!, contentMode: .default, options: options) { (result, info) in
        
          guard let result = result else { return }

          DispatchQueue.main.async(execute: {
            self.photoImageView.image = result
          })

    }
  }
}

Sure, here are the logs; (this time the error is on "print("photoImageView", photoImageView)" Regarding this viewController, on the same photoImageView outlet, I also access my photo library from the previous viewController without any error. I've been having trouble with accessing the latest photo in the library as the log prints photoImageView as nil.


2020-03-22 17:34:31.197852+0300 Stitch[60446:1861363] [Warning] Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a table view cell's content view. We're considering the collapse unintentional and using standard height instead. Cell: <Stitch.PhotoPopUpCell: 0x7ff73c89c5a0; baseClass = UITableViewCell; frame = (0 0; 341.667 44); clipsToBounds = YES; autoresize = W; layer = <CALayer: 0x6000029609c0>>

photoImageView nil

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file /Users/sedatonurduzova/Desktop/Xcode Projects/iOS Photos Framework/Manipulate Assets to Generate Collages/P02E04 Observe Photo Library Changes to Update UI/end/Stitch/Effect View Controllers/PhotoViewController.swift, line 67

2020-03-22 17:34:39.737915+0300 Stitch[60446:1861363] Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file /Users/sedatonurduzova/Desktop/Xcode Projects/iOS Photos Framework/Manipulate Assets to Generate Collages/P02E04 Observe Photo Library Changes to Update UI/end/Stitch/Effect View Controllers/PhotoViewController.swift, line 67

(lldb)

My previous message with the full logs is being moderated. This time the error is on "print("photoImageView", photoImageView)" Regarding this viewController, on the same photoImageView outlet, I also access my photo library from the previous viewController without any error. I've been having trouble with accessing the latest photo in the library as the log prints photoImageView as nil.


photoImageView nil

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file

What is exactly the error message you get ?

Are you sure it is on line 04

print("photoImageView", photoImageView)

and not on line 05

print("photoImageView.bounds", photoImageView.bounds)


Please show more code (the full code of the class where photoImageView is declared, so that we can understand:

- where and how photoImageView is declared

- the cause of error

I apologise, you are right the error is on line 05; print("photoImageView.bounds", photoImageView.bounds)


The complete PhotoViewController;


import Foundation

import UIKit

import Photos


class PhotoViewController: UIViewController {


@IBOutlet weak var photoImageView: UIImageView!


var imagePicker = UIImagePickerController()


override func viewDidLoad() {

super.viewDidLoad()


imagePicker.delegate = self

}


func chooseImage(_ sender: Any) {


if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {

imagePicker.sourceType = .photoLibrary

imagePicker.allowsEditing = true

self.present(imagePicker,animated: true, completion: nil)

}

}


func accessVideo(_ sender: Any) {


if UIImagePickerController.isSourceTypeAvailable(.camera) {

imagePicker.sourceType = .camera

self.present(imagePicker, animated: true, completion: nil)

} else {

print("Camera not available")

}

}


public func chooseLastImage(_ sender: Any) {


let scale = UIScreen.main.scale

print("photoImageView", photoImageView)

print("photoImageView.bounds", photoImageView.bounds)


let targetSize: CGSize? = CGSize(width: photoImageView.bounds.width * scale, height: photoImageView.bounds.height * scale)

print("targetSize", targetSize) // Is it nil ? If not, what is its value


let options = PHImageRequestOptions()

options.isNetworkAccessAllowed = true

options.deliveryMode = .highQualityFormat


let imgManager = PHImageManager.default()

let fetchOptions = PHFetchOptions()

fetchOptions.sortDescriptors = [NSSortDescriptor(key:"lastCreationDate", ascending: true)]

let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: (.image), options: fetchOptions)


print("targetSize for request", targetSize) //What is its value

imgManager.requestImage(for: fetchResult.lastObject!, targetSize: targetSize!, contentMode: .default, options: options) { (result, info) in

guard let result = result else { return }


DispatchQueue.main.async(execute: {

self.photoImageView.image = result

})


}

}

}


extension PhotoViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {


func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {

if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {

photoImageView.image = image

}

dismiss(animated: true, completion: nil)

}


func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {

dismiss(animated: true, completion: nil)

}


}

Pretty likely, photoImageView IBOutlet connection is wrong, or has been damaged.


To correct :

- In Interface Builder, select photoImageView and open Connection inspector (or right click on the object)

- You should see a section named Referencing Outlets, with photoImageView on the left part and PhotoViewController on the right

- click the small x close to PhotoViewController to remove the connection

- You should see in PhotoViewController code that the circle on the left of photoImageView is white

- recreate the connection by usual control drag

- do an option clean build Folder from Product menu in XCode


Then run again

The function is called from the previous viewController

Please show the code of the previous viewController. You may be doing something wrong there.

I've taken the steps you have suggested, the same error continues on the same line. The viewController calling the function;


import Foundation

import UIKit

import Photos



class PhotoPopUpViewController: UIViewController {


let photoViewController = PhotoViewController()


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

dismiss(animated: true, completion: nil)

}


@IBOutlet private var photoCollectionView: UICollectionView!

@IBOutlet weak var tableView: UITableView!


var imageArray = [UIImage]()


var tableImages: [PhotoPopUpCellData] = []


var selectedTableImage: PhotoPopUpCellData?


override func viewDidLoad() {

super.viewDidLoad()

grabPhotos()

tableImages = createArray()

tableView.delegate = self

tableView.dataSource = self

}


func grabPhotos() {

let imgManager = PHImageManager.default()

let requestOptions = PHImageRequestOptions()

requestOptions.isSynchronous = true

requestOptions.deliveryMode = .highQualityFormat

let fetchOptions = PHFetchOptions()

fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]

let fetchResult: PHFetchResult<PHAsset> = PHAsset.fetchAssets(with: .image, options: fetchOptions)

if fetchResult.count > 0 {

for i in 0..<fetchResult.count {

imgManager.requestImage(for: fetchResult.object(at: i), targetSize: CGSize(width: 200, height: 200), contentMode: .aspectFill, options: requestOptions, resultHandler: {

image, error in

self.imageArray.append(image!)

})

}

} else {

print("You got no photos!")

}

}

}


extension PhotoPopUpViewController: UICollectionViewDelegate, UICollectionViewDataSource {


func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

return imageArray.count

}


func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoPopUpCell", for: indexPath) as? DataCollectionViewCell

cell?.img.image = imageArray[indexPath.row]

return cell!

}


}


extension PhotoPopUpViewController: UICollectionViewDelegateFlowLayout {


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

let width = collectionView.bounds.width / 3.0

let height = collectionView.bounds.height

return CGSize(width: width, height: height)

}


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {

return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

}


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {

return 1

}


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {

return 1

}

}


extension PhotoPopUpViewController {


func createArray() -> [PhotoPopUpCellData] {

var tempData: [PhotoPopUpCellData] = []

let tableImages1 = PhotoPopUpCellData(image: UIImage(systemName: "photo")!, title: "Open from Albums")

let tableImages2 = PhotoPopUpCellData(image: UIImage(systemName: "camera.fill")!, title: "Take a Photo")

let tableImages3 = PhotoPopUpCellData(image: UIImage(systemName: "tray.and.arrow.up.fill")!, title: "Open last Image")

tempData.append(tableImages1)

tempData.append(tableImages2)

tempData.append(tableImages3)

return tempData

}

}


extension PhotoPopUpViewController: UITableViewDataSource, UITableViewDelegate {


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

return tableImages.count

}


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let tableImage = tableImages[indexPath.row]

let cell = tableView.dequeueReusableCell(withIdentifier: "PhotoPopUpCell") as! PhotoPopUpCell

cell.setPhotoPopUp(photoPopUpCellData: tableImage)

return cell

}


func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

let tableImage = tableImages[indexPath.row]

selectedTableImage = tableImage

if indexPath.row == 0 {


present(photoViewController, animated: false, completion: {

self.photoViewController.chooseImage(self)

})

}

if indexPath.row == 1 {

present(photoViewController, animated: false, completion: {

self.photoViewController.accessVideo(self)

})

}

if indexPath.row == 2 {

present(photoViewController, animated: false, completion: {

self.photoViewController.chooseLastImage(self)

})

}

}

}

The worst thing in your code is this line:

  let photoViewController = PhotoViewController()

With this line, you instatiate your `PhotoViewController` with initializer `init()`, which is not documented clearly,

but as in the current implementation of iOS,

- it ignores any settings you have defined in your storyboard

- so, any of the IBActions or IBOutlets are not connected

- thus, `photoImageView` is still nil, even if you successfully connected the IBOutlet in the storyboard

Causes Fatal Error: Unexpectedly found nil while implicitly unwrapping an Optional value...


If you want to make iOS create an instance according to your storyboard settings, you need to use an appropriate method for storyboard.

(Or you can use segue, with a little more settings in the storyboard and a little bit different Swift code. I assume you do not use segues.)


Remove the line shown above, and modify your `tableView(_:didSelectRowAt:)` method as follows:

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        let tableImage = tableImages[indexPath.row]
        
        selectedTableImage = tableImage
        
        //### Instantiate your view controller using storyboard
        //(You need to give "PhotoViewController" as the Storyboard ID of your PhotoViewController in the storyboard.
        let photoViewController = self.storyboard!.instantiateViewController(identifier: "PhotoViewController") as! PhotoViewController
        _ = photoViewController.view //Make iOS construct views baased on the storyboard settings.
        
        if indexPath.row == 0 {
            
            present(photoViewController, animated: false, completion: {
                photoViewController.chooseImage(self) //###
            })
            
        }
        
        if indexPath.row == 1 {
            
            present(photoViewController, animated: false, completion: {
                photoViewController.accessVideo(self) //###
            })
            
        }
        
        if indexPath.row == 2 {
            present(photoViewController, animated: false, completion: {
                
                photoViewController.chooseLastImage(self) //###
            })
        }
    }


I have not checked other parts of your code yet, so you may need to modify some other parts,

but, at least, you need to change the way instantiating the next view controller.

Thank you so much for your effort, it really helped me alot on my project.