4

What is the difference between segue and instantiateViewController?

I've been trying to figure out how to use segues to send an image from one view controller to another and 2 answers (Passing Image to another View Controller (Swift) and How do I segue an image to another ViewController and display it within an ImageView?) both say to use segues but when trying to use segues I encountered a few problems like the second view controller not showing up after the photo library dismissed or that the image was not showing up.

Segue example

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.destination is XferImageViewController {
        print("Test: ", pickedImage.image)
        let xferVC = segue.destination as? XferImageViewController
        xferVC?.storedImage = pickedImage.image
    }
    print("WHAT IS GOING ON")
//        if segue.destination is XferImageViewController {
//            let xferVC = segue.destination as? XferImageViewController
//            print(pickedImage.image)
//            //xferVC?.storedImage = pickedImage.image
//            xferVC?.storedImage = pickedImageVar
//        }

}

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
        pickedImage.image = image
    } else {
        print("Something went wrong")
    }
    let image = info[UIImagePickerControllerOriginalImage] as? UIImage
    dismiss(animated:true, completion: nil)
    performSegue(withIdentifier: "xferImage", sender: self)
}

InstantiateViewController Example

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
        pickedImage.image = image
    } else {
        print("Something went wrong")
    }
    let image = info[UIImagePickerControllerOriginalImage] as? UIImage
    dismiss(animated:true, completion: nil)
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let controller = storyboard.instantiateViewController(withIdentifier: "xferImage") as! XferImageViewController
    controller.storedImage = image
    present(controller, animated: true, completion: nil)
}

So after using InstantiateViewController instead of segue I got the results that I wanted. What is the difference between the two? (I made sure that that segue identifier, tried segue.destination and storyboard ID but still wasn't getting what I needed) It is possible that I don't know how to use segue after the photo library dismiss call but still want to know the difference.

LampPost
  • 856
  • 11
  • 30

2 Answers2

4

The issue is that the segue is particular about the state of the view controller hierarchy when you initiate the segue. You have to defer the performSegue(withIdentifier:sender:) until the dismiss is done, namely put it into the completion handle of dismiss:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
        pickedImage.image = image
    } else {
        print("Something went wrong")
    }

    dismiss(animated: true) {
        self.performSegue(withIdentifier: "xferImage", sender: self)
    }
}

The above worked fine for me.

BTW, you can simplify your prepare(for:sender:) implementation:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let xferVC = segue.destination as? XferImageViewController {
        xferVC.storedImage = pickedImage.image
    }
}

Two somewhat unrelated observations:

  1. If pickedImage is an image view, I'd suggest renaming it (and updating the outlets in your storyboard) to pickedImageView. It's a good convention to avoid confusion between UIImage properties and UIImageView outlets.

  2. This is an even more minor observation, but in Model-View-Controller design, you generally don't want to rely on UIKit objects, like UIImageView to hold model objects, namely the selected image. It suggests a conceptual confusion between "view" objects and "model" objects. Plus, what if the current view controller didn't have a UIImageView?

    I'd personally suggest storing the selected image in a separate UIImage? property:

    private var selectedImage: UIImage?
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
            selectedImage = image
    
            // if you also want to update a `UIImageView` in the current
            // view controller, fine, do that, but it shouldn't be confused
            // with the "model".
            //
            // pickedImageView.image = image
        } else {
            print("Something went wrong")
        }
    
        dismiss(animated: true) {
            self.performSegue(withIdentifier: "xferImage", sender: self)
        }
    }
    

    And:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let xferVC = segue.destination as? XferImageViewController {
            xferVC.storedImage = selectedImage
        }
    }
    
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • As an added note, make sure that `xferImage` is your segue's identifier and not the viewController's identifier. Since the code ran smoothly by using the same identifier with `instantiateViewController`, I'm guessing that you used the identifier for the viewController and not the segue – Malik Jan 12 '18 at 05:33
  • @Malik - Yes, I see what you mean; you don't want to confuse the segue identifier with the view controller identifier. Fortunately, if you don't have a segue with that identifier, it will generate an error telling you that, so it will be fairly obvious. But good clarification. – Rob Jan 12 '18 at 06:25
  • Thanks for the in-depth explanation and helpful tips! I did in fact have a UIImageView in the first controller for testing purposes. – LampPost Jan 12 '18 at 15:26
0

Try using Function as Below

override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
    if segue.identifier == "segue"
    {
        if let xferVC = segue.destination as? XferImageViewController {
            xferVC.storedImage = pickedImage.image

                //Why Optional here ?
                //xferVC?.storedImage = pickedImage.image
            }
        }  
  }
iOS Geek
  • 4,825
  • 1
  • 9
  • 30