lauwis
lauwis

Reputation: 411

Is there a way to give outer border in every section of uicollctionview?

Try to add outer border of every section in a collection view.

If i'm using cell.layer.border, it will also create an inner border. Is there a simple way to create outer border only for every section in collection view?

Try to created red border like image below

enter image description here

Upvotes: 0

Views: 1283

Answers (1)

Shawn Frank
Shawn Frank

Reputation: 5193

As Matt pointed out in the comments and the articles pointed out, you would need to make use of a DecorationView.

You can read up on this here

So to do this, you would have to follow these steps:

  1. Create a custom UICollectionReusableView which would serve as your decoration view
  2. Subclass UICollectionViewFlowLayout to create a custom layout
  3. Override layoutAttributesForDecorationView and layoutAttributesForElements to figure out the frame of each section and place the decoration view in the section frame
  4. Use the custom flow layout as the layout of your collection view

Here is that in code

  1. Create the Decoration view, which is just a regular view with a border
class SectionBackgroundView : UICollectionReusableView {
    
    static let DecorationViewKind = "SectionBackgroundIdentifier"
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        // Customize the settings to what you want
        backgroundColor = .clear
        layer.borderWidth = 5.0
        layer.borderColor = UIColor.blue.cgColor
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
  1. Create a custom flow layout
class BorderedFlowLayout: UICollectionViewFlowLayout {
    
    override init() {
        super.init()
        
        // Register your decoration view for the layout
        register(SectionBackgroundView.self,
                 forDecorationViewOfKind: SectionBackgroundView.DecorationViewKind)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutAttributesForDecorationView(ofKind elementKind: String,
                                                    at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        if elementKind == SectionBackgroundView.DecorationViewKind {
            
            guard let collectionView = collectionView else { return nil }
            
            // Initialize a UICollectionViewLayoutAttributes for a DecorationView
            let decorationAttributes
                = UICollectionViewLayoutAttributes(forDecorationViewOfKind: SectionBackgroundView.DecorationViewKind,
                                                   with:indexPath)
            
            // Set it behind other views
            decorationAttributes.zIndex = 2
            
            let numberOfItemsInSection
                = collectionView.numberOfItems(inSection: indexPath.section)
            
            // Get the first and last item in the section
            let firstItem = layoutAttributesForItem(at: IndexPath(item: 0, section: indexPath.section))
            
            let lastItem = layoutAttributesForItem(at: IndexPath(item: (numberOfItemsInSection - 1),
                                                                 section: indexPath.section))
            
            // The difference between the maxY of the last item and
            // the the minY of the first item is the height of the section
            let height = lastItem!.frame.maxY - firstItem!.frame.minY
            
            // Set the frame of the decoration view for the section
            decorationAttributes.frame = CGRect(x: 0,
                                                y: firstItem!.frame.minY,
                                                width: collectionView.bounds.width,
                                                height: height)
            
            return decorationAttributes
        }
        
        return nil
    }
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
        // Get all the UICollectionViewLayoutAttributes for the current view port
        var attributes = super.layoutAttributesForElements(in: rect)
        
        // Filter to get all the different sections
        let sectionAttributes
            = attributes?.filter { $0.indexPath.item == 0 } ?? []
        
        // Loop through the different sections
        for sectionAttribute in sectionAttributes {
            
            // Create decoration attributes for the current section
            if let decorationAttributes
                = self.layoutAttributesForDecorationView(ofKind: SectionBackgroundView.DecorationViewKind,
                                                         at: sectionAttribute.indexPath) {
                
                // Add the decoration attributes for a section if it is in the current viewport
                if rect.intersects(decorationAttributes.frame) {
                    attributes?.append(decorationAttributes)
                }
            }
        }
        
        return attributes
    }
}
  1. Make use of the custom layout in your view controller
private func configureCollectionView() {
    
    collectionView = UICollectionView(frame: CGRect.zero,
                                      collectionViewLayout: createLayout())
    
    collectionView.backgroundColor = .white
    
    collectionView.register(UICollectionViewCell.self,
                            forCellWithReuseIdentifier: "cell")
    
    // You can ignore the header and footer views as you probably already did this
    collectionView.register(HeaderFooterView.self,
                            forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
                            withReuseIdentifier: HeaderFooterView.identifier)
    
    collectionView.register(HeaderFooterView.self,
                            forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter,
                            withReuseIdentifier: HeaderFooterView.identifier)
    
    collectionView.dataSource = self
    collectionView.delegate = self
    
    view.addSubview(collectionView)
}

private func createLayout() -> UICollectionViewFlowLayout {
    
    let flowLayout = BorderedFlowLayout()
    flowLayout.minimumLineSpacing = 10
    flowLayout.minimumInteritemSpacing = 10
    flowLayout.scrollDirection = .vertical
    flowLayout.sectionInset = UIEdgeInsets(top: 10,
                                           left: horizontalPadding,
                                           bottom: 10,
                                           right: horizontalPadding)
    
    return flowLayout
}

Doing all of this should give you what you want

Custom UIViewCollectionView UICollectionViewFlowLayout border around each section decoration view iOS swift

I have only posted the most important snippets. If for some reason you can't follow along, here is the full code to recreate the example

Upvotes: 1

Related Questions