Alex Olshansky
Alex Olshansky

Reputation: 51

iOS UICollectionView background around cell

I have screen which look like:

enter image description here

As you see there is UICollectionView with image background. Background should be whiter than original image. But cells of collection view have to be transparent and display original part of image. Does somebody have idea how to it?

Update Sample of background image https://i.sstatic.net/h9Qp4.jpg

Upvotes: 2

Views: 246

Answers (1)

DonMag
DonMag

Reputation: 77433

The basic idea will be:

  • add a background imageView behind the collectionView
  • configure your cells to have a translucent background with a transparent rectangular "hole"
  • configure the collectionView to exactly fit two columns with no spacing between the cells
  • handle variable "padding" on the cells so the outer portion matches the cell / row size

Here is an example:

class SeeThruCollectionViewCell: UICollectionViewCell {
    static let identifier = "seeThruCellIdentifier"

    var theLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .clear
        v.textColor = .white
        v.shadowColor = .black
        v.shadowOffset = CGSize(width: 1, height: 1)
        v.font = UIFont.boldSystemFont(ofSize: 17)
        return v
    }()

    var overlayPath: UIBezierPath!
    var transparentPath: UIBezierPath!
    var fillLayer: CAShapeLayer!

    var padding: CGRect!

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }


    func commonInit() -> Void {

        padding = CGRect.zero

        // init and add the sublayer we'll use as a mask
        fillLayer = CAShapeLayer()
        layer.addSublayer(fillLayer)

        // add a label
        addSubview(theLabel)

        // center the label
        NSLayoutConstraint.activate([
            theLabel.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0.0),
            theLabel.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0.0),
        ])

    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // overlayPath and transparentPath will combine to
        // create a translucent mask with a transparent rect in the middle

        overlayPath = UIBezierPath(rect: bounds)

        var r = bounds
        r.origin.x += padding.origin.x
        r.origin.y += padding.origin.y
        r.size.width -= r.origin.x + padding.size.width
        r.size.height -= r.origin.y + padding.size.height

        transparentPath = UIBezierPath(rect: r)

        overlayPath.append(transparentPath)
        overlayPath.usesEvenOddFillRule = true

        fillLayer.path = overlayPath.cgPath
        fillLayer.fillRule = kCAFillRuleEvenOdd
        fillLayer.fillColor = UIColor(white: 1.0, alpha: 0.75).cgColor

    }

}

class SeeThruViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

    var theCollectionView: UICollectionView = {
        let v = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .clear
        return v
    }()

    var theBackgroundImageView: UIImageView = {
        let v = UIImageView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    // translucent padding for cells
    let pad = CGFloat(12)

    let numItems = 10
    let numSections = 1

    override func viewDidLoad() {
        super.viewDidLoad()

        // add background image view
        view.addSubview(theBackgroundImageView)

        // add collection view view
        view.addSubview(theCollectionView)

        if let img = UIImage(named: "cvBKG") {
            theBackgroundImageView.image = img
        }

        let guide = view.safeAreaLayoutGuide

        // constrain background image view and collection view to same frames
        NSLayoutConstraint.activate([

            theBackgroundImageView.topAnchor.constraint(equalTo: guide.topAnchor, constant: 0.0),
            theBackgroundImageView.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: 0.0),
            theBackgroundImageView.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 0.0),
            theBackgroundImageView.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: 0.0),

            theCollectionView.topAnchor.constraint(equalTo: theBackgroundImageView.topAnchor, constant: 0.0),
            theCollectionView.bottomAnchor.constraint(equalTo: theBackgroundImageView.bottomAnchor, constant: 0.0),
            theCollectionView.leadingAnchor.constraint(equalTo: theBackgroundImageView.leadingAnchor, constant: 0.0),
            theCollectionView.trailingAnchor.constraint(equalTo: theBackgroundImageView.trailingAnchor, constant: 0.0),

            ])

        // normal collection view tasks
        theCollectionView.register(SeeThruCollectionViewCell.self, forCellWithReuseIdentifier: SeeThruCollectionViewCell.identifier)
        theCollectionView.dataSource = self
        theCollectionView.delegate = self

        // we want ZERO spacing between the cells
        if let flow = theCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            flow.minimumLineSpacing = 0
            flow.minimumInteritemSpacing = 0
        }

    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        // set cell size to fill exactly two "columns"
        if let flow = theCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            flow.itemSize = CGSize(width: theCollectionView.frame.size.width / 2.0, height: theCollectionView.frame.size.width / 2.0)
        }

    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return numSections
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return numItems
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SeeThruCollectionViewCell.identifier, for: indexPath) as! SeeThruCollectionViewCell

        cell.theLabel.text = "Cell: \(indexPath.item)"

        var padRect = CGRect(x: pad, y: pad, width: pad, height: pad)

        if indexPath.item <= 1 {
            // if it's the first row
            // add paddingn on the top
            padRect.origin.y = pad * 2
        }

        if indexPath.item % 2 == 0 {
            // if it's the left "column"
            // add padding on the left
            padRect.origin.x = pad * 2
        } else {
            // if it's the right "column"
            // add padding on the right
            padRect.size.width = pad * 2
        }

        if indexPath.item / 2 >= (numItems / 2) - 1 {
            // if it's the last row
            // add paddingn on the bottom
            padRect.size.height = pad * 2
        }

        cell.padding = padRect

        return cell
    }

}

Name your background image cvNKG.png and add it to your project's Assets, then create a new UIViewController and assign its class to SeeThruViewController. You should be able to run this as-is.

Result:

enter image description here

Note: this is a starting point. You'll (obviously) need to add labels for your needs, and you'll want to handle cases such as an odd number of items or if the number of rows don't fill the screen.

Upvotes: 1

Related Questions