Uicollectionview Decoration View

How To Achieve the desired Design in uicollection view using decorator view

You could do it like this,

  • Create two different types of decoration view, one for vertical line which is at the center of the collection view and other being horizontal which appear below two cell
  • Vertical decoration view is created only once for the whole view, while horizontal decoration view is created for a pair of two which appears below each row. For the last row, dont create the decoration view.
  • subclass UICollectionViewFlowLayout, and override override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? and return appropriate decoration view.

Here is how the layout looks for me,

Sample Image

And here is the code used for that,

CollectionViewController,

class ViewController: UICollectionViewController {

let images = ["Apple", "Banana", "Grapes", "Mango", "Orange", "Strawberry"]

init() {
let collectionViewLayout = DecoratedFlowLayout()
collectionViewLayout.register(HorizontalLineDecorationView.self,
forDecorationViewOfKind: HorizontalLineDecorationView.decorationViewKind)
collectionViewLayout.register(VerticalLineDecorationView.self,
forDecorationViewOfKind: VerticalLineDecorationView.decorationViewKind)
super.init(collectionViewLayout: collectionViewLayout)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.white
collectionView?.register(CollectionViewCell.self,
forCellWithReuseIdentifier: CollectionViewCell.CellIdentifier)
}
}

extension ViewController: UICollectionViewDelegateFlowLayout {

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.CellIdentifier,
for: indexPath) as! CollectionViewCell
let name = images[indexPath.item]
cell.imageView.image = UIImage(named: "\(name).png")
cell.label.text = name
return cell
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return .zero
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10 + DecoratedFlowLayout.horizontalLineWidth
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

let width = (collectionView.bounds.size.width - DecoratedFlowLayout.verticalLineWidth) * 0.5
return CGSize(width: width,
height: width + 20)
}
}

CollectionViewCell,

class CollectionViewCell: UICollectionViewCell {

static let CellIdentifier = "CollectionViewCellIdentifier"

var imageView: UIImageView!
var label: UILabel!

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

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func createViews() {
imageView = UIImageView(frame: .zero)
imageView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(imageView)

label = UILabel(frame: .zero)
label.font = UIFont.systemFont(ofSize: 20)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)

NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
imageView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
label.leftAnchor.constraint(equalTo: contentView.leftAnchor),
label.rightAnchor.constraint(equalTo: contentView.rightAnchor),
label.topAnchor.constraint(equalTo: imageView.bottomAnchor),
label.heightAnchor.constraint(equalToConstant: 20)
])
}
}

DecoratedFlowLayout,

class DecoratedFlowLayout: UICollectionViewFlowLayout {

static let verticalLineWidth: CGFloat = 20
static let horizontalLineWidth: CGFloat = 20

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
minimumLineSpacing = 40 // should be equal to or greater than horizontalLineWidth
}

override init() {
super.init()
minimumLineSpacing = 40
}

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

guard let attributes = super.layoutAttributesForElements(in: rect) else {
return nil
}

var attributesCopy: [UICollectionViewLayoutAttributes] = []

for attribute in attributes {

attributesCopy += [attribute]

let indexPath = attribute.indexPath

if collectionView!.numberOfItems(inSection: indexPath.section) == 0 {
continue
}

let firstCell = IndexPath(item: 0,
section: indexPath.section)
let lastCell = IndexPath(item: collectionView!.numberOfItems(inSection: indexPath.section) - 1,
section: indexPath.section)

if let attributeForFirstItem = layoutAttributesForItem(at: firstCell),
let attributeForLastItem = layoutAttributesForItem(at: lastCell) {


let verticalLineDecorationView = UICollectionViewLayoutAttributes(forDecorationViewOfKind: VerticalLineDecorationView.decorationViewKind,
with: IndexPath(item: 0, section: indexPath.section))

let firstFrame = attributeForFirstItem.frame
let lastFrame = attributeForLastItem.frame

let frame = CGRect(x: collectionView!.bounds.midX - DecoratedFlowLayout.verticalLineWidth * 0.5,
y: firstFrame.minY,
width: DecoratedFlowLayout.verticalLineWidth,
height: lastFrame.maxY - firstFrame.minY)
verticalLineDecorationView.frame = frame

attributesCopy += [verticalLineDecorationView]
}


let contains = attributesCopy.contains { layoutAttribute in
layoutAttribute.indexPath == indexPath
&& layoutAttribute.representedElementKind == HorizontalLineDecorationView.decorationViewKind
}

let numberOfItemsInSection = collectionView!.numberOfItems(inSection: indexPath.section)

if indexPath.item % 2 == 0 && !contains && indexPath.item < numberOfItemsInSection - 2 {

let horizontalAttribute = UICollectionViewLayoutAttributes(forDecorationViewOfKind: HorizontalLineDecorationView.decorationViewKind,
with: indexPath)

let frame = CGRect(x: attribute.frame.minX,
y: attribute.frame.maxY + (minimumLineSpacing - DecoratedFlowLayout.horizontalLineWidth) * 0.5,
width: collectionView!.bounds.width,
height: DecoratedFlowLayout.horizontalLineWidth)

horizontalAttribute.frame = frame

attributesCopy += [horizontalAttribute]
}
}
return attributesCopy
}

override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}

And decoration views

class VerticalLineDecorationView: UICollectionReusableView {

static let decorationViewKind = "VerticalLineDecorationView"

let verticalInset: CGFloat = 40

let lineWidth: CGFloat = 4.0

let lineView = UIView()

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

lineView.backgroundColor = .black

addSubview(lineView)

lineView.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
lineView.widthAnchor.constraint(equalToConstant: lineWidth),
lineView.topAnchor.constraint(equalTo: topAnchor, constant: verticalInset),
lineView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -verticalInset),
lineView.centerXAnchor.constraint(equalTo: centerXAnchor),
])
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}


class HorizontalLineDecorationView: UICollectionReusableView {

let horizontalInset: CGFloat = 20

let lineWidth: CGFloat = 4.0

static let decorationViewKind = "HorizontalLineDecorationView"

let lineView = UIView()

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

lineView.backgroundColor = .black

addSubview(lineView)

lineView.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
lineView.heightAnchor.constraint(equalToConstant: lineWidth),
lineView.leftAnchor.constraint(equalTo: leftAnchor, constant: horizontalInset),
lineView.rightAnchor.constraint(equalTo: rightAnchor, constant: -horizontalInset),
lineView.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

I hope you can make use of it and tweak values to suit your own need. Some of the calculations might make sense to change to some level. But, I hope you get the idea about how this could be achieved.

how to get the instance of the decoration view in the collection view?

Answer my own question!
Thanks for this article!

First, subclass UICollectionViewLayoutAttributes and add any property you want.

Second, in the function layoutAttributesForDecorationViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes!, set the property.

Third, in the UICollectionReusableView class, override the function applyLayoutAttributes: to get the custom UICollectionViewLayoutAttributes instance that contains your property.

Done!

DecorationView gets resized when AutoSizing cells are enabled in UICollectionViewFlowLayout

It's possible that the frames of your decoration views are not being updated (i.e. invalidated) after the frames of your cells have been self-sized. The result is that the width of each decoration view remains at its default size.

Try implementing this function, which should invalidate the layout of the decoration view for each section every time the layout of an item in that section is invalidated:

override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
let invalidatedSections = context.invalidatedItemIndexPaths?.map { $0.section } ?? []
let decorationIndexPaths = invalidatedSections.map { IndexPath(item: 0, section: $0) }
context.invalidateDecorationElements(ofKind: backgroundViewClass.reuseIdentifier(), at: decorationIndexPaths)
super.invalidateLayout(with: context)
}

Creating decoration view as custom column in UICollection View

Small trick we can use to get this.

Cell size should be one - fifth of ViewController. So we can get 5 Cells in one row. U can create numberOfItems what do u need.

Create and store seat number in Dictionary. So we can avoid reuse.

I have did sample for 48 number of seats.

Coding

var busSeatNumDict = [Int : String]()
var pathWayNumber = Int()
var seatNumer = Int()

override func viewDidLoad() {
super.viewDidLoad()
pathWayNumber = 2 // CENTER - PASSENGER CAN WALK
seatNumer = 1 // STARTING NUMBER
for i in 0...59
{
if i == pathWayNumber // If it s centre, values empty to dictionary
{
busSeatNumDict[i] = ""
pathWayNumber = pathWayNumber + 5 // Position empty - 2,7,12,17,22 ...... like that
}
else
{
busSeatNumDict[i] = String(seatNumer)
seatNumer = seatNumer + 1
}

}
}


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

cell.alpha = 0 // Initially alpha 0
let text = busSeatNumDict[indexPath.row]!

if text == "" || text == "2"
{
cell.alpha = 0
}
else
{
cell.alpha = 1
}
cell.backgroundColor = UIColor.clear

cell.layer.borderColor = UIColor(red: 83/255, green: 50/255, blue: 129/255, alpha: 1.0).cgColor
cell.layer.borderWidth = 1.0
cell.txtLbl.text = text
cell.txtLbl.textAlignment = .center
cell.txtLbl.textColor = UIColor.darkGray
cell.layer.cornerRadius = 5
return cell
}

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

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

return CGSize(width: (self.collectionView?.frame.width)! / 5, height: (self.collectionView?.frame.width)! / 5)
}

Output

Sample Image



Related Topics



Leave a reply



Submit