适用于 iOS 11+ 的可扩展自定义 UITableViewCell [英] Expandable custom UITableViewCell for iOS 11+

查看:30
本文介绍了适用于 iOS 11+ 的可扩展自定义 UITableViewCell的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现很多人都以各种形式问过这个问题,而且答案也很多,所以我总结一下我的具体情况,希望能得到更具体的答案.首先,我正在为 iOS 11+ 构建并且拥有相对较新版本的 XCode(11+).也许不是最新的,但足够新.

I realize that many people have asked this question in various forms and the answers are all over the page, so let me summarize my specific situation in hopes of getting more specific answers. First of all, I'm building for iOS 11+ and have a relatively recent version of XCode (11+). Maybe not the latest, but recent enough.

基本上,我需要一个自动调整大小的表格视图,当用户与它们交互时,其中的单元格可以在运行时展开和折叠.在 viewDidLoad 中,我将 rowHeight 设置为 UITableView.automaticDimension 并将estimatedRowHeight 设置为大于固定值 44 的某个数字.但是单元格并没有像它应该的那样扩展,即使我似乎已经尝试了书中的所有建议.

Basically, I need a self-sizing tableview where the cells may expand and collapse at runtime when the user interacts with them. In viewDidLoad I set the rowHeight to UITableView.automaticDimension and estimatedRowHeight to some number that's bigger than the canned value of 44. But the cell is not expanding like it should, even though I seem to have tried every bit of advice in the book.

如果这很重要,我有一个用于表格单元格的自定义类,但没有 .XIB 文件 - UI 直接在原型中定义.我已经尝试了许多其他变体,但感觉最简单的是让 UIStackView 成为原型的唯一直接子元素(可以说收入"功能都在其中.就我而言,它们包括一个label 和另一个 tableview - 我嵌套了 3 层深 - 但这可能不是重点)并将它的所有 4 个边缘限制到父级.我已经尝试过,并且我已经修改了堆栈视图中的分布(填充、均匀填充、按比例填充),但似乎都不起作用.我该怎么做才能使细胞正确扩增?

If that matters, I have a custom class for the table cell but no .XIB file for it - the UI is defined directly in the prototype. I've tried a number of other variations, but it feels like the easiest is making a UIStackView the only direct child of the prototype (the "revenue" features so to speak would all be inside it. In my case, they include a label and another tableview - I nest 3 levels deep - but that's probably beside the point) and constraining all 4 of it's edges to the parent. I've tried that, and I've tinkered with the distribution in the stack view (Fill, Fill Evenly, Fill Proportionately), but none of it seems to work. What can I do to make the cells expand properly?

如果有人想知道,我曾经覆盖了 heightForRowAt 但现在我不这样做,因为在运行时预测高度并不容易,我希望这个过程可以自动化.

In case anyone's wondering, I used to override heightForRowAt but now I don't because it's not easy to predict the height at runtime and I'm hoping the process could be automated.

推荐答案

从基础开始...

这是一个带有两个标签的垂直UIStackView:

Here is a vertical UIStackView with two labels:

红色轮廓显示堆栈视图的框架.

The red outline shows the frame of the stack view.

如果我们点击按钮,它会设置bottomLabel.isHidden = true:

If we tap the button, it will set bottomLabel.isHidden = true:

请注意,除了被隐藏之外,堆栈视图还会移除它所占用的空间.

Notice that in addition to being hidden, the stack view removes the space it was occupying.

现在,我们可以使用表格视图单元格中的堆栈视图来实现展开/折叠功能.

Now, we can do that with a stack view in a table view cell to get expand/collapse functionality.

我们将从每隔一行展开:

We'll start with every-other row expanded:

现在我们点击折叠"按钮第 1 行的按钮,我们得到:

Now we tap the "Collapse" button for row 1 and we get:

不是我们想要的.我们成功崩溃"了单元格内容,但表格视图对此一无所知.

Not quite what we want. We successfully "collapsed" the cell content, but the table view doesn't know anything about it.

所以,我们可以添加一个闭包...当我们点击按钮时,单元格中的代码将显示/隐藏底部标签AND它将使用关闭告诉表视图发生了什么.我们的 cellForRowAt 函数看起来像这样:

So, we can add a closure... when we tap the button, the code in the cell will show/hide the bottom label AND it will use the closure to tell the table view what happened. Our cellForRowAt func looks like this:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! ExpColCell

    c.setData("Top \(indexPath.row)", str2: "Bottom \(indexPath.row)\n2\n3\n4\n5", isCollapsed: isCollapsedArray[indexPath.row])

    c.didChangeHeight = { [weak self] isCollapsed in
        guard let self = self else { return }
        // update our data source
        self.isCollapsedArray[indexPath.row] = isCollapsed
        // tell the tableView to re-run its layout
        self.tableView.performBatchUpdates(nil, completion: nil)
    }

    return c
}

我们得到:

这是一个完整的例子:

简单的虚线轮廓视图"

class DashedOutlineView: UIView {
    
    @IBInspectable var dashColor: UIColor = .red
    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
        shapeLayer.lineWidth = 1.0
        shapeLayer.lineDashPattern = [8,8]
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        shapeLayer.strokeColor = dashColor.cgColor
        shapeLayer.path = UIBezierPath(rect: bounds).cgPath
    }
}

细胞类

class ExpColCell: UITableViewCell {

    public var didChangeHeight: ((Bool) -> ())?
    
    private let stack = UIStackView()
    private let topLabel = UILabel()
    private let botLabel = UILabel()
    private let toggleButton = UIButton()
    
    private let outlineView = DashedOutlineView()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        // button properties
        toggleButton.translatesAutoresizingMaskIntoConstraints = false
        toggleButton.backgroundColor = .systemBlue
        toggleButton.setTitleColor(.white, for: .normal)
        toggleButton.setTitleColor(.gray, for: .highlighted)
        toggleButton.setTitle("Collapse", for: [])
        
        // label properties
        topLabel.text = "Top Label"
        botLabel.text = "Bottom Label"
        topLabel.font = .systemFont(ofSize: 32.0)
        botLabel.font = .italicSystemFont(ofSize: 24.0)
        topLabel.backgroundColor = .green
        botLabel.backgroundColor = .systemTeal
        
        botLabel.numberOfLines = 0
        
        // outline view properties
        outlineView.translatesAutoresizingMaskIntoConstraints = false
        
        // stack view properties
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.axis = .vertical
        stack.spacing = 8
        
        // add the labels
        stack.addArrangedSubview(topLabel)
        stack.addArrangedSubview(botLabel)
        
        // add outlineView, stack view and button to contentView
        contentView.addSubview(outlineView)
        contentView.addSubview(stack)
        contentView.addSubview(toggleButton)
        
        // we'll use the margin guide
        let g = contentView.layoutMarginsGuide
        
        NSLayoutConstraint.activate([
            
            stack.topAnchor.constraint(equalTo: g.topAnchor),
            stack.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            
            outlineView.topAnchor.constraint(equalTo: stack.topAnchor),
            outlineView.leadingAnchor.constraint(equalTo: stack.leadingAnchor),
            outlineView.trailingAnchor.constraint(equalTo: stack.trailingAnchor),
            outlineView.bottomAnchor.constraint(equalTo: stack.bottomAnchor),

            toggleButton.topAnchor.constraint(equalTo: g.topAnchor),
            toggleButton.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            toggleButton.leadingAnchor.constraint(equalTo: stack.trailingAnchor, constant: 16.0),
            toggleButton.widthAnchor.constraint(equalToConstant: 92.0),
            
        ])
        
        // we set the bottomAnchor constraint like this to avoid intermediary auto-layout warnings
        let c = stack.bottomAnchor.constraint(equalTo: g.bottomAnchor)
        c.priority = UILayoutPriority(rawValue: 999)
        c.isActive = true

        // set label Hugging and Compression to prevent them from squeezing/stretching
        topLabel.setContentHuggingPriority(.required, for: .vertical)
        topLabel.setContentCompressionResistancePriority(.required, for: .vertical)
        botLabel.setContentHuggingPriority(.required, for: .vertical)
        botLabel.setContentCompressionResistancePriority(.required, for: .vertical)

        contentView.clipsToBounds = true
        
        toggleButton.addTarget(self, action: #selector(toggleButtonTapped), for: .touchUpInside)
        
    }
    
    func setData(_ str1: String, str2: String, isCollapsed: Bool) -> Void {
        topLabel.text = str1
        botLabel.text = str2
        botLabel.isHidden = isCollapsed
        updateButtonTitle()
    }
    func updateButtonTitle() -> Void {
        let t = botLabel.isHidden ? "Expand" : "Collapse"
        toggleButton.setTitle(t, for: [])
    }
    
    @objc func toggleButtonTapped() -> Void {
        botLabel.isHidden.toggle()
        updateButtonTitle()
        
        // comment / un-comment this line to see the difference
        didChangeHeight?(botLabel.isHidden)
    }
}

和一个表视图控制器来演示

class ExpColTableViewController: UITableViewController {

    var isCollapsedArray: [Bool] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.register(ExpColCell.self, forCellReuseIdentifier: "c")
        
        // 16 "rows" start with every-other row collapsed
        for i in 0..<15 {
            isCollapsedArray.append(i % 2 == 0)
        }
        
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return isCollapsedArray.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! ExpColCell

        c.setData("Top \(indexPath.row)", str2: "Bottom \(indexPath.row)\n2\n3\n4\n5", isCollapsed: isCollapsedArray[indexPath.row])

        c.didChangeHeight = { [weak self] isCollapsed in
            guard let self = self else { return }
            // update our data source
            self.isCollapsedArray[indexPath.row] = isCollapsed
            // tell the tableView to re-run its layout
            self.tableView.performBatchUpdates(nil, completion: nil)
        }

        return c
    }
}

这篇关于适用于 iOS 11+ 的可扩展自定义 UITableViewCell的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆