0

I have a collection view inside a table view cell. The model is wish list items grouped by wish list name. There may be many wish lists. When a user selects wish lists they get a flat array of wish lists but I want to display a preview of the wish lists' item images in the cell. They are not individually selectable, a cell selection goes to a list of items for the selected wish list. Likewise, I will delete the entire wish list with a swipe delete motion.

My problem here is that the collectionView functions don't fire at all. The source data I'm testing with has one wish list and 4 items.

Here is my TableViewCell.

import Foundation
import UIKit
import Alamofire
import AlamofireImage

class WishListsCell: UITableViewCell {

var collectionView: UICollectionView!

let screenWidth = UIScreen.main.bounds.width

var alamofireRequest: Alamofire.Request?

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code

    let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
    layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    layout.itemSize = CGSize(width: screenWidth, height: ((screenWidth / 4) * Constants.IMAGE_ASPECT_RATIO))
    layout.minimumInteritemSpacing = 0
    layout.minimumLineSpacing = 0
    layout.estimatedItemSize.height = ((screenWidth / 4) * Constants.IMAGE_ASPECT_RATIO)
    layout.estimatedItemSize.width = screenWidth

    collectionView = UICollectionView(frame: contentView.frame, collectionViewLayout: layout)

    collectionView.delegate = self

    collectionView.dataSource = self   

    collectionView.register(WishListsCollectionViewCell.self, forCellWithReuseIdentifier: cellId)

    self.contentView.addSubview(collectionView)

}

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

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)    
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
}


extension WishListsCell: UICollectionViewDelegate, UICollectionViewDataSource {


func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

    debugPrint(wishListItems.count)

    return wishListItems.count

}


func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WishListsCollectionViewCell", for: indexPath) as! WishListsCollectionViewCell

    if let imageUrl = wishListItems[indexPath.row]["image"] as? String {

        debugPrint(imageUrl)

        cell.imageView.image = nil

        if let image = ShoppingManager.sharedManager.photoCache.image(withIdentifier: imageUrl) {
            cell.imageView.image = image
        }
        else
        {                              
            cell.alamofireRequest?.cancel()

            cell.alamofireRequest = Alamofire.request(imageUrl)
                .responseImage { response in

                    let img = response.result.value

                    cell.imageView.image = img

                    cell.imageView.contentMode = UIViewContentMode.scaleAspectFit

                    let imageWidth:CGFloat = self.screenWidth/4

                    let imageHeight:CGFloat = imageWidth * Constants.IMAGE_ASPECT_RATIO

                    cell.imageView.frame.size.width = imageWidth

                    cell.imageView.frame.size.height = imageHeight

                    ShoppingManager.sharedManager.photoCache.add(img!, withIdentifier: imageUrl)               
            }
        }
    }
    return cell   
}   
}

Here is my CollectionViewCell:

import Foundation
import UIKit
import Alamofire
import AlamofireImage

class WishListsCollectionViewCell: UICollectionViewCell {

    var alamofireRequest: Alamofire.Request?

    var imageView: UIImageView

    let screenWidth = UIScreen.main.bounds.width

    override init(frame: CGRect) {

        imageView = UIImageView()

        super.init(frame: frame)

        contentView.addSubview(imageView)

        imageView.translatesAutoresizingMaskIntoConstraints = false

        imageView.contentMode = .scaleAspectFit

        NSLayoutConstraint.activate([

           NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal,
                               toItem: nil, attribute: .width,
                               multiplier: 1.0, constant: screenWidth / 4),

            NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal,
                               toItem: nil, attribute: .height,
                               multiplier: 1.0, constant: (screenWidth / 4) * Constants.IMAGE_ASPECT_RATIO),

            ])
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }    
}

Here are relevant parts of my ViewController code:

import UIKit
import Alamofire
import AlamofireImage
import MBProgressHUD
import DBAlertController

class WishListsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    var tableView: UITableView = UITableView()

    var screenSize: CGRect!
    var screenWidth: CGFloat!
    var screenHeight: CGFloat!

    var cellId = "WishListsCell"

    var didSelectItem:Bool = false

    override func viewDidLoad() {
        super.viewDidLoad()

        screenSize = UIScreen.main.bounds
        screenWidth = screenSize.width
        screenHeight = screenSize.height

        tableView.delegate      =   self

        tableView.dataSource    =   self

        tableView.backgroundColor = .white

        tableView.register(WishListsCell.self, forCellReuseIdentifier: cellId)

        self.view.addSubview(tableView)

        tableView.translatesAutoresizingMaskIntoConstraints = false

        tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true

        let customView = UIView(frame: CGRect(x:0, y:0, width:screenWidth, height:30))

        customView.backgroundColor = Constants.APP_BACKGROUND_COLOR

        let button = UIButton(frame: CGRect(x: 0, y: 0, width: screenWidth, height: 30))

        button.setTitle("Add New Wish List", for: .normal)

        button.setTitleColor(Constants.APP_TEXT_COLOR, for: .normal)

        button.titleLabel?.font = Constants.APP_HEADER_FONT

        button.addTarget(self, action: #selector(addButtonAction), for: .touchUpInside)

        customView.addSubview(button)

        tableView.tableHeaderView = customView        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return wishLists.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

        return 120
    }

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

        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! WishListsCell

        let listId = wishLists[indexPath.section]["listid"] as! Int

        cell.alamofireRequest?.cancel()

        cell.alamofireRequest = Alamofire.request(myURL)
            .responseJSON(completionHandler: {
                response in  
                self.parseWishListItemData(JSONData: response.data!)             
            })
        return cell
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {

        let listName = wishLists[section]["listname"] as! String

        return listName
       }
    }
markhorrocks
  • 1,199
  • 19
  • 82
  • 151
  • Just a guess but shouldn't your collection view delegates be setup in the the view controller where you added the collection view protocols? And setup datasource and delegates there? I see you have added an extension though so maybe not. – Alex McPherson Jan 11 '17 at 15:12
  • I'm following a couple of blogs who did it this way, https://contentpedlar.wordpress.com/2016/10/22/uicollectionview-in-uitableview/ – markhorrocks Jan 11 '17 at 15:14
  • Maybe because you are doing web-requests on main thread and is blocking the UI and its updates of the collection view hence why you are not getting the function calls. What happens if you comment out your Alamofire code. – Alex McPherson Jan 11 '17 at 15:25
  • Nothing different. I thought Alamofire requests were async? – markhorrocks Jan 11 '17 at 15:29
  • Yes my bad I believe they are. I assume you have connected your collection view outlets within the tableviewcell also? i.e tableviewcell > collectionview or are you creating the collectionview in code? Also your collectionview doesnt have numberOfSections only numberOfItemsInSection – Alex McPherson Jan 11 '17 at 15:31
  • Everything is in code apart from a view controller wrapped in a nav controller. I will try adding number of sections but I think it just defaults to 1. I see it missing a lot in examples. – markhorrocks Jan 11 '17 at 15:44
  • Check my answer for this : https://stackoverflow.com/a/45618501/3400991 – Shobhakar Tiwari Aug 10 '17 at 16:03

2 Answers2

2

It doesn't look like you're adding your collection view to the view hierarchy after instantiating it in awakeFromNib. You need to call addSubview on the cell's content view.

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code

    let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
    layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    layout.itemSize = CGSize(width: screenWidth, height: ((screenWidth / 4) * Constants.IMAGE_ASPECT_RATIO))
    layout.minimumInteritemSpacing = 0
    layout.minimumLineSpacing = 0
    layout.estimatedItemSize.height = ((screenWidth / 4) * Constants.IMAGE_ASPECT_RATIO)
    layout.estimatedItemSize.width = screenWidth

    collectionView = UICollectionView(frame: contentView.frame, collectionViewLayout: layout)

    collectionView.delegate = self

    collectionView.dataSource = self

    self.contentView.addSubview(collectionView) // <-- Need this        
}

You may also have some issues if the table cell gets reused. You'll have to make sure that your collection view delegate gets set each time it's re-used--in prepareForReuse probably.

Matt Long
  • 24,438
  • 4
  • 73
  • 99
0

The init was incorrectly placed in awakeFromNib() and should have been in

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
markhorrocks
  • 1,199
  • 19
  • 82
  • 151