3D Touch Peek and Pop with custom CollectionViewLayout

1 minute read

I followed this tutorial while learning how to add 3D Touch Peek & Pop to my app. And it works fine if you have standard collection view layout.

In case you use custom collection layout, you’ll see inconsistencies between detection of 3D Touch on collection cells, and cell.frame assigned to previewingContext.sourceRect will be incorrect.

What I found, is that you can get layout attributes for every element in collection view bounds, iterate over it, do point and rect conversion between coordinate systems and get correct frames for detection of 3D Touched cell.

How to detect which cell was 3D Touched in collection view with custom layout:

public func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
    
    var controller: UIViewController?
    
    guard let layoutAttributes = collectionView!.collectionViewLayout.layoutAttributesForElementsInRect(collectionView!.bounds) else { return nil }
    
    for attributes in layoutAttributes {
        let point = collectionView!.convertPoint(location, fromView: collectionView!.superview)
        
        if attributes.representedElementKind == nil && CGRectContainsPoint(attributes.frame, point) {
            if #available(iOS 9.0, *) {
                previewingContext.sourceRect = collectionView!.convertRect(attributes.frame, toView: collectionView!.superview)
                
                controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ProductNavViewController")
                
                setupProductController(controller as? UINavigationController, indexPath: attributes.indexPath)
            }
            break
        }
    }
    
    return controller
}

I convert location passed by the system to collection view coordinate space. Then I find which cell’s rect contains passed location and use that rect after converting it to app window coordinate space in assignment to previewingContext.sourceRect.

Hope it will save you 30-40 minutes :-)

Updated: