Search code examples
swiftuitableviewbordercell

Wallet style with multiple cells border Swift


How do I make only the last cell with round corner and black border color? and the rest of the cell will only have left and right border?

This is the design of the cell. The pink part is the section header, the white part is the cell. In the image I have 6 cells and I want the 6th one to have round corner and black border. Cell 1-5 will only have left and right border.

My tableview will contain few sets of todo please see the image under.

Thank you.

enter image description here

enter image description here

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         .    
         .
         .

    cell.view.clipsToBounds = true

    if indexPath.row == todoList.count - 1 {
        cell.view.layer.cornerRadius = 10
        cell.view.layer.maskedCorners = [.layerMinXMaxYCorner,.layerMaxXMaxYCorner]
        cell.view.layer.borderColor = UIColor.black.cgColor   //not working it makes all cell has border
        cell.view.layer.borderWidth = 1
       
    } else {
        //only want left and right with black border
    }
        .    
        .
        .
}

Solution

  • I do think @Jithin answer using adding a subview is the easiest and greatest answer, but if you really want to draw your own border line, we can use UIBezierPath to achieve this. (which I think is a little bit overkill for this).

    extension ViewController: UITableViewDataSource {
        func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
        
            let cornerRadius: CGFloat = 10.0
            let lineWidth: CGFloat = 2
        
            // deduct the line width to keep the line stay side the view
            let point1 = CGPoint(x: 0.0 + lineWidth / 2, y: view.frame.height)
            let point2 = CGPoint(x: 0.0 + lineWidth / 2, y: 0.0 + cornerRadius + lineWidth / 2)
            let point3 = CGPoint(x: 0.0 + cornerRadius + lineWidth / 2, y: 0.0 + lineWidth / 2)
            let point4 = CGPoint(x: view.frame.width - cornerRadius - lineWidth / 2, y: 0.0 + lineWidth / 2)
            let point5 = CGPoint(x: view.frame.width - lineWidth / 2, y: 0.0 + cornerRadius + lineWidth / 2)
            let point6 = CGPoint(x: view.frame.width - lineWidth / 2, y: view.frame.height - lineWidth / 2)
        
            // draw the whole line with upper corner radius
            let path = UIBezierPath()
            path.move(to: point1)
            path.addLine(to: point2)
            path.addArc(withCenter: CGPoint(x: point3.x, y: point2.y),
                        radius: cornerRadius,
                        startAngle: .pi,
                        endAngle: -.pi/2,
                        clockwise: true)
            path.addLine(to: point4)
            path.addArc(withCenter: CGPoint(x: point4.x, y: point5.y),
                        radius: cornerRadius,
                        startAngle: -.pi/2,
                        endAngle: 0,
                        clockwise: true)
            path.addLine(to: point6)
            path.addLine(to: point1)
        
            let topBorder = CAShapeLayer()
            topBorder.path = path.cgPath
            topBorder.lineWidth = lineWidth
            topBorder.strokeColor = UIColor.purple.cgColor
            topBorder.fillColor = nil
    
            // add the line to header view
            view.layer.addSublayer(topBorder)
            }
    
            func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "testingCell", for: indexPath) as! TableViewCell
        
            cell.cellLabel.text = "\(mockData[indexPath.section][indexPath.row])"
            cell.backgroundColor = .green
    
            if indexPath.row == mockData[indexPath.section].count - 1 {
                cell.setAsLastCell()
                // we can add a mask to cut those area outside our border line
                let maskPath = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 10, height: 10))
                let maskLayer = CAShapeLayer()
                maskLayer.path = maskPath.cgPath
                cell.layer.mask = maskLayer
            } else {
                cell.setAsNormalCell()
                cell.layer.mask = nil
            }
        
            return cell
        }
    }
    

    And here is the UITableViewwCell:

    class TableViewCell: UITableViewCell {
    
        @IBOutlet weak var cellLabel: UILabel!
    
        let leftBorder = CALayer()
        let rightBorder = CALayer()
        let bottomBorder = CAShapeLayer()
        let cornerRadius: CGFloat = 10
        let lineWidth: CGFloat = 2
    
        override func awakeFromNib() {
            super.awakeFromNib()
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
            leftBorder.frame = CGRect(x: 0, y: 0, width: lineWidth, height: self.frame.height)
            leftBorder.backgroundColor = UIColor.blue.cgColor
            self.layer.addSublayer(leftBorder)
    
            rightBorder.frame = CGRect(x: self.frame.width - lineWidth, y: 0.0, width: lineWidth, height: self.frame.height)
            rightBorder.backgroundColor = UIColor.blue.cgColor
            self.layer.addSublayer(rightBorder)
        
            // same idea as drawing line in the header view
            let point1 = CGPoint(x: 0.0 + lineWidth / 2, y: 0.0)
            let point2 = CGPoint(x: 0.0 + lineWidth / 2, y: self.frame.height - cornerRadius - lineWidth / 2)
            let point3 = CGPoint(x: cornerRadius + lineWidth / 2, y: self.frame.height - lineWidth / 2)
            let point4 = CGPoint(x: self.frame.width - cornerRadius - lineWidth / 2, y: self.frame.height - lineWidth / 2)
            let point5 = CGPoint(x: self.frame.width - lineWidth / 2, y: self.frame.height - cornerRadius - lineWidth / 2)
            let point6 = CGPoint(x: self.frame.width - lineWidth / 2, y: 0.0)
        
            let path = UIBezierPath()
            path.move(to: point1)
            path.addLine(to: point2)[![enter image description here][1]][1]
            path.addArc(withCenter: CGPoint(x: point3.x, y: point2.y),
                        radius: cornerRadius,
                        startAngle: .pi,
                        endAngle: .pi/2,
                        clockwise: false)
            path.addLine(to: point4)
            path.addArc(withCenter: CGPoint(x: point4.x,y: point5.y),
                        radius: cornerRadius,
                        startAngle: .pi/2,
                        endAngle: 0,
                        clockwise: false)
            path.addLine(to: point6)
        
            bottomBorder.path = path.cgPath
            bottomBorder.strokeColor = UIColor.red.cgColor
            bottomBorder.lineWidth = lineWidth
            bottomBorder.fillColor = nil
            self.layer.addSublayer(bottomBorder)
        }
    
        func setAsNormalCell() {
            leftBorder.isHidden = false
            rightBorder.isHidden = false
            bottomBorder.isHidden = true
        }
    
        func setAsLastCell() {
            leftBorder.isHidden = true
            rightBorder.isHidden = true
            bottomBorder.isHidden = false
        }
    
    }
    

    And of course, the above code is just for testing purposes and maybe a bit messy, but I hope it can explain a bit about drawing a line.

    The result: enter image description here