I'm trying to add a border on all sides to my table view cell, I was able to add the border but facing an issue where the border on the right side is not occupying the full width of the cell as in the screenshot below. The cell border seems to take the initial width on which the storyboard UI is set, for instance, I have set the UI as iPhone SE but if I run on iPhone 11 this issue occurs. Seems to be some issue with the layout not being refreshed. I have tried adding setNeedsLayout, setNeedsDisplay, and layoutSubviews but none of them seems to work.
Storyboard layout
Below is the full code
class ViewController: UIViewController {
var dataSource = [String] ()
override func viewDidLoad() {
super.viewDidLoad()
for index in 1...10 {
dataSource.append("Cell value \(index)")
}
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell: MyTableViewCell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as? MyTableViewCell else{return UITableViewCell()}
cell.titleLabel.text = dataSource[indexPath.row]
if indexPath.row == 0 {
cell.containerView.addBorder(toEdges: [.left, .top, .right], color: .gray, thickness: 1.0)
} else if indexPath.row == dataSource.count - 1 {
cell.containerView.addBorder(toEdges: .all, color: .gray, thickness: 1.0)
} else {
cell.containerView.addBorder(toEdges: [.left, .top, .right], color: .gray, thickness: 1.0)
}
return cell
}
}
class MyTableViewCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var containerView: UIView!
}
extension UIView {
func addBorder(toEdges edges: UIRectEdge, color: UIColor, thickness: CGFloat) {
func addBorder(toEdge edges: UIRectEdge, color: UIColor, thickness: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
switch edges {
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
}
layer.addSublayer(border)
}
if edges.contains(.top) || edges.contains(.all) {
addBorder(toEdge: .top, color: color, thickness: thickness)
}
if edges.contains(.bottom) || edges.contains(.all) {
addBorder(toEdge: .bottom, color: color, thickness: thickness)
}
if edges.contains(.left) || edges.contains(.all) {
addBorder(toEdge: .left, color: color, thickness: thickness)
}
if edges.contains(.right) || edges.contains(.all) {
addBorder(toEdge: .right, color: color, thickness: thickness)
}
}
}
You may find it much easier to use a UIView
subclass to handle the "edges."
Add this class to your project:
class BorderedView: UIView {
var edges: UIRectEdge = []
var color: UIColor = .clear
var thickness: CGFloat = 0
var shapeLayer: CAShapeLayer!
override class var layerClass: AnyClass {
return CAShapeLayer.self
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
shapeLayer = self.layer as? CAShapeLayer
shapeLayer.fillColor = UIColor.clear.cgColor
}
override func layoutSubviews() {
super.layoutSubviews()
let pth = UIBezierPath()
if edges.contains(.top) || edges.contains(.all) {
pth.move(to: CGPoint(x: bounds.minX, y: bounds.minY))
pth.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
}
if edges.contains(.bottom) || edges.contains(.all) {
pth.move(to: CGPoint(x: bounds.minX, y: bounds.maxY))
pth.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
}
if edges.contains(.left) || edges.contains(.all) {
pth.move(to: CGPoint(x: bounds.minX, y: bounds.minY))
pth.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
}
if edges.contains(.right) || edges.contains(.all) {
pth.move(to: CGPoint(x: bounds.maxX, y: bounds.minY))
pth.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
}
shapeLayer.lineWidth = thickness
shapeLayer.strokeColor = color.cgColor
shapeLayer.path = pth.cgPath
}
}
Then assign the Custom Class of your containerView
to BorderedView
. Connect it as normal in your cell class:
@IBOutlet weak var containerView: BorderedView!
Change your cell class to:
class MyTableViewCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var containerView: BorderedView!
func configureContainer(toEdges edges: UIRectEdge, color: UIColor, thickness: CGFloat) -> Void {
containerView.edges = edges
containerView.color = color
containerView.thickness = thickness
}
}
Then, in cellForRowAt
, instead of calling:
cell.containerView.addBorder(toEdges: [.left, .top, .right], color: .gray, thickness: 1.0)
call it like this:
cell.configureContainer(toEdges: [.left, .top, .right], color: .gray, thickness: 1.0)
Result - with the tableView inset 8-pts on each side so you can see the left/right edges:
When the tableView / cells change size (such as on device rotation), the edges automatically update:
I agree with @Asperi' s answer. Either you have to subclass it and write this method in drawrect. Or for quick solution change frame.width to UIScreen.main.bounds.width.Check It will work with your current requirement and with current code.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.