Instagram / IGListKit

A data-driven UICollectionView framework for building fast and flexible lists.
https://instagram.github.io/IGListKit/
MIT License
12.87k stars 1.54k forks source link

Selected cell doesn't always have a superview (IGListCollectionView). #495

Closed talkaboutdesign closed 7 years ago

talkaboutdesign commented 7 years ago

I am working on interactive transition to detail view when user clicks on one of the cells.

I am using didSelectItem(at index: Int) function to check when the user clicks on the cell.

I then get the cell they selected:

guard let cell = self.cellForItem(at: index) as? DesignInspirationCell else {
            return
}

In the details view I need to get original frame of the cell and convert it into new coordinate system.

When I try to get cells superview it sometimes comes back as nil, and other times as IGListCollectionView.

Wondering why this is the case?

Here is the bit of code that calculates the originFrame:

 func calcOriginFrame(_ sender: UIView) -> CGRect {        
        if let senderViewOriginalFrameTemp = sender.superview?.convert(sender.frame, to:nil) {
            return senderViewOriginalFrameTemp
        } else if let senderViewOriginalFrameTemp = sender.layer.superlayer?.convert(sender.frame, to: nil) {
            return senderViewOriginalFrameTemp
        } else {
            return .zero
        }
}

Because sometimes the sender.superview is nil and same for the superlayer case the frame ends up being zero, this results in weird animation. Wondering if there is something else I should be doing here.

Any help will be appreciated.

General information

jamieQ commented 7 years ago

hey @talkaboutdesign, i think this is due to the nature of how collection views store cells for reuse. if you take a look at the implementation of the single section controller you'll notice that it ends up simply calling one of the dequeueReusableCell... methods. depending on how much scrolling has occurred, the cell that you dequeue may not have yet been added to the collection view. this is why you're seeing the behavior where sometimes the cell has a superview and other times it does not.

the method you're using will grab a cell from the reuse pool, potentially creating a new one, but in your case you don't really want a new cell, you just want the frame of an existing cell. one possible solution would be to use the visibleCellsForSectionController method in the selection callback. you could filter this array for the cell with the index you care about, and since it is visible it should be attached to the view hierarchy.

there might be a way to query the underlying layout for the cell's layout attributes directly, but i'm not sure if it is exposed via the list kit interfaces.

rnystrom commented 7 years ago

@jamieQ is spot on. @talkaboutdesign you're probably dequeuing another cell. Instead you want to do:

guard let cell = self.collectionContext?.cellForItem(at: index, sectionController: self) as? DesignInspirationCell else {
  return
}

This will query the underlying UICollectionView for a cell that is already in the hierarchy, which will have a superview.

If this solves your problem feel free to close this issue!

talkaboutdesign commented 7 years ago

Here is a gif i recorded with the behavior. Sometimes the animation is correct, others it has no idea where the cell is and the preview comes from the bottom of the screen ignoring the position of the cell.

http://www.giphy.com/gifs/l44QEjtdPMOrBbSgM

rnystrom commented 7 years ago

@talkaboutdesign have you tried changing the API you use to fetch the cell that we mentioned above?

talkaboutdesign commented 7 years ago

This is how I get the cell now.

 func didSelect(_ sectionController: IGListSingleSectionController, with object: Any) {
guard let cell = sectionController.cellForItem(at: 0) as? DesignInspirationCell else {
            return
        }
}
rnystrom commented 7 years ago

@talkaboutdesign that's still using the same API that will dequeue a new cell. You want to use one of the methods that gets a cell directly from the collection view. See:

talkaboutdesign commented 7 years ago

Using UICollectionView API I see the same results.

        guard let cell = self.collectionView.cellForItem(at: [0]) as? DesignInspirationCell else {
            return
        }

And your API method returns an error in my code:

screen shot 2017-02-21 at 2 52 01 pm

rnystrom commented 7 years ago

😕 its a little tough to tell why you're getting a compiler error w/out the scope of the code. Are you using that from within the section controller's didSelect(...) function?

Your code above looks like its using IGListSingleSectionController, but the compiler error shows SingleSectionViewController, so self would be the view controller, no? You can only access collectionContext from a section controller, not a UIViewController.

talkaboutdesign commented 7 years ago

Sorry I'm an idiot. Was debugging wrong ViewController. This fixed it:

guard let cell = self.collectionContext?.cellForItem(at: index, sectionController: self) as? DesignInspirationCell else {
  return
}