1

Hi I am trying to make a home feed like facebook using UICollectionView But in each cell i want to put another collectionView that have 3 cells.

you can clone the project here

I have two bugs the first is when i scroll on the inner collection View the bounce do not bring back the cell to center. when i created the collection view i enabled the paging and set the minimumLineSpacing to 0 i could not understand why this is happening. when i tried to debug I noticed that this bug stops when i remove this line

layout.estimatedItemSize = CGSize(width: cv.frame.width, height: 1)

but removing that line brings me this error

The behavior of the UICollectionViewFlowLayout is not defined because: the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values

because my cell have a dynamic Height here is an example

example

my second problem is the text on each inner cell dosent display the good text i have to scroll until the last cell of the inner collection view to see the good text displayed here is an example example

cczak
  • 143
  • 13
  • So first issue is fixed? What is the second issue? – meaning-matters Jan 02 '18 at 11:21
  • no none of them is fixed on the first Gif you can see that at the end the cell is not coming back to center – cczak Jan 02 '18 at 11:26
  • my second problem is the text on each inner cell dosent display the good text i have to scroll until the last cell of the inner collection view to see the good text displayed here is an example you can see the second GIF – cczak Jan 02 '18 at 11:27
  • What do you mean with 'good' text? – meaning-matters Jan 02 '18 at 11:30
  • i mean the text that suppose to be there.I have an array of Posts each post has an array of String so to create all the Outer cells i use Posts.count then i have to create the inner cells to do so i use posts.listText.count in the cellForItemAt indexPath function i do cell.text = listText[indexPath.row] but the indexPath.row is not the good one you can see on the second gif that the index path that i print on the cell before the Lorem Ipsum is not the good one but after i scroll and come back the good one is displayed – cczak Jan 02 '18 at 11:48
  • So much code. Could you post the actual project as that would be much easier to take a look at. – Upholder Of Truth Jan 02 '18 at 11:52
  • Aren't you missing a collection view reload to fix the second issue? With the debugger you can follow where this wrong text comes from. Good luck! – meaning-matters Jan 02 '18 at 11:54
  • @UpholderOfTruth yes sure so i just remove all the code and post the project ? it's my first question on stack i dont see a button to upload files – cczak Jan 02 '18 at 11:57
  • You can leave the code if you want. You can always create a git repository for the code which people can then clone or zip it all up, put it somewhere like dropbox and then share a link to it. – Upholder Of Truth Jan 02 '18 at 11:59
  • Or possibly just add the code for the Post class as I think everything else works cut and paste. – Upholder Of Truth Jan 02 '18 at 12:00
  • @UpholderOfTruth i added the project you can clone is and test it thanks for your comments ! – cczak Jan 02 '18 at 12:09
  • @meaning-matters where i should call it ? – cczak Jan 02 '18 at 12:12

1 Answers1

1

You first issue will be solved by setting the minimumInteritemSpacing for the innerCollectionView in the OuterCell. So the definition for innerCollectionView becomes this:

let innerCollectionView : UICollectionView = {
    let layout = UICollectionViewFlowLayout()
    layout.scrollDirection = .horizontal
    layout.minimumLineSpacing = 0
    layout.minimumInteritemSpacing = 0

    let cv = UICollectionView(frame :.zero , collectionViewLayout: layout)
    cv.translatesAutoresizingMaskIntoConstraints = false
    cv.backgroundColor = .orange
    layout.estimatedItemSize =  CGSize(width: cv.frame.width, height: 1)
    cv.isPagingEnabled = true
    cv.showsHorizontalScrollIndicator = false

    return cv

}()

The second issue is solved by adding calls to reloadData and layoutIfNeeded in the didSet of the post property of OuterCell like this:

var post: Post? {
    didSet {
        if let numLikes = post?.numLikes {
            likesLabel.text = "\(numLikes) Likes"
        }

        if  let numComments = post?.numComments {
            commentsLabel.text = "\(numComments) Comments"
        }
        innerCollectionView.reloadData()
        self.layoutIfNeeded()
    }
}

What you are seeing is related to cell reuse. You can see this in effect if you scroll to the yellow bordered text on the first item and then scroll down. You will see others are also on the yellow bordered text (although at least with the correct text now).

EDIT

As a bonus here is one method to remember the state of the cells.

First you need to track when the position changes so in OuterCell.swft add a new protocol like this:

protocol OuterCellProtocol: class {
    func changed(toPosition position: Int, cell: OutterCell)
}

then add an instance variable for a delegate of that protocol to the OuterCell class like this:

public weak var delegate: OuterCellProtocol?

then finally you need to add the following method which is called when the scrolling finishes, calculates the new position and calls the delegate method to let it know. Like this:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    if let index = self.innerCollectionView.indexPathForItem(at: CGPoint(x: self.innerCollectionView.contentOffset.x + 1, y: self.innerCollectionView.contentOffset.y + 1)) {
        self.delegate?.changed(toPosition: index.row, cell: self)
    }
}

So that's each cell detecting when the collection view cell changes and informing a delegate. Let's see how to use that information.

The OutterCellCollectionViewController is going to need to keep track the position for each cell in it's collection view and update them when they become visible.

So first make the OutterCellCollectionViewController conform to the OuterCellProtocol so it is informed when one of its

class OutterCellCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, OuterCellProtocol   {

then add a class instance variable to record the cell positions to OuterCellCollectionViewController like this:

var positionForCell: [Int: Int] = [:]

then add the required OuterCellProtocol method to record the cell position changes like this:

func changed(toPosition position: Int, cell: OutterCell) {
    if let index = self.collectionView?.indexPath(for: cell) {
        self.positionForCell[index.row] = position
    }
}

and finally update the cellForItemAt method to set the delegate for a cell and to use the new cell positions like this:

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "OutterCardCell", for: indexPath) as! OutterCell
    cell.post = posts[indexPath.row]

    cell.delegate = self

    let cellPosition = self.positionForCell[indexPath.row] ?? 0
    cell.innerCollectionView.scrollToItem(at: IndexPath(row: cellPosition, section: 0), at: .left, animated: false)
    print (cellPosition)

    return cell
}

If you managed to get that all setup correctly it should track the positions when you scroll up and down the list.

Upholder Of Truth
  • 4,643
  • 2
  • 13
  • 23
  • Thank you very much for your help but i tried your code and i have questions your solution really works but i am wondering if this line is really usefull now that i am updating the layout it seems not rally usefull layout.estimatedItemSize = CGSize(width: cv.frame.width, height: 1) and another question is how come now when we scroll one on the outer cell other random cells are scrolled to .... – cczak Jan 02 '18 at 12:37
  • While not strictly necessary it's always good practice to give some kind of sensible default for the estimatedItemSize and it doesn't hurt to do it. The documentation says that doing so can help to improve performance. – Upholder Of Truth Jan 02 '18 at 12:42
  • do you think it is better to create 2 subclass of the innerCell and register them to prevent the reuse ? and the scrolling problems – cczak Jan 02 '18 at 13:10
  • Creating extra subclasses won't prevent the cell reuse issue. Do you want the cells to remember how they have scrolled the collection view when you scroll up and down the list or can they all just go back to the first text item? – Upholder Of Truth Jan 02 '18 at 13:11
  • i want them to remember where they were – cczak Jan 02 '18 at 13:17
  • Ok I will see what I can come up with and then update my example. – Upholder Of Truth Jan 02 '18 at 13:17
  • Updated the answer. It's a bit complex but I hope you get the idea. – Upholder Of Truth Jan 02 '18 at 13:47
  • Thanks a lot for your time i understand your idea ! – cczak Jan 02 '18 at 14:02
  • is it possible to tag you on a question i posted ? – cczak Jan 08 '18 at 15:03
  • Sure or just link it here and I will take a look. – Upholder Of Truth Jan 08 '18 at 15:04
  • thank you very much [link](https://stackoverflow.com/questions/48144576/called-within-transaction-when-uisearch-bar-clicked-and-keyboard-showed) – cczak Jan 08 '18 at 15:10