zamanada
zamanada

Reputation: 158

Swift 5 remote image loads in UITableViewCell on click not on load

I have a tableview cell that does not load properly. when it loads it is supposed to load the title and the image at the same time, like any normal page. as of now it only loads the images when you click on the cell. and when you click the image it is larger than it's supposed to be. not sure why. it's similar code used in another part of the project and I don't have that issue there. so there it is. not sure if what's going on is related to all those constraints I haven't set yet.

the project is an Amazon clone. the data is called from an api and the images are given as url strings. so I'm loading images from urls and placing them into my image views.

here's a video of what is going on. in the video I wait a few seconds before clicking on the cell to give it a chance to load on its own. in the code where the image is processed from the url there is a print statement that fires around the same time the title is generated so I know the image is created. it's just waiting until it is clicked to appear. not sure why. https://gfycat.com/talkativebackhornet

the page where the code is loaded. this code performs a search and uses the results to form the ProductsDetails struct where the data is displayed. this is basically just the Amazon page you get when you select a product. the image view will later be converted into a horizontal scrolling view once the code is working.

class ProductViewController: UITableViewController {

var asinForSearch: String = ""
var resultsManager = ResultsManager()
var productsDetails = Array<Products>()
{
    didSet {
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    
    resultsManager.detailsDelegate = self
    tableView.dataSource = self
    tableView.register(UINib(nibName: "ImagesCell", bundle: nil), forCellReuseIdentifier: "imagesCell")
    
    tableView.rowHeight = 750
    populateDetailsPage()
    
}

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

    return productsDetails.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let productResults = productsDetails[indexPath.row]
    let cell = tableView.dequeueReusableCell(withIdentifier: "imagesCell", for: indexPath) as! ImagesCell
    let url = URL(string: productResults.mainImage)
    cell.imagioView.kf.setImage(with: url)
    cell.title.text = productResults.title
    
    return cell
}

func populateDetailsPage(){
    resultsManager.getDetails(asinForSearch)
    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
}

}

extension ProductViewController: ResultsDetailDelegate {
func updateDetails(_ resultsManager: ResultsManager, products: Products){
    self.productsDetails.append(products)
}
}
 

here's the code that creates the cell. not sure if this is necessary.

class ImagesCell: UITableViewCell, UIScrollViewDelegate {


@IBOutlet weak var imageScrollView: UIScrollView!
@IBOutlet weak var title: UILabel!
@IBOutlet weak var imagioView: UIImageView!
@IBOutlet weak var view: UIView!

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code
    
    imageScrollView.delegate = self
    imageScrollView.contentSize = CGSize(width: imagioView.frame.width, height: imagioView.frame.height)
    self.imageScrollView.addSubview(imagioView)
    
    view.addSubview(imageScrollView)
}

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state
}
}

func getDetails(_ asin: String){
    let search = "\(detailRequest)\(asin)&country=US"
    var request = URLRequest(url: URL(string: search)!)
    
    request.httpMethod = "GET"
    request.allHTTPHeaderFields = params
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")

    let session = URLSession.shared
    
    let task = session.dataTask(with: request) { data, response, error -> Void in
    if let jsonData = data {
        do {
            let root = try JSONDecoder().decode(ProductDetail.self, from: jsonData)
            if let productDetails =  self.parseJSONDetails(root.product) {
                self.detailsDelegate?.updateDetails(self, products: productDetails)
            }
        } catch {
            print(error)
        }
    }
    }
    task.resume()
}

func parseJSONDetails(_ safeData: Products) -> Products? {
    do {
        let products = Products(asin: safeData.asin, deliveryMessage: safeData.deliveryMessage, productDescription: safeData.productDescription, featureBullets: safeData.featureBullets, images: safeData.images, mainImage: safeData.mainImage, price: safeData.price, productInformation: safeData.productInformation, title: safeData.title, totalImages: safeData.totalImages, totalVideos: safeData.totalVideos, url: safeData.url)
        return products
    } catch {
        print()
    }
}

let me know if there's any other code you need to see

results from parseJSONDetails

Upvotes: 0

Views: 669

Answers (1)

Starsky
Starsky

Reputation: 2028

You need to let your tableView know that there is new data and it needs to update its cells.

Call self.tableView.reloadData() when the delegate announces that it finished getting products.

extension ProductViewController: ResultsDetailDelegate {
    func updateDetails(_ resultsManager: ResultsManager, products: Products){
        self.productsDetails.append(products)
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }
}

Remove the reloading from here:

func populateDetailsPage(){
    resultsManager.getDetails(asinForSearch)
}

This whole function is useless. It doesn't do anything:

func parseJSONDetails(_ safeData: Products) -> Products? {
    do {
        let products = Products(asin: safeData.asin, deliveryMessage: safeData.deliveryMessage, productDescription: safeData.productDescription, featureBullets: safeData.featureBullets, images: safeData.images, mainImage: safeData.mainImage, price: safeData.price, productInformation: safeData.productInformation, title: safeData.title, totalImages: safeData.totalImages, totalVideos: safeData.totalVideos, url: safeData.url)
        return products
    } catch {
        print()
    }
}

There is no reason why you would instantiate a Products object from another Products. At least, I can't understand why it is needed.

You have this ProductDetail object after you make the network request, then just pass its parameter .products to the delegate:

func getDetails(_ asin: String){
    let search = "\(detailRequest)\(asin)&country=US"
    var request = URLRequest(url: URL(string: search)!)
    
    request.httpMethod = "GET"
    request.allHTTPHeaderFields = params
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")

    let session = URLSession.shared
    
    let task = session.dataTask(with: request) { [weak self] data, response, error -> Void in
        guard let self = self else { return }
    
        if let jsonData = data {
            do {
                let productDetail = try JSONDecoder().decode(ProductDetail.self, from: jsonData)
                let products = productDetail.products
                self.detailsDelegate?.updateDetails(self, products: products)
            } catch {
                print(error)
            }
        }
    }
    task.resume()
}

Also, make sure your detailsDelegate is declared as weak var, otherwise you risk creating a retain cycle.

Upvotes: 2

Related Questions