2

I have a horizontally scrollable UICollectionView with three cells each of which are different subclasses of UICollectionViewCell. Each one of these cells contains a UITableView.

Inside of the first two cells, my table view cells are the same subclasses of UITableViewCell and have just a UIImageView. I use it to set its backgroundColor. Inside of the third cell, my table view's cells are different subclasses of UITableViewCell than in the previous two. They have both a UILabel and a UIImageView. The label has some dummy text, and I set imageView's backgroundColor to some color, again.

In order to follow MVC pattern, I use my UIViewController as a data source and a delegate for both collection view, and table view. Here is the code of UIViewController:

 class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

let collectionViewCellId = "collectionViewCell"
let tableViewCellId = "tableViewCell"
let collectionViewCellId2 = "collectionViewCellId2"
let collectionViewCellId3 = "collectionViewCellId3"
let tableViewCellDif = "tableViewCellDif"
var collectionViewIndex: Int?

@IBOutlet weak var collectionView: UICollectionView! {
    didSet {
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.isPagingEnabled = true
    }
}


//MARK: UITableViewDataSource

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

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

    let colors: [UIColor] = [.red, .green, .purple, .orange, .blue]
    let colors2: [UIColor] = [.blue, .brown, .yellow, .magenta, .cyan]


    if collectionViewIndex == 0 {
        let cell = tableView.dequeueReusableCell(withIdentifier: tableViewCellId, for: indexPath) as! TableViewCell
        cell.colorForImageView = colors[indexPath.row]
         return cell
    } else
    if collectionViewIndex == 1 {
        let cell = tableView.dequeueReusableCell(withIdentifier: tableViewCellId, for: indexPath) as! TableViewCell
        cell.colorForImageView = colors2[indexPath.row]
         return cell 
    } else
    if collectionViewIndex == 2 {
        let cell = tableView.dequeueReusableCell(withIdentifier: tableViewCellDif, for: indexPath) as! TableViewCellDifferent
        cell.colorForImageView = colors2[indexPath.row]
        return cell
    } else {
        return UITableViewCell()
    }

  }

 }

//MARK: UICollectionViewDataSource

extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView,    numberOfItemsInSection section: Int) -> Int {
    return 3
}

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

    let identifier: String
    if indexPath.item == 0 {
        identifier = collectionViewCellId
    } else if indexPath.item == 1 {
        identifier = collectionViewCellId2
    } else if indexPath.item == 2 {
        identifier = collectionViewCellId3
    } else {
        identifier = ""
    }
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath)

    return cell
 }
}

//MARK: UICollectionViewDelegate
extension ViewController: UICollectionViewDelegate {

func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    if indexPath.item == 0 {
        let cell = cell as! CollectionViewCell
        cell.tableView.dataSource = self
        cell.tableView.delegate = self
        collectionViewIndex = 0
    }
    if indexPath.item == 1 {
        let cell = cell as! CollectionViewCell2
        cell.tableView.dataSource = self
        cell.tableView.delegate = self
        collectionViewIndex = 1
    }
    if indexPath.item == 2 {
        let cell = cell as! CollectionViewCell3
        cell.tableView.dataSource = self
        cell.tableView.delegate = self
        collectionViewIndex = 2
        print (collectionViewIndex)
    }
  }

}

//MARK: UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let layout = collectionViewLayout as! UICollectionViewFlowLayout
    layout.minimumInteritemSpacing = 0
    layout.minimumLineSpacing = 0

    return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}

}

As I stated in a title of the question, nothing happens on a background thread. I, basically, only set the backgroundColor of table view's cells.

The problem is that inside the collection view's third cell (and only inside of there), my table view dequeues its cells only after a minor scroll or tap happens. Here is how it looks like:

enter image description here

I can't figure out why this happens. Maybe, this happens because inside of the third cell of the collection view, my table view's cells are instances of different subclass than inside of the first two?

EDITED

I could solve the problem by reloading the table view before before showing the collection view's each cell but I'm not sure that this is the most efficient solution. Here is the code:

 //MARK: UICollectionViewDelegate
 extension ViewController: UICollectionViewDelegate {

func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    if indexPath.item == 0 {
        let cell = cell as! CollectionViewCell
        cell.tableView.dataSource = self
        cell.tableView.delegate = self
        cell.tableView.reloadData()
        collectionViewIndex = 0
    }
    if indexPath.item == 1 {
        let cell = cell as! CollectionViewCell2
        cell.tableView.dataSource = self
        cell.tableView.delegate = self
        cell.tableView.reloadData()
        collectionViewIndex = 1
    }
    if indexPath.item == 2 {
        let cell = cell as! CollectionViewCell3
        cell.tableView.dataSource = self
        cell.tableView.delegate = self
        cell.tableView.reloadData()
        collectionViewIndex = 2
    }
  }

}

If you know a better way, I would appreciate your help.

Tigran Iskandaryan
  • 1,371
  • 2
  • 14
  • 41
  • What about calling cell.tableView.reloadData() inside if indexPath.item == 2 { let cell = cell as! CollectionViewCell3 cell.tableView.dataSource = self cell.tableView.delegate = self collectionViewIndex = 2 print (collectionViewIndex) } ? Of course, after setting the delegate and datasource. – crom87 Mar 08 '18 at 16:00
  • @crom87, I've done that, already, see edited question. The problem is, I'm not sure that this is the best solution – Tigran Iskandaryan Mar 08 '18 at 16:01
  • Is the method public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {, called with collectionViewIndex == 2? – crom87 Mar 08 '18 at 16:17
  • Have you tried setting tableView `.delegate` and `.dataSource` inside `cellForItemAt` instead of `willDisplay cell`? – DonMag Mar 08 '18 at 17:16
  • @crom87, if you mean whether the method is called within `collectionViewIndex == 2`, then it isn't until I scroll the table view a little bit. That is the reason why the problem occurs, but I can't figure out why the method doesn't get called here, whereas on previous cells it does – Tigran Iskandaryan Mar 08 '18 at 18:34

2 Answers2

1

I gave this a try, and saw the same results. So, I moved your collection view cell "setup" code from willDisplay cell: to cellForItemAt and it fixed the problem.

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

    if indexPath.item == 0 {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewCellId, for: indexPath) as! CollectionViewCell
        cell.tableView.dataSource = self
        cell.tableView.delegate = self
        collectionViewIndex = 0
        return cell
    }
    if indexPath.item == 1 {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewCellId2, for: indexPath) as! CollectionViewCell2
        cell.tableView.dataSource = self
        cell.tableView.delegate = self
        collectionViewIndex = 1
        return cell
    }
    // if we get here, indexPath.item must equal 2
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewCellId3, for: indexPath) as! CollectionViewCell3
    cell.tableView.dataSource = self
    cell.tableView.delegate = self
    collectionViewIndex = 2
    return cell

}

Now, since you're not showing your code for your tableview cells, it's possible there might be another issue, but this worked for me:

class TableViewCell: UITableViewCell {

    @IBOutlet var theImageView: UIImageView!

    var colorForImageView: UIColor = UIColor.gray {
        didSet {
            self.theImageView.backgroundColor = colorForImageView
        }
    }

}

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Thanks. This works. Honestly speaking, I can't understand why I didn't try this at first. P.s my table view cell code is almost identical to yours, so the problem was in setting the tableView's data source and delegate – Tigran Iskandaryan Mar 09 '18 at 14:08
0

You can try to dequeue CollectionViewCell or TableViewCell explicitly on the main thread

DispatchQueue.main.async {
   let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath)
}

or

DispatchQueue.main.async {
    let cell = tableView.dequeueReusableCell(withIdentifier: tableViewCellDif, for: indexPath) as! TableViewCellDifferent
    cell.colorForImageView = colors2[indexPath.row]
}

It could help to wake up the main thread

But in general, it would be much easier if a data source for the table view was inside collection view cell

Andrey Volobuev
  • 862
  • 8
  • 11
  • Well, shouldn't dequeuing happen on a main thread by definition because it is an operation that affects `UIView`s? And I know that by making the collection view's cell a data source (and/or a delegate), we can control all this processes easier, however, that violates MVC pattern, and isn't considered as a good design. – Tigran Iskandaryan Mar 08 '18 at 16:10
  • It should happen on the main thread, but I encounter a bug once like here https://stackoverflow.com/questions/21075540/presentviewcontrolleranimatedyes-view-will-not-appear-until-user-taps-again/30787046#30787046 – Andrey Volobuev Mar 08 '18 at 16:17
  • And putting data source inside collection view cell doesn't violate MVC. In this case, UITableViewCell class is a controller, and it has a model and a view. – Andrey Volobuev Mar 08 '18 at 16:21
  • In the documentation it is said that UITableViewCell inherits from UIView, so it is basically a subclass of UIView. That's why it is a view, not a controller. A subclass of UITableViewController is a controller because it controls how its views (usually, cells) behave and what data they should contain (model part). – Tigran Iskandaryan Mar 08 '18 at 18:24
  • I've tried your suggestion, but it didn't change the behaviour of the table view – Tigran Iskandaryan Mar 08 '18 at 18:29
  • UITableViewCell has a contentView property so logic controlling this view acts as a Controller. – Andrey Volobuev Mar 08 '18 at 18:52