aman
aman

Reputation: 30

Add border of varying width to different sides

I have a requirement where I need to border width 1 (CGFloat) on the top, left, and bottom side of the uiview and 0 (CGFloat) on the right side. I tried adding multiple CALayers for each side, but the view was not good, as while scrolling the border did not scroll with uiview.

Code I tried using:

    enum ViewSide: String {
        case left
        case right
        case top
        case bottom
    }
    
    func addBorderToAllSides(color: UIColor, thickness: CGFloat) {
        addBorder(toSides: [.left, .right, .bottom, .top], withColor: color, andThickness: thickness)
    }
    
    func addBorder(toSides sides: [ViewSide], withColor color: UIColor, andThickness thickness: CGFloat) {
        
        sides.forEach { (side) in
            ///remove previously added sublayer
            layer.sublayers?.removeAll(where: {$0.name == side.rawValue})
            let border = CALayer()
            border.backgroundColor = color.cgColor
            switch side {
            case .left:
                border.frame = CGRect(x: frame.minX, y: frame.minY, width: thickness, height: frame.height)
            case .right:
                border.frame = CGRect(x: frame.maxX, y: frame.minY, width: thickness, height: frame.height)
            case .top:
                border.frame = CGRect(x: frame.minX, y: frame.minY, width: frame.width, height: thickness)
            case .bottom:
                border.frame = CGRect(x: frame.minX, y: frame.maxY, width: frame.width, height: thickness)
            }
            border.name = side.rawValue
            layer.addSublayer(border)
        }
    }
}

src - https://gist.github.com/MrJackdaw/6ffbc33fc274838412bfe3ad48592b9b

What I'm trying to achieve:

enter image description here

here for each item, adding the same border width on each side causes the border in the middle of 2 items to be double width. Want to get rid of that.

Any help is appreciated

Upvotes: 0

Views: 563

Answers (1)

DonMag
DonMag

Reputation: 77477

There are various ways to do this, but one method that you may like is to add a CAShapeLayer as a single sublayer, and use a UIBezierPath for the sides.

Here's a simple example:

class SidesCell: UICollectionViewCell {
    
    enum ViewSide: String {
        case left
        case right
        case top
        case bottom
    }

    var sides: [ViewSide] = []
    
    let sidesLayer: CAShapeLayer = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        contentView.layer.addSublayer(sidesLayer)
        sidesLayer.fillColor = UIColor.clear.cgColor
        sidesLayer.strokeColor = UIColor.gray.cgColor
        sidesLayer.lineWidth = 1.0
    }
    
    // modify this based on what you're setting in the cell
    //  label text, colors, whatever...
    func setData(_ str: String, sides: [ViewSide]) -> Void {
        self.sides = sides
        // use other data
    }

    // this will be called when the cell is ready for layout
    override func layoutSubviews() {
        super.layoutSubviews()

        let pth = UIBezierPath()
        
        if sides.contains(.left) {
            pth.move(to: CGPoint(x: bounds.minX, y: bounds.minY))
            pth.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
        }
        if sides.contains(.top) {
            pth.move(to: CGPoint(x: bounds.minX, y: bounds.minY))
            pth.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
        }
        if sides.contains(.right) {
            pth.move(to: CGPoint(x: bounds.maxX, y: bounds.minY))
            pth.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
        }
        if sides.contains(.bottom) {
            pth.move(to: CGPoint(x: bounds.minX, y: bounds.maxY))
            pth.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
        }
        
        sidesLayer.path = pth.cgPath
    }
    
}

Then, in cellForItemAt, you would set the sides needed for each cell.

For example, if you have 7 cells, and you want Top/Left/Bottom "side lines" on the first 6:

    let c = collectionView.dequeueReusableCell(withReuseIdentifier: "myIdentifier", for: indexPath) as! SidesCell
    if indexPath.item == 6 {
        c.setData("Test", sides: [.top, .bottom])
    } else {
        c.setData("Test", sides: [.top, .left, .bottom])
    }
    return c

Upvotes: 1

Related Questions