2

I've developed a custom CollectionViewLayout which uses DecorationView to show shadows behind the cells.

However, I'd like to add this decoration only to some cells. The UICollectionView is vertical, but it may contain an embedded horizontal UICollectionView inside the cell. The cells with an embedded UICollectionView should not be decorated, as shown on the image:

enter image description here

Here is the code I'm using to add a shadow. The UICollectionViewLayout does not provide a method how to retrieve a cell's class, so it could decide whether to add a shadow or not:

  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    let parent = super.layoutAttributesForElements(in: rect)
    guard let attributes = parent, !attributes.isEmpty else {
      return parent
    }

    let sections = attributes.map{$0.indexPath.section}
    let unique = Array(Set(sections))


    // Need to detect, which sections contain an embedded UICollectionView and exclude them from the UNIQUE set


    let backgroundShadowAttributes: [UICollectionViewLayoutAttributes] = unique.compactMap{ section in
      let indexPath = IndexPath(item: 0, section: section)
      return self.layoutAttributesForDecorationView(ofKind: backgroundViewClass.reuseIdentifier(),
                                                    at: indexPath)
    }

    return attributes + backgroundShadowAttributes + separators
  }

Is there any way to conditionally specify, which views should be decorated?

Richard Topchii
  • 7,075
  • 8
  • 48
  • 115
  • Why you can't create custom cell class with the shadow and create appropriate paddings in the UICollectionViewLayout? – Andrew Romanov Apr 13 '18 at 10:14
  • There are more complex cases, when the `UICollectionView` should look like a `UITableView` and have shadows rendered correctly. Attaching a shadow to a `UICollectionViewCell` does not work in such a case: https://i.stack.imgur.com/hVKoY.png – Richard Topchii Apr 13 '18 at 10:20

1 Answers1

4

Finished with this code: A protocol to directly ask the DataSource, whether to show a shadow for a particular section:

protocol SectionBackgroundFlowLayoutDataSource {
  func shouldDisplayBackgroundFor(section: Int) -> Bool
}

And leverage the protocol in the func layoutAttributesForElements(in rect: CGRect) method:

  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    let parent = super.layoutAttributesForElements(in: rect)
    guard let attributes = parent, !attributes.isEmpty else {
      return parent
    }

    attributes.forEach(configureRoundCornersAttributes)

    // Display shadows for every section by default
    var sectionsWithShadow = Set(attributes.map{$0.indexPath.section})
    if let dataSource = collectionView?.dataSource as? SectionBackgroundFlowLayoutDataSource {
    // Ask DataSource for sections with shadows, if it supports the protocol
      sectionsWithShadow = sectionsWithShadow.filter{dataSource.shouldDisplayBackgroundFor(section: $0)}
    }

    let backgroundShadowAttributes: [UICollectionViewLayoutAttributes] = sectionsWithShadow.compactMap{ section in
      let indexPath = IndexPath(item: 0, section: section)
      return self.layoutAttributesForDecorationView(ofKind: backgroundViewClass.reuseIdentifier(),
                                                    at: indexPath)
    }

    return attributes + backgroundShadowAttributes + separators
  }

func shouldDisplayBackgroundFor(section: Int) -> Bool may return faster, than cellForItemAtIndexPath, as it doesn't require full cell configuration.

Richard Topchii
  • 7,075
  • 8
  • 48
  • 115
  • Thanks for this answer. Would it be possible to post the whole code for your collectionviewflowlayout subclass, as I'm trying to do the exact same thing and I'm struggling! – Tometoyou Aug 20 '21 at 18:33