1

I am writing an iOS card game. I am displaying the player's cards in a collection view with horizontal scrolling.

When a card is selected, I want that card to "pop up" a bit. This means that it should move up by 10% of its height.

This is how I approached the problem:

I overrode this method to calculate the size of each cell:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    // the height of each card should be a bit smaller than the collection view
    // height so there is some space for the card to pop up
    let height = collectionView.height / 1.15
    // 5:7 is the W:H ratio of each card
    let width = height * 5 / 7
    return CGSize(width: width, height: height)
}

In each cell, there is an imageView that completely fills the cell. I did this with constraints. Here is the cell class:

class CardCell: UICollectionViewCell {
    @IBOutlet var imageView: UIImageView!

    override var isSelected: Bool {
        didSet {
            if isSelected {
                self.transform = .identity
            } else {
                // when it is not selected, it is transformed downwards because
                // identity transformation will place the cell at the top of the
                // collection view
                self.transform = CGAffineTransform(translationX: 0, y: self.bounds.height * 0.1)
            }
        }
    }
}

In cellForItemAt, I did the following:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CardCell
    cell.isSelected = false
    cell.imageView.image = ...
    return cell
}

This is all well and good and appears to work.

When the device orientation changes, the collection view will change its size accordingly (I have added constraints on the collection view as well). This means that I need to recalculate the size for the cells again, so I call collectionView.reloadData() in viewWillTransitionTo. This is where it doesn't work. After an orientation change, some of the cells doesn't seem to be transformed downwards. If I tap on these cells, they will be transformed further upwards, exceeding the collection view's bounds.

I checked the debugger and found that these abnormal cells indeed have the identity transformation in the selected state. That shouldn't happen, right? The collection view should by default position all its cells inside its bounds, right?

How can I make it so that all the cells are transformed downwards even after an orientation change?

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • The `viewWillTransitionTo` is called be before the layout is calculate again, no? Might want to `reloadData()` after, or maybe invalidate the layout at that point to force its recalculation (once the transition is done). – Larme Sep 12 '18 at 11:09
  • @Larme I _am_ reloading after the transition is done. At least I think I am. I am calling `reloadData` in a `DispatchQueue.main.async` block. The cells’ sizes are correctly calculated, their positions are not. And about your second point, should I call `setNeedsLayout` on the riot view of the VC, or the collection view? – Sweeper Sep 12 '18 at 12:29
  • For the first point, so in `willTransitionTo` you called the `super` one and implemented your `DispatchQueue.main` in the completion block, right? For the other point, I meant with overriding `shouldInvalidateLayout(forBoundsChange:)` if you use a custom layout. It should recalculate it. – Larme Sep 12 '18 at 12:35
  • @Larme wait there is a completion block for `viewWillTransitionTo`? – Sweeper Sep 12 '18 at 12:37
  • https://stackoverflow.com/questions/26943808/ios-how-to-run-a-function-after-device-has-rotated-swift Yes, else, you reload before the transition, no? I didn't read again the documentation of the method, but the "will" suggest future. – Larme Sep 12 '18 at 12:38
  • @Larme I never knew that! Will try that in a few hours. – Sweeper Sep 12 '18 at 12:39
  • @Larme It worked! Thank you! – Sweeper Sep 12 '18 at 15:10
  • I voted to close, and if I were youn and if you think it's a duplicate, I'd keep the duplicate (not a delete) because your issue is tricky (so other might not find it so easily the final solution). If you don't think it's a duplicate, I'll answer it, your issue being in the fact that you reload before the view has changed its frame. – Larme Sep 12 '18 at 15:14

0 Answers0