0

I'd like to add a gradient to a collection view cell's background in the context of the new collection view with compositional layouts. Here's an example of how a cell's background is configured from Apple's sample code Implementing Modern Collection Views in line 180 of EmojiExplorerViewController:

func configuredGridCell() -> UICollectionView.CellRegistration<UICollectionViewCell, Emoji> {
    return UICollectionView.CellRegistration<UICollectionViewCell, Emoji> { (cell, indexPath, emoji) in
        var content = UIListContentConfiguration.cell()
        content.text = emoji.text
        content.textProperties.font = .boldSystemFont(ofSize: 38)
        content.textProperties.alignment = .center
        content.directionalLayoutMargins = .zero
        cell.contentConfiguration = content
        var background = UIBackgroundConfiguration.listPlainCell()
        background.cornerRadius = 8
        background.strokeColor = .systemGray3
        background.strokeWidth = 1.0 / cell.traitCollection.displayScale
        cell.backgroundConfiguration = background
    }
}

Since the new UIBackgroundConfiguration is a structure rather than a layer-backed UIView subclass, I can't just add a CAGradientLayer instance as a sublayer.

What would be a good approach to adding a gradient to a cell background configuration?

zcoop98
  • 2,590
  • 1
  • 18
  • 31
Curiosity
  • 544
  • 1
  • 15
  • 29
  • 1
    “Since the new UIBackgroundConfiguration is a structure rather than a layer-backed UIView subclass, I can't just add a CAGradientLayer instance as a sublayer.” Actually the UIBackgroundConfiguration has a background view and a view can be a gradient layer carrier, so it’s hard to see what the difficulty is. https://developer.apple.com/documentation/uikit/uibackgroundconfiguration/3600757-customview – matt Oct 16 '20 at 03:12
  • I believe I tried setting `customView` to a gradient layer carrier to no avail, but the fact that I also had one of the preset list cell configurations also set might be the problem. Somehow I missed that there’s an empty view configuration in your linked documentation, so when I get back to my computer in an hour I’ll test using both the empty background view and setting the custom view. – Curiosity Oct 16 '20 at 05:04
  • @matt I just tested and verified that `backgroundConfiguration.customView?.layer.insertSublayer(gradientLayer, at: 0)` does not display a gradient, only the preset background configuration is shown which in this case is `UIBackgroundConfiguration.listPlainCell()`. Changing the configuration to `UIBackgroundConfiguration.clear()` shows a clear view. – Curiosity Oct 16 '20 at 06:34
  • Well, your test is wrong. Giving an actual example. – matt Oct 16 '20 at 12:37
  • Sorry for the misunderstanding, and thank you. As someone new to Core Animation, inserting a sublayer on the background `customView` seemed like the right way to give the background a `CALayer` based on reference documentation (https://developer.apple.com/documentation/quartzcore/calayer). When you said "gradient layer carrier" it didn't occur to me that this was a specific pattern (`override static var layerClass: AnyClass { CAGradientLayer.self }`) and as far as I can tell Apple doesn't have a conceptual explanation including that. Given the downvote, how can I improve the question? – Curiosity Oct 16 '20 at 22:10
  • Well, you _could_ set the `customView` to a UIView and add the CAGradientLayer as a sublayer to that, but then you'd have to worry about how to get the size of the gradient layer correct and _keep_ it correct when the `customView` size changes, which is a bit of extra work because there is no autolayout for layers. The intent of my answer was to keep it as simple as possible, and just prove that it's possible and easy to give cells a gradient background, so I used the shortest possible code, i.e. a UIView whose layer _is_ a gradient layer. – matt Oct 16 '20 at 22:43

1 Answers1

3

Since the new UIBackgroundConfiguration is a structure rather than a layer-backed UIView subclass, I can't just add a CAGradientLayer instance as a sublayer.

Yes, you can. The fact that UIBackgroundConfiguration is a struct is irrelevant. It has a customView property that's a view, and that will be used as the background view (behind the content view) in the cell. So set that view to something (it is nil by default) and you're all set.

Here's an example. This is a toy table view for test purposes, but the test is exactly about configuration objects, so it is readily adaptable to demonstrate the technique. It doesn't matter whether you're using a table view, a collection view, or neither, as long as you are using something that has a UIBackgroundConfiguration property. As you can see, I've made a vertical gradient from black to red as the background to my cells.

enter image description here

Here's the relevant code. First, I have defined a gradient-carrier view type:

class MyGradientView : UIView {
    override static var layerClass: AnyClass { CAGradientLayer.self }
}

Then, I use that view as the background view when I configure the cell:

var back = UIBackgroundConfiguration.listPlainCell()
let v = MyGradientView()
(v.layer as! CAGradientLayer).colors = 
    [UIColor.black.cgColor, UIColor.red.cgColor]
back.customView = v
cell.backgroundConfiguration = back

Whatever else you want to achieve is merely a variant of the above. For example, you could use an image view or a solid background view and combine them with the gradient view. The point is, the customView is the background view, and whatever view you set it to will be displayed in the background of the cell.

I should also point out that there is another way to do this, namely, to use a cell subclass and implement updateConfigurationUsingState:. The advantage of this approach is that once you've given the background configuration a customView, you can just modify that customView each time the method is called. You can use this technique to respond to selection, for example, as I have demonstrated in other answers here (such as https://stackoverflow.com/a/63064099/341994).

matt
  • 515,959
  • 87
  • 875
  • 1,141