如何制作带有圆角和单线边框的缩进UITableViewCell

时间:2019-03-26 13:08:40

标签: ios swift uitableview autolayout

我正在尝试使用具有圆角,边框和缩进的单元格构建UITableView。结合使用这三种方法,我很难在每种情况下(1个单元格,2个单元格,> 2个单元格)在单元格的侧面和之间获得1.0pt的边框。

在最简单的版本中,唯一的问题是单元格之间的边界是双倍宽度。由于borderWidth只能为整个框架设置,因此我尝试使用以下方法添加单个边框:

extension CALayer {
    func addBorder(edge: UIRectEdge, color: UIColor, thickness: CGFloat) {
        let border = CALayer()
        switch edge {
        case .top:
            border.frame = CGRect(x: 0, y: 0, width: frame.width, height: thickness)
        case .bottom:
            border.frame = CGRect(x: 0, y: frame.height - thickness, width: frame.width, height: thickness)
        case .left:
            border.frame = CGRect(x: 0, y: 0, width: thickness, height: frame.height)
        case .right:
            border.frame = CGRect(x: frame.width - thickness, y: 0, width: thickness, height: frame.height)
        default:
            break
        }

        border.backgroundColor = color.cgColor;
        addSublayer(border)
    }
}

使用addBorder方法分别添加边框时,会出现其他问题,例如:

  • 不显示右侧边框,因为未考虑为缩进设置的新框架宽度
  • 角没有正确倒圆
  • 仍然有一个双宽度分隔符

此外,问题的类型还取决于是直接加载视图(重新运行应用程序之后)还是添加/删除单元格之后。

clipToBounds在IB中设置为true,ContentView的ContentMode设置为center。

我将UITableViewCell分为以下子类:

enum RoundedTableViewCellType {
    case first
    case last
    case single
    case middle
}

class RoundedTableViewCell: UITableViewCell {

    override var frame: CGRect {
        get {
            return super.frame
        }
        set {
            let inset: CGFloat = 20
            var frame = newValue
            frame.origin.x += inset
            frame.size.width -= 2 * inset
            super.frame = frame
        }
    }

    var type: RoundedTableViewCellType = .middle {
        didSet {
            switch type {
            case .first:
                layer.cornerRadius = 6
                layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
            case .last:
                layer.cornerRadius = 6
                layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
            case .single:
                layer.cornerRadius = 6
                layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner, .layerMaxXMinYCorner]
            case .middle:
                layer.cornerRadius = 0
                layer.maskedCorners = []
            }
        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        layer.borderColor = UIColor.primaryTransparant.cgColor
        layer.borderWidth = 1.0
    }
}

要澄清:

所需结果

enter image description here

实际结果

enter image description here

非常感谢!

2 个答案:

答案 0 :(得分:1)

使用典型的UITableView,可以达到以下目的:

enter image description here

通过在viewDidLoad()中添加以下行:

    myTableView.layer.borderColor = myTableView.separatorColor?.cgColor
    myTableView.layer.borderWidth = 1.0
    myTableView.layer.cornerRadius = 6.0

编辑:另一种方法是适应使用frame覆盖来赋予表“插入项”。

向该单元格添加CAShapeLayer作为子层。将其路径设置为UIBezierPath,以形成正确的边角。

顶部单元格只有左,上和右边缘(没有底部),顶部角是圆角的。

中间单元将具有左,上和右边缘(无底),且没有四角。

底部单元格将具有所有4条边,且底部角均被圆化。

单行表格中的一个单元格将具有所有4个边,所有4个角都被弄圆。

结果:

enter image description here

完整代码(无需IBOutlets):

enum RoundedTableViewCellType {
    case first
    case last
    case single
    case middle
}

class RoundedTableViewCell: UITableViewCell {

    var theLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.font = UIFont.systemFont(ofSize: 16.0, weight: .bold)
        v.textColor = UIColor(red: 62.0 / 255.0, green: 43.0 / 255.0, blue: 191.0 / 255.0, alpha: 1.0)
        return v
    }()

    private var borderLayer = CAShapeLayer()
    private var myType: RoundedTableViewCellType = .middle

    override var frame: CGRect {
        get {
            return super.frame
        }
        set {
            let inset: CGFloat = 20
            var frame = newValue
            frame.origin.x += inset
            frame.size.width -= 2 * inset
            super.frame = frame
        }
    }

    var borderColor: UIColor = .clear {
        didSet {
            borderLayer.strokeColor = borderColor.cgColor
        }
    }

    var borderWidth: CGFloat = 0.0 {
        didSet {
            borderLayer.lineWidth = borderWidth
        }
    }

    // need to re-set layer cornerRadius if radius is set *after* type (in VC's cellForRowAt)
    var radius: CGFloat = 6.0 {
        didSet {
            type = myType
        }
    }

    var type: RoundedTableViewCellType = .middle {
        didSet {
            myType = type
            switch type {
            case .first:
                layer.cornerRadius = radius
                layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
            case .last:
                layer.cornerRadius = radius
                layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
            case .single:
                layer.cornerRadius = radius
                layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner, .layerMaxXMinYCorner]
            case .middle:
                layer.cornerRadius = 0
                layer.maskedCorners = []
            }
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let r = radius
        var bPath = UIBezierPath()

        let ptTopLeft  = CGPoint(x: 0.0, y: 0.0)
        let ptTopRight = CGPoint(x: bounds.width, y: 0.0)
        let ptBotRight = CGPoint(x: bounds.width, y: bounds.height)
        let ptBotLeft  = CGPoint(x: 0.0, y: bounds.height)

        switch type {
        case .first:
            // top cell, add left, top and right edges
            // round top corners
            bPath.move(to: ptBotLeft)
            bPath.addLine(to: CGPoint(x: ptTopLeft.x, y: ptTopLeft.y + r))

            bPath.addQuadCurve(to: CGPoint(x: ptTopLeft.x + r, y: ptTopLeft.y),
                               controlPoint: ptTopLeft)

            bPath.addLine(to: CGPoint(x: ptTopRight.x - r, y: ptTopRight.y))

            bPath.addQuadCurve(to: CGPoint(x: ptTopRight.x, y: ptTopRight.y + r),
                               controlPoint: ptTopRight)

            bPath.addLine(to: CGPoint(x: ptBotRight.x, y: ptBotRight.y))

        case .last:
            // bottom cell, add all four edges
            // round bottom corners
            bPath = UIBezierPath(roundedRect: bounds,
                                 byRoundingCorners: [.bottomLeft, .bottomRight],
                                 cornerRadii: CGSize(width: r, height: r))

        case .single:
            // one-row table, add all four edges
            // round all four corners
            bPath = UIBezierPath(roundedRect: bounds, cornerRadius: r)

        case .middle:
            // middle cell, add left, top, right edges
            // round NO corners
            bPath.move(to: ptBotLeft)
            bPath.addLine(to: ptTopLeft)
            bPath.addLine(to: ptTopRight)
            bPath.addLine(to: ptBotRight)

        }

        borderLayer.path = bPath.cgPath

    }

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }

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

    func commonInit() -> Void {

        contentView.addSubview(theLabel)
        NSLayoutConstraint.activate([
            theLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16.0),
            theLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16.0),
            theLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16.0),
            theLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16.0),
            ])

        layer.addSublayer(borderLayer)

        borderLayer.fillColor = UIColor.clear.cgColor

        // default values
        borderColor = UIColor(red: 220.0 / 255.0, green: 215.0 / 255.0, blue: 244.0 / 255.0, alpha: 1.0)
        borderWidth = 1.0

    }

}

class RoundedCornersInsetTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var myTableView: UITableView = {
        let v = UITableView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    var theData = [1, 2, 3, 4]

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor(red: 242.0 / 255.0, green: 240.0 / 255.0, blue: 250.0 / 255.0, alpha: 1.0)

        myTableView.dataSource = self
        myTableView.delegate = self

        myTableView.register(RoundedTableViewCell.self, forCellReuseIdentifier: "RoundedTableViewCell")

        myTableView.backgroundColor = .clear
        myTableView.separatorStyle = .none

        myTableView.tableFooterView = UIView(frame: CGRect.zero)

        view.addSubview(myTableView)

        NSLayoutConstraint.activate([

            // constrain top + 40-pts
            myTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),

            // constrain leading / trailing to 0.0
            myTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0.0),
            myTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0.0),

            // change this as appropriate
            myTableView.heightAnchor.constraint(equalToConstant: 400.0)

            ])
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return theData.count
    }

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

        let cell = tableView.dequeueReusableCell(withIdentifier: "RoundedTableViewCell", for: indexPath) as! RoundedTableViewCell
        cell.theLabel.text = "Cell \(theData[indexPath.row])"
        cell.accessoryType = .disclosureIndicator

        if theData.count == 1 {

            cell.type = .single

        } else {

            if indexPath.row == 0 {
                cell.type = .first
            } else if indexPath.row == theData.count - 1 {
                cell.type = .last
            } else {
                cell.type = .middle
            }

        }

        // configurable cell border properties
        //cell.borderColor = UIColor(red: 220.0 / 255.0, green: 215.0 / 255.0, blue: 244.0 / 255.0, alpha: 1.0)
        //cell.borderWidth = 2.0
        //cell.radius = 16.0

        return cell
    }

}

答案 1 :(得分:0)

enter image description here当将border设置为1像素帧时,问题带有边框,第一个单元格从所有四个侧面获取1个像素,创建第二个单元格时,从所有四个侧面再次获取1个像素。单元格与第一个单元格的底部边框(实际上看起来像2像素厚度)相结合,您需要处理此问题。

    extension UIView {
// Example use: myView.addBorder(toSide: .Left, withColor: UIColor.redColor().CGColor, andThickness: 1.0)

enum ViewSide {
case Left, Right, Top, Bottom
    }
func addBorder(toSide side: ViewSide, withColor color: CGColor, andThickness thickness: CGFloat) {
let border = CALayer()
        border.backgroundColor = color
switch side {
case .Left: border.frame = CGRect(x: frame.minX, y: frame.minY, width: thickness, height: frame.height); break
case .Right: border.frame = CGRect(x: frame.maxX, y: frame.minY, width: thickness, height: frame.height); break
case .Top: border.frame = CGRect(x: frame.minX, y: frame.minY, width: frame.width, height: thickness); break
case .Bottom: border.frame = CGRect(x: frame.minX, y: frame.maxY, width: frame.width, height: thickness); break
        }
        layer.addSublayer(border)
    }
}

1-对于第一个单元格,向所有侧面添加边框

2-,其他则添加除“顶边框”以外的边框。