UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Problems with Milestone 10-12

Forums > 100 Days of Swift

Hi, everyone.

Currently, I am on Milestone projects 10-12 and stuck.

So, where I am:

  • I have already built a basic interface: I used UITableViewController, changed everything in the interface builder, and used numberOfRowsInSection and cellForRowAt.
  • I created a new custom class called Picture with the name and image property;
  • then I created an imagePickerController with UUID, jpegData.write and etc;
  • then I created DetailViewController and tied it up with the imageView of DetailedViewController at the Storyboard; my DetailViewController file has the property selectedImage which conforms custom Picture class;
  • Finally, I have overridden didSelectRowAt method and made the connection between view controller and detailed view controller. (I left the task of renaming and the task of being able to use a photo from the camera for the end.)

So, when I run the app everything works fine: I can open the picker and add photos saved on my phone. When I click on the photo, the Detailviewcontroller appears, BUT... but the photo is not displayed for some reason.

I have already been stuck for two days))) and somehow I know where the problem is:

import UIKit

class DetailViewController: UIViewController {
    @IBOutlet var imageView: UIImageView!
    var selectedImage: Picture?

    override func viewDidLoad() {
        super.viewDidLoad()

        title = selectedImage?.name

        if let imageToLoad = selectedImage {
            imageView.image = UIImage(contentsOfFile: imageToLoad.image)

            ///I HAVE TRIED PRINT(imageView.image!) AND I HAVE GOT TERRIBLE CRUSH.
        }
    }
}

Now I know that for some reason my imageView.image is left "empty". But everything that I have tried is not working.

So, the question why dosn't my imageView.image work? and why while I run the app my detail view controler is not showing the image?

I would be extremely grateful if someone would help me to solve the problem.

3      

Hi @Mykola!

I suspect you forgot to retrieve the path. You saved the image before displaying it in DetailView so it should be something like this.

class DetailViewController: UIViewController {

  @IBOutlet weak var imageView: UIImageView!
  var selectedImage: String?

  override func viewDidLoad() {
    super.viewDidLoad()

    if let image = selectedImage {
      let path = getDocumentsDirectory().appendingPathComponent(image)
      imageView.image = UIImage(contentsOfFile: path.path)
    }
  }

  func getDocumentsDirectory() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    return paths[0]
  }

}

for Picture Class you can use this:

class Picture: NSObject, Codable {
  var name: String
  var image: String

  init(name: String, image: String) {
    self.name = name
    self.image = image
  }
}

4      

@ygeras

Thanks a lot! I have tried but it still does not work. So I will share all my code with you)

class ViewController: UITableViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    var pictures = [Picture]()

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(selectPicture))
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return pictures.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Picture", for: indexPath)
        let picture = pictures[indexPath.row]

        cell.textLabel?.text = picture.name

        return cell
    }

    @objc func selectPicture() {
        let picker = UIImagePickerController()
        picker.allowsEditing = true
        picker.delegate = self
        present(picker, animated: true)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let image = info[.editedImage] as? UIImage else { return }

        let imageName = UUID().uuidString
        let imagePath = getDocumentsDirectory().appendingPathExtension(imageName)

        if let jpegData = image.jpegData(compressionQuality: 0.8) {
            try? jpegData.write(to: imagePath)
        }

        let name = "Unknown"

        let picture = Picture(name: name, image: imageName)
        pictures.append(picture)
        tableView.reloadData()

        dismiss(animated: true)
    }

    func getDocumentsDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let dvc = storyboard?.instantiateViewController(withIdentifier: "Detail") as! DetailViewController
        let picture = pictures[indexPath.row]
        dvc.selectedImage = picture
        navigationController?.pushViewController(dvc, animated: true)
    }
}

Now DetailView with your suggestions:

class DetailViewController: UIViewController {
    @IBOutlet var imageView: UIImageView!
    var selectedImage: Picture?

    override func viewDidLoad() {
        super.viewDidLoad()

        title = selectedImage?.name
        navigationItem.largeTitleDisplayMode = .never

        if let imageToLoad = selectedImage?.image {
            let path = getDocumentsDirectory().appendingPathComponent(imageToLoad)
                  imageView.image = UIImage(contentsOfFile: path.path)
        }
    }

    func getDocumentsDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
      }
}

and here is my Picture class:

class Picture: NSObject, Codable {
    var name: String
    var image: String

    init(name: String, image: String) {
        self.name = name
        self.image = image
    }
}

I would be very appreciated if you check the code!

3      

Your mistake most probably here in DetailView

var selectedImage: Picture?

you have to pass it as

var selectedImage: String?

also in didSelectRowAt pass a string not an object Picture

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let dvc = storyboard?.instantiateViewController(withIdentifier: "Detail") as! DetailViewController
        dvc.selectedImage = pictures[indexPath.row].image // add 'image'
        navigationController?.pushViewController(dvc, animated: true)
    }

if it doens't help let me know. Will check line by line tomorrow :)

4      

@ygeras

It still does not work) Please, check it by the line tomorrow. 🙏

Have goot night!)

3      

Now cannot fall asleep )))) unless try to solve it. So let's start with ViewController Class

class ViewController: UITableViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    var pictures = [Picture]()

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(selectPicture))

        // This is to load info on images we saved before
        load()
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return pictures.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Picture", for: indexPath)
        let picture = pictures[indexPath.row]
        cell.textLabel?.text = picture.name

        return cell
    }

    @objc func selectPicture() {
        let picker = UIImagePickerController()

        picker.allowsEditing = true
        picker.delegate = self
        present(picker, animated: true)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let image = info[.editedImage] as? UIImage else { return }

        let imageName = UUID().uuidString
        let imagePath = getDocumentsDirectory().appendingPathComponent(imageName) // NOT appendingPathExtension(imageName)

        if let jpegData = image.jpegData(compressionQuality: 0.8) {
            try? jpegData.write(to: imagePath)
        }

        // let name = "Unknown" // Why do declare it here and then pass below. So we can remove it

        let picture = Picture(name: "Unknown", image: imageName) // You use it once so place string "Unknown" directly
        pictures.append(picture)
        tableView.reloadData()

        // So we picked up image gave it a name, gave it a path where we wrote image to and added to pictures array
        // Let's save it to UserDefaults so next time view appears all info is retrieved from there

        save()

        dismiss(animated: true)
    }

    func getDocumentsDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
      // It is advisable to use if let to instantiate VCs, if something goes wrong but let's disregard it for now
        let dvc = storyboard?.instantiateViewController(withIdentifier: "Detail") as! DetailViewController
        dvc.selectedImage = pictures[indexPath.row] // OK let's pass the whole object :)
        navigationController?.pushViewController(dvc, animated: true)
    }

      func save() {
    let jsonEncoder = JSONEncoder()

    if let picturesSaved = try? jsonEncoder.encode(pictures) {
      let defaults = UserDefaults.standard
      defaults.set(picturesSaved, forKey: "pictures")
    } else {
      print("Failed to save pictures data.")
    }
  }

  // This is to retrieve all images we saved before

   func load() {
    let defaults = UserDefaults.standard

    if let picturesSaved = defaults.object(forKey: "pictures") as? Data {
      let jsonDecoder = JSONDecoder()

      do {
        pictures = try jsonDecoder.decode([Picture].self, from: picturesSaved)
      } catch {
        print("Failed to load pictures data.")
      }
    }
  }

}

In DetailView basically we leave the same.

class DetailViewController: UIViewController {
    @IBOutlet var imageView: UIImageView!
    var selectedImage: Picture?

    override func viewDidLoad() {
        super.viewDidLoad()

        title = selectedImage?.name
        navigationItem.largeTitleDisplayMode = .never

        if let imageToLoad = selectedImage?.image {
            let path = getDocumentsDirectory().appendingPathComponent(imageToLoad)
                  imageView.image = UIImage(contentsOfFile: path.path)
        }
    }

    func getDocumentsDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
      }
}

So the issues should have been also here appendingPathExtension(imageName). You added it as a file extension to save not as PathComponent.

PS Before running on simulator, delete app so that all leftovers from previous app are gone.

4      

Thanks Yuri. Now its working! Going to learn where I made mistakes! Best wishes!

3      

Glad to hear it helped! Please mark post as solved :) Cheers and keep coding!

4      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.