0

My swifts code goal is to place a image in the imageview pic in class customtv when the user imports a photo from the photo gallery. The code works if you hit the import button twice but on the first time the code does not work and nothing happens you can see me testing the code in the gif below. I tried to start the index at 1 but that is not working. I think imagePickerController is what is causing the problem.

enter image description here

import UIKit

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource, UIImagePickerControllerDelegate & UINavigationControllerDelegate {
    
     var selectedIndexPath = IndexPath(row: 0, section: 0)
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 4
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 118
    }
    @objc func importPhoto(_ button: UIButton) {
           selectedIndexPath = IndexPath(row: button.tag , section: 0)
           let imagePicker = UIImagePickerController()
           imagePicker.delegate = self
           imagePicker.sourceType = .photoLibrary
           present(imagePicker, animated: true, completion: nil)
       }
       
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let selectedImage = info[.originalImage] as? UIImage else {
            fatalError("Expected a dictionary containing an image, but was provided the following: \(info)")
        }
        guard let cell = tableview.cellForRow(at: selectedIndexPath) as? customtv else { return }
        cell.pic.image = selectedImage
        
        tableview.reloadRows(at: [selectedIndexPath], with: .automatic)
        dismiss(animated: true, completion: nil)
    }
       
       @objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
           if let error = error {
               // we got back an error!
               let ac = UIAlertController(title: "Save error", message: error.localizedDescription, preferredStyle: .alert)
               ac.addAction(UIAlertAction(title: "OK", style: .default))
               present(ac, animated: true)
           } else {
               let ac = UIAlertController(title: "Saved!", message: "Your altered image has been saved to your photos.", preferredStyle: .alert)
               ac.addAction(UIAlertAction(title: "OK", style: .default))
               present(ac, animated: true)
           }
       }
      
    
    var tableview = UITableView()
    
     
      


      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! customtv
            cell.btn.tag = indexPath.row
            cell.btn.addTarget(self, action: #selector(importPhoto), for: .touchDown)
    
            
         
            return cell
        }
    
      


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        tableview.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
        view.addSubview(tableview)
        tableview.register(customtv.self, forCellReuseIdentifier: "cell")
        tableview.delegate = self
        tableview.dataSource = self
        
        
    }
    


}


class customtv: UITableViewCell {
    lazy var backView : UIView = {
        let view = UIView(frame: CGRect(x: 10, y: 6, width: self.frame.width  , height: 110))
        view.backgroundColor = .green
        print(self.frame.width)
        return view
    }()
    
    
    
    override func layoutSubviews() {

        backView.frame =  CGRect(x: 0, y: 6, width: bounds.maxX  , height: 110)
        
        
    }

    lazy var btn : UIButton = {
        let press = UIButton(frame: CGRect(x: 180, y: 40, width: 120 , height: 50))
        press.backgroundColor = .blue
        press.setTitle("Import", for: .normal)
        
        
        return press
    }()
    
    lazy var pic : UIImageView = {
        let press = UIImageView(frame: CGRect(x: 20, y: 40, width: 120 , height: 50))
        press.backgroundColor = .red
   
        
        
        return press
    }()
  
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(animated, animated: true)
        addSubview(backView)
        backView.addSubview(pic)
        backView.addSubview(btn)
    }
    
    
    
}
Sam Burns
  • 65
  • 1
  • 10

2 Answers2

0

You are using the table view in a wrong manner. The table view is using the UITableViewDataSource methods to retrieve the data by calling tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath). In that function - and only there - you fill the cell with the appropriate data. When you set the image in imagePickerController(_:, didFinishPickingMediaWithInfo:), what happens is that the next time the user scrolls or the table view decides that it needs to re-display the cell, it calls cellForRowAt which then will reset the image that you just set.

Therefore, you need to keep references to the images that the user selects in a property

class ViewController {
    var selectedImages = [IndexPath:UIImage]()
    var selectedRow:Int? 

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

        // set the image if there is one, otherwise set to nil
        cell.imageView?.image = selectedImages[indexPath]
    }
    
    @objc func importPhoto(_ button: UIButton) {
        selectedRow = button.tag
        // ...
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let selectedImage = info[.originalImage] as? UIImage else {
            fatalError("Expected a dictionary containing an image, but was provided the following: \(info)")
        }
        guard let selectedRow = self.selectedRow else {
            fatalError("selectedRow is nil, which is unexpected")
        }
        let selectedIndexPath = IndexPath(row:selectedRow, section:0)
        selectedImages[selectedIndexPath] = selectedImage
        
        tableview.reloadRows(at: [selectedIndexPath], with: .automatic)
        dismiss(animated: true, completion: nil)
    }
    
}

Instead of storing the (binary image) data in selectedImages, you could also just store a reference to the camera roll. See Storing a reference to a camera roll image - Swift 4 for details

Andreas Oetjen
  • 9,889
  • 1
  • 24
  • 34
  • Do I keep the var selectedIndexPath = IndexPath(row: 0, section: 0) that is what I originally declared or do I delete it. THanks. – Sam Burns Sep 24 '20 at 01:10
  • Since the selected index path represents the path to the button that was pressed, you still need to store it like you did in `selectedIndexPath = IndexPath(row: button.tag , section: 0)`. But you should make it an optional, `var selectedIndexPath:IndexPath? = nil` and then checkin `didFinishPickingMediaWithInfo` if there actually is a selected path available – Andreas Oetjen Sep 24 '20 at 06:25
  • I tried your code its giving me a run time error. Can you write how to implement selectedIndexPath in your answer above. – Sam Burns Sep 25 '20 at 23:25
  • OK, I use `selectedRow` not indexPath since you only have one section. This should not crash or if it crashes, something else must be wrong. – Andreas Oetjen Sep 28 '20 at 07:22
-1

Remove the below line

tableview.reloadRows(at: [selectedIndexPath], with: .automatic)

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

Kamran
  • 14,987
  • 4
  • 33
  • 51
  • This will not work. If you modify the cell outside of `cellForRowAt:`, the cell data will be overwritten the next time the table view is re-displaying its cells. – Andreas Oetjen Sep 23 '20 at 06:50