简体   繁体   中英

How can i draw this line between the tableview cell. For now i just set a background image but cell size will be increase or decrease

在此处输入图像描述

How can I draw this line between the table view cells? For now I just set a background image, but cell size will increase or decrease. Background image is not the permanent solution for me.

If you had a max number of rows less than, say, 50, it would be much easier to use a normal scroll view with repeating subviews, rather than a table view.

However, if you may have 100 - or a few hundred - rows, you may run into memory issues.

So, one way to get that "curving dashed line" is to use a custom view class in your cell that draws the line with a shape layer.

It would look something like this (the yellow rectangle is showing the "cell" frame):

在此处输入图像描述

So the code generating the bezier path for the shape layer would be:

  • move to 1
  • add line to 2
  • add arc with center c
  • add line to 3

Shape lines/strokes are centered on the path. So, if we use a line width of 4 , 2-points will extend above the top of the cell/view, and 2-points will extend below the bottom .

If we layout those same 4 views with Zero vertical spacing, and alternate right / left / right / left, we get this:

在此处输入图像描述

We can then implement that in our table view cell:

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

A couple issues will crop up though...

First, because the rows have variable heights, the line-lengths will be different. The dash-patterns don't "stretch to fill" the line, so the ends will vary:

在此处输入图像描述

The other issue would hit if your rows are taller than one-half the width (actually, less than half because we allow space at the sides).

Here's what that means:

在此处输入图像描述

Of course, that is more of a design issue than a coding issue, as it would be up to you to decide how you want the line to look in that case.

Here's the code I used to generate those images:

enum - for left/right layout:

enum LayoutDirection: Int {
    case left, right
}

the cells have 3 labels - so a simple 3-string struct for the data:

struct MyDataStruct {
    var first: String = ""
    var second: String = ""
    var third: String = ""
}

PieView - a simple pie-shape UIView subclass

class PieView: UIView {
    
    private let shapeLayer1 = CAShapeLayer()
    private let shapeLayer2 = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        [shapeLayer1, shapeLayer2].forEach { v in
            layer.addSublayer(v)
            v.fillColor = UIColor.systemOrange.cgColor
            v.strokeColor = UIColor.systemOrange.cgColor
            v.lineWidth = 2
        }
        shapeLayer1.fillColor = UIColor.clear.cgColor
    }
    override func layoutSubviews() {
        super.layoutSubviews()

        var bez: UIBezierPath!
        let ptC: CGPoint = CGPoint(x: bounds.midX, y: bounds.midY)
        let a1: Double = -90.0 * .pi / 180.0
        let a2: Double = 135.0 * .pi / 180.0

        bez = UIBezierPath()
        bez.addArc(withCenter: ptC, radius: bounds.midX, startAngle: a2, endAngle: a1, clockwise: true)
        shapeLayer1.path = bez.cgPath

        bez = UIBezierPath()
        bez.move(to: ptC)
        bez.addArc(withCenter: ptC, radius: bounds.midX, startAngle: a1, endAngle: a2, clockwise: true)
        bez.close()
        shapeLayer2.path = bez.cgPath
    }
    
}

MyDashedArcView - UIView subclass that draws the dashed-arc

class MyDashedArcView: UIView {
    
    public var layoutDirection: LayoutDirection = .left {
        didSet {
            setNeedsLayout()
        }
    }
    
    private 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()
    }
    private func commonInit() {
        shapeLayer = self.layer as? CAShapeLayer
        shapeLayer.strokeColor = UIColor.blue.cgColor
        shapeLayer.lineWidth = 4
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineDashPattern = [20, 10]
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let inset: CGFloat = 32.0
        let radius: CGFloat = bounds.midY
        var ptC: CGPoint = CGPoint(x: 0.0, y: bounds.midY)
        ptC.x = layoutDirection == .right ? bounds.maxX - (inset + radius) : inset + radius
        let a1: Double = -90.0 * .pi / 180.0
        let a2: Double = 90.0 * .pi / 180.0
        let xOff: CGFloat = 0.0
        
        let bez = UIBezierPath()
        bez.move(to: CGPoint(x: bounds.midX + xOff, y: bounds.minY - 0.0))
        bez.addLine(to: CGPoint(x: ptC.x, y: bounds.minY))
        if layoutDirection == .right {
            bez.addArc(withCenter: ptC, radius: bounds.midY, startAngle: a1, endAngle: a2, clockwise: true)
        } else {
            bez.addArc(withCenter: ptC, radius: bounds.midY, startAngle: a1, endAngle: a2, clockwise: false)
        }
        bez.addLine(to: CGPoint(x: bounds.midX + xOff, y: bounds.maxY))
        shapeLayer.path = bez.cgPath
    }
}

MyPieCell - table view cell

class MyPieCell: UITableViewCell {
    
    private var layoutDirection: LayoutDirection = .right {
        didSet {
            
            // update horizontal constraints to position the pieView and labels stack view
            
            let g = contentView
            
            pieHorizontalConstraint.isActive = false
            stackLeadingConstraint.isActive = false
            stackTrailingConstraint.isActive = false
            
            if layoutDirection == .left {
                // pie is on the left
                pieHorizontalConstraint = pieView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 48.0)
                stackLeadingConstraint = stack.leadingAnchor.constraint(equalTo: pieView.trailingAnchor, constant: 20.0)
                stackTrailingConstraint = stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0)
                [firstLabel, secondLabel, thirdLabel].forEach { v in
                    v.textAlignment = .left
                }
            } else {
                // pie is on the right
                pieHorizontalConstraint = pieView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -48.0)
                stackLeadingConstraint = stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0)
                stackTrailingConstraint = stack.trailingAnchor.constraint(equalTo: pieView.leadingAnchor, constant: -20.0)
                [firstLabel, secondLabel, thirdLabel].forEach { v in
                    v.textAlignment = .right
                }
            }
            
            pieHorizontalConstraint.isActive = true
            stackLeadingConstraint.isActive = true
            stackTrailingConstraint.isActive = true
            
        }
    }
    
    func fillData(_ str: MyDataStruct, direction: LayoutDirection) {
        firstLabel.text = str.first
        secondLabel.text = str.second
        thirdLabel.text = str.third
        layoutDirection = direction
        arcView.layoutDirection = direction
    }
    
    private let pieView = PieView()
    private let arcView = MyDashedArcView()
    
    private let firstLabel: UILabel = {
        let v = UILabel()
        v.font = .systemFont(ofSize: 13.0, weight: .regular)
        return v
    }()
    private let secondLabel: UILabel = {
        let v = UILabel()
        v.font = .systemFont(ofSize: 16.0, weight: .bold)
        return v
    }()
    private let thirdLabel: UILabel = {
        let v = UILabel()
        v.font = .systemFont(ofSize: 13.0, weight: .regular)
        v.numberOfLines = 0
        return v
    }()
    
    // stack view for the labels
    private let stack: UIStackView = {
        let v = UIStackView()
        v.axis = .vertical
        v.spacing = 2
        return v
    }()
    
    private var pieHorizontalConstraint: NSLayoutConstraint!
    private var stackLeadingConstraint: NSLayoutConstraint!
    private var stackTrailingConstraint: NSLayoutConstraint!
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        
        [firstLabel, secondLabel, thirdLabel].forEach { v in
            v.setContentCompressionResistancePriority(.required, for: .vertical)
            stack.addArrangedSubview(v)
        }
        [arcView, pieView, stack].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview(v)
        }
        
        let g = contentView
        
        // initialize the horizontal constraints that we will update
        //  based on left or right layout
        pieHorizontalConstraint = pieView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0)
        stackLeadingConstraint = stack.leadingAnchor.constraint(equalTo: pieView.trailingAnchor, constant: 20.0)
        stackTrailingConstraint = stack.trailingAnchor.constraint(equalTo: pieView.leadingAnchor, constant: -20.0)
        
        // giving avoid auto-layout complaints
        //  pieView is square (1:1 ratio)
        
        // pieView width constant
        pieView.widthAnchor.constraint(equalToConstant: 60.0).isActive = true
        
        let pieHeightConstraint = pieView.heightAnchor.constraint(equalTo: pieView.widthAnchor)
        pieHeightConstraint.priority = .required - 1
        pieHeightConstraint.isActive = true
        
        NSLayoutConstraint.activate([
            
            // constrain arcView to all 4 sides
            arcView.topAnchor.constraint(equalTo: g.topAnchor),
            arcView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            arcView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            arcView.bottomAnchor.constraint(equalTo: g.bottomAnchor),

            // center the pieView vertically
            pieView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
            // we want at least 12-points above and below the pieView
            pieView.topAnchor.constraint(greaterThanOrEqualTo: g.topAnchor, constant: 12.0),
            pieView.bottomAnchor.constraint(lessThanOrEqualTo: g.bottomAnchor, constant: -12.0),
            
            // center the labels stack view vertically
            stack.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
            // we want at least 12-points above and below the stack view
            stack.topAnchor.constraint(greaterThanOrEqualTo: g.topAnchor, constant: 12.0),
            stack.bottomAnchor.constraint(lessThanOrEqualTo: g.bottomAnchor, constant: -12.0),
            
        ])
        
        // we need to see the table view's background view through the cells
        contentView.backgroundColor = .clear
        self.backgroundColor = .clear
        
        // during development, if we want to see the framing
        //pieView.backgroundColor = .green
        //stack.backgroundColor = .yellow
    }
    
}

SampleTableVC - example view controller with table view

class SampleTableVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    var myData: [MyDataStruct] = []
    
    let tableView = UITableView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // generate some sample data
        let sampleStrings: [String] = [
            "Short string.",
            "Medium length string that may or may not wrap.",
            "This is a very long string that will definitely wrap. When running on an iPhone 8 in portrait orientation, it should wrap to four lines.",
        ]
        let thirdLabels: [Int] = [
            0, 1, 0, 1, 0, 1, 2, 0, 1, 2, 1, 2, 2, 2,
        ]
        var rowNum: Int = 0
        thirdLabels.forEach { n in
            var str: MyDataStruct = MyDataStruct()
            str.first = "Level \(rowNum)"
            str.second = "Foundation \(rowNum)"
            str.third = sampleStrings[n % sampleStrings.count]
            myData.append(str)
            rowNum += 1
        }
        // and some more data, with increasing number of lines for the third label
        for i in 4...16 {
            var str: MyDataStruct = MyDataStruct()
            str.first = "Level \(rowNum)"
            str.second = "Foundation \(rowNum)"
            str.third = (1...i).compactMap({"Line \($0)"}).joined(separator: "\n")
            myData.append(str)
            rowNum += 1
        }
        // and a few rows with extremely long strings
        let reallyLongString = "UILabel - A label can contain an arbitrary amount of text, but UILabel may shrink, wrap, or truncate the text, depending on the size of the bounding rectangle and properties you set. You can control the font, text color, alignment, highlighting, and shadowing of the text in the label.\n\nUITextField - Displays a rounded rectangle that can contain editable text. When a user taps a text field, a keyboard appears; when a user taps Return in the keyboard, the keyboard disappears and the text field can handle the input in an application-specific way. UITextField supports overlay views to display additional information, such as a bookmarks icon. UITextField also provides a clear text control a user taps to erase the contents of the text field."
        for _ in 1...5 {
            var str: MyDataStruct = MyDataStruct()
            str.first = "Level \(rowNum)"
            str.second = "Foundation \(rowNum)"
            str.third = reallyLongString
            myData.append(str)
            rowNum += 1
        }
        
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0),
            tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0),
            tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0),
            tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0),
        ])
        
        tableView.register(MyPieCell.self, forCellReuseIdentifier: "c")
        tableView.dataSource = self
        tableView.delegate = self
        tableView.separatorStyle = .none
        
        // because the dashed line will extend above the top of the first cell
        //  and below the bottom of the last cell
        //  we want to add a little "inset padding" on top and bottom of the table view
        
        var defaultInset = tableView.contentInset
        defaultInset.top += 8
        defaultInset.bottom += 8
        tableView.contentInset = defaultInset
        
        tableView.contentInsetAdjustmentBehavior = .never
        tableView.contentOffset.y = -8
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! MyPieCell
        let dir: LayoutDirection = indexPath.row % 2 == 0 ? .right : .left
        c.fillData(myData[indexPath.row], direction: dir)
        return c
    }
    
}

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM