2

Im trying to get tableview cells with auto resizing images to work. Basically I want the image width in the cell to always be the same, and the height to change in accordance with the aspect ratio of the image.

I have created a cell class, which only has outlets for a label, imageView and a NSLayoutConstraint for the height of the image. I have some async methods to download an image and set it as the image for the cell imageView. Then the completion handle gets called and I run the following code to adjust the height constraint to the correct height:

cell.cellPhoto.loadImageFromURL(url: photos[indexPath.row].thumbnailURL, completion: {
            // Set imageView height to the width
            let imageSize = cell.cellPhoto.image?.size
            let maxHeight = ((self.tableView.frame.width-30.0)*imageSize!.height) / imageSize!.width
            cell.cellPhotoHeight.constant = maxHeight
            cell.layoutIfNeeded()
        })
        return cell

And here is the UIImageView extension I wrote which loads images:

func loadImageFromURL(url: String, completion: @escaping () -> Void) {
        let url = URL(string: url)
        makeDataRequest(url: url!, completion: { data in
            DispatchQueue.main.async {
                self.image = UIImage(data: data!)
                completion()
            }
        })
    }

And the makeDataRequest function which it calls:

func makeDataRequest(url: URL, completion: @escaping (Data?) -> Void) {
    let session = URLSession.shared

    let task = session.dataTask(with: url, completionHandler: { data, response, error in
        if error == nil {
            let response = response as? HTTPURLResponse
            switch response?.statusCode {
                case 200:
                    completion(data)
                case 404:
                    print("Invalid URL for request")
                default:
                    print("Something else went wrong in the data request")
            }
        } else {
            print(error?.localizedDescription ?? "Error")
        }
    })

    task.resume()
}

This works for all the cells out of frame, but the imageviews in the cells in the frame are small. Only when I scroll down and then back up again do they correctly size. How do I fix this? I know other people have had this issue but trying their fixes did nothing.

Thijs van der Heijden
  • 1,147
  • 1
  • 10
  • 25

1 Answers1

2

I had to sorta recreate the problem to understand what was going on. Basically you need to reload the tableview. I would do this when a picture finishes downloading.

In the view controller that has the table view var. Add this to the viewDidLoad() function.

   override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self

        //Create a notification so we can update the list from anywhere in the app. Good if you are calling this from an other class.
         NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "loadList"), object: nil)

    }

   //This function updates the cells in the table view
   @objc func loadList(){
          //load data here
          self.tableView.reloadData()
      }

Now, when the photo is done downloading, you can notify the viewcontroller to reload the table view by using the following,

func loadImageFromURL(url: String, completion: @escaping () -> Void) {
        let url = URL(string: url)
        makeDataRequest(url: url!, completion: { data in
            DispatchQueue.main.async {
                self.image = UIImage(data: data!)
                completion()
                //This isn't the best way to do this as, if you have 25+ pictures,
                //the list will pretty much freeze up every time the list has to be reloaded.
                //What you could do is have a flag to check if the first 'n' number of cells 
                //have been loaded, and if so then don't reload the tableview.

                //Basically what I'm saying is, if the cells are off the screen who cares.
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "loadList"), object: nil)
            }
        })
    }

Heres something I did to have better Async, see below. Sim picture

My code as follows, I didn't do the resizing ratio thing like you did but the same idea applies. It's how you go about reloading the table view. Also, I personally don't like writing my own download code, with status code and everything. It isn't fun, why reinvent the wheel when someone else has done it?

Podfile

pod 'SDWebImage',  '~> 5.0' 

mCell.swift

class mCell: UITableViewCell {
    //This keeps track to see if the cell has been already resized. This is only needed once.
    var flag = false
    @IBOutlet weak var cellLabel: UILabel!
    @IBOutlet weak var cell_IV: UIImageView!

    override func awakeFromNib() { super.awakeFromNib() }
}

viewController.swift (Click to see full code) I'm just going to give the highlights of the code here.

//Set the image based on a url
//Remember this is all done with Async...In the backgorund, on a custom thread.
mCell.cell_IV.sd_setImage(with: URL(string: ViewController.cell_pic_url[row])) { (image, error, cache, urls) in
    // If failed to load image
    if (error != nil) {
        //Set to defult
         mCell.cell_IV.image = UIImage(named: "redx.png")
     }
    //Else we got the image from the web.
    else {
         //Set the cell image to the one we downloaded
         mCell.cell_IV.image = image

         //This is a flag to reload the tableview once the image is done downloading. I set a var in the cell class, this is to make sure the this is ONLY CALLED once. Otherwise the app will get stuck in an infinite loop.
         if (mCell.flag != true){
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.025){ //Nothing wrong with a little lag.
                      NotificationCenter.default.post(name: NSNotification.Name(rawValue: "loadList"), object: nil)
                      mCell.flag = true
                   }
         }



    }
}
Greg432
  • 530
  • 4
  • 25
  • To build on this, it is easier to reload the whole tableview instead of a cell in the view. – Greg432 Jan 14 '20 at 00:07
  • 1
    Thanks! I figured out the problem, I should just load all images before displaying them, that way they get layout correctly instantly! – Thijs van der Heijden Jan 14 '20 at 12:32
  • Assuming you're not downloading a lot of pictures then? If you have too many, loading all of them first might take awhile for some users. Just keep in mind that it's something you might have/want to change down the road. – Greg432 Jan 14 '20 at 13:43
  • 1
    Only loading 10 small images, more get loaded about 75% down the table view. Plus I just added some skeleton views when loading the initial 10 images, but unless you have an internet speed of less than 1mbps you will never see them. :) – Thijs van der Heijden Jan 14 '20 at 13:56