【问题标题】:UITableView with UITableViewCells + AutoLayout - Not As Smooth As It *Should* Be带有 UITableViewCells + AutoLayout 的 UITableView - 不像*应该*那样平滑
【发布时间】:2016-03-14 00:39:44
【问题描述】:

我最近发布了一个关于带有自定义 UITableCells 的 UITableView 的问题,当单元格的子视图使用 AutoLayout 定位时,该问题不流畅。我得到一些 cmets 表明缺乏平滑度是由于单元格的复杂布局。虽然我同意单元格布局越复杂,tableView 必须进行更多计算才能获得单元格的高度,但我认为 10-12 UIView 和 UILabel 子视图不会导致我在滚动时看到的滞后量iPad。

所以为了进一步证明我的观点,我创建了一个 UIViewController 项目,其中包含一个 UITableView 子视图和自定义 UITableViewCells,它们的子类中只有 2 个标签。而且滚动仍然不是很流畅。从我的角度来看,这是你能得到的最基本的——所以如果 UITableView 在这种设计下仍然不能正常工作,我一定是遗漏了一些东西。

下面使用的estimatedRowHeight 110 与实际行高平均值非常接近。当我使用“用户界面检查器”并逐个查看每个单元格的高度时,它们的范围是 103 - 124。

请记住,当我将下面的代码切换为使用estimatedRowHeightUITableViewAutomaticDimension 而是实现func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {,使用帧值计算高度时,UITableView 像黄油一样滚动.

应用截图(供参考)

App源代码(滚动不流畅)

// The custom `Quote` object that holds the
// properties for our data mdoel
class Quote {
    var text: String!
    var author: String!

    init(text: String, author: String) {
        self.text = text
        self.author = author
    }
}


// Custom UITableView Cell, using AutoLayout to
// position both a "labelText" (the quote itself)
// and "labelAuthor" (the author's name) label
class CellQuote: UITableViewCell {
    private var containerView: UIView!
    private var labelText: UILabel!
    private var labelAuthor: UILabel!


    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        contentView.backgroundColor = UIColor.whiteColor()

        containerView = UIView()
        containerView.backgroundColor = UIColor(
            red: 237/255,
            green: 237/255,
            blue: 237/255,
            alpha: 1.0
        )
        contentView.addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.leadingAnchor.constraintEqualToAnchor(contentView.leadingAnchor, constant: 0).active = true
        containerView.trailingAnchor.constraintEqualToAnchor(contentView.trailingAnchor, constant: 0).active = true
        containerView.topAnchor.constraintEqualToAnchor(contentView.topAnchor, constant: 4).active = true
        containerView.bottomAnchor.constraintEqualToAnchor(contentView.bottomAnchor, constant: 0).active = true

        labelText = UILabel()
        labelText.numberOfLines = 0
        labelText.font = UIFont.systemFontOfSize(18)
        labelText.textColor = UIColor.darkGrayColor()

        containerView.addSubview(labelText)
        labelText.translatesAutoresizingMaskIntoConstraints = false
        labelText.leadingAnchor.constraintEqualToAnchor(containerView.leadingAnchor, constant: 20).active = true
        labelText.topAnchor.constraintEqualToAnchor(containerView.topAnchor, constant: 20).active = true
        labelText.trailingAnchor.constraintEqualToAnchor(containerView.trailingAnchor, constant: -20).active = true

        labelAuthor = UILabel()
        labelAuthor.numberOfLines = 0
        labelAuthor.font = UIFont.boldSystemFontOfSize(18)
        labelAuthor.textColor = UIColor.blackColor()

        containerView.addSubview(labelAuthor)
        labelAuthor.translatesAutoresizingMaskIntoConstraints = false
        labelAuthor.topAnchor.constraintEqualToAnchor(labelText.bottomAnchor, constant: 20).active = true
        labelAuthor.leadingAnchor.constraintEqualToAnchor(containerView.leadingAnchor, constant: 20).active = true
        labelAuthor.trailingAnchor.constraintEqualToAnchor(containerView.trailingAnchor, constant: -20).active = true
        labelAuthor.bottomAnchor.constraintEqualToAnchor(containerView.bottomAnchor, constant: -20).active = true

        self.selectionStyle = UITableViewCellSelectionStyle.None
    }

    func configureWithData(quote: Quote) {
        labelText.text = quote.text
        labelAuthor.text = quote.author
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

// The UIViewController that is a 
class ViewController: UIViewController, UITableViewDataSource {

    var tableView: UITableView!
    var dataItems: [Quote]!

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView = UITableView()
        tableView.dataSource = self

        tableView.registerClass(CellQuote.self, forCellReuseIdentifier: "cellQuoteId")

        tableView.backgroundColor = UIColor.whiteColor()
        tableView.separatorStyle = UITableViewCellSeparatorStyle.None
        tableView.estimatedRowHeight = 110
        tableView.rowHeight = UITableViewAutomaticDimension

        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor).active = true
        tableView.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 20).active = true
        tableView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor).active = true
        tableView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor).active = true

        dataItems = [
            Quote(text: "One kernel is felt in a hogshead; one drop of water helps to swell the ocean; a spark of fire helps to give light to the world. None are too small, too feeble, too poor to be of service. Think of this and act.", author: "Michael.Frederick"),
            Quote(text: "A timid person is frightened before a danger, a coward during the time, and a courageous person afterward.", author: "Lorem.Approbantibus."),
            Quote(text: "There is only one way to defeat the enemy, and that is to write as well as one can. The best argument is an undeniably good book.", author: "Lorem.Fruitur."),
            // ... many more quotes ...
        ]

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    // MARK: - UITableViewDataSource

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

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

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cellQuoteId") as! CellQuote
        cell.configureWithData(dataItems[indexPath.row])
        return cell
    }
}

我喜欢下面马特的建议,但我仍在尝试对其进行调整以适合我:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var cellHeights: [CGFloat] = [CGFloat]()

    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        if cellHeights.count == 0 {
            var cellHeights = [CGFloat]()
            let numQuotes: Int = dataItems.count

            for index in 0...numQuotes - 1 {
                let cell = CellQuote()
                let quote = dataItems[index]

                cell.configureWithData(quote)
                let size = cell.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
                cellHeights.append(size.height)
            }

            self.cellHeights = cellHeights
        }

        return self.cellHeights[indexPath.row]

    }
}

【问题讨论】:

    标签: ios swift autolayout uitableview


    【解决方案1】:

    我从来没有发现自动行高机制像我们在自动布局出现之前使用的旧计算布局技术那样平滑。使用 Instruments 可以很容易地看到,瓶颈是运行时必须在每个新单元格滚动到视图中时调用 systemLayoutSizeFittingSize:

    在我的书中,我展示了我的首选技术,即在表格视图首次出现时一次计算所有单元格的高度。这意味着我可以从那时起立即向heightForRowAtIndexPath 提供答案,从而获得最佳的用户体验。此外,如果您随后将您对 dequeueReusableCellWithIdentifier 的调用替换为更好、更现代的 dequeueReusableCellWithIdentifier:forIndexPath,您的优势是单元格以正确的大小出现在您面前,并且不需要进一步的布局在那之后。

    【讨论】:

    • 这对我来说听起来不错 - @matt,一旦表格视图首次出现,我如何计算所有单元格的高度,我很想知道如何!我会缓存高度,然后完全在heightForRowAtIndexPath func 中使用它们
    • 太棒了,也会查看 dequeueReusableCellWithIdentifier:forIndexPath!
    • 我实现了您的解决方案,但由于某种原因,我的所有单元格都以相同的高度返回。我认为这与我的setUpCell:forIndexPath 函数有关,我一直将其作为我的configureWithData(quote: Quote) function
    • 我将您对heightForRowAtIndexPath 的建议的实现复制到我上面的原始问题中。
    【解决方案2】:

    两个文本标签的清晰背景导致性能问题。添加这些行,您应该会看到性能提升:

    labelText.backgroundColor = containerView.backgroundColor
    labelAuthor.backgroundColor = containerView.backgroundColor
    

    检查任何其他潜在问题的好方法是在 iOS 模拟器的“调试”菜单选项中打开“颜色混合图层”

    更新

    通常我为动态单元格高度所做的是创建一个原型单元格并将其用于调整大小。这是你在你的情况下会做的事情:

    class CellQuote: UITableViewCell {
        private static let prototype: CellQuote = {
            let cell = CellQuote(style: .Default, reuseIdentifier: nil)
            cell.contentView.translatesAutoresizingMaskIntoConstraints = false
            return cell
        }()
    
        static func heightForQuote(quote: Quote, tableView:UITableView) -> CGFloat {
            prototype.configureWithData(quote)
            prototype.labelText.preferredMaxLayoutWidth = CGRectGetWidth(tableView.frame)-40
            prototype.labelAuthor.preferredMaxLayoutWidth = CGRectGetWidth(tableView.frame)-40
    
            prototype.layoutIfNeeded();
            return CGRectGetHeight(prototype.contentView.frame)
        }
    
        // existing code here
    }
    

    在您的 viewDidLoad 中删除 rowHeight 和estimatedRowHeight 行并替换为成为代表

    class ViewController {
        override func viewDidLoad() {
            // existing code
    
            self.tableView.delegate = self
    
            // existing code
        }
    
        // get prototype cell height
        func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
            let quote = dataItems[indexPath.row]
            return CellQuote.heightForQuote(quote, tableView: tableView)
        }
    

    【讨论】:

    • 感谢@Casey,我添加了这些线条来为标签的背景着色,但并没有看到太大的不同。现在要运行颜色混合图层。
    • 当我打开颜色混合图层时,我的 TableView 中的所有内容都是绿色的。唯一红色的是状态栏的文本(运营商、Wifi、时间和电池),以及滚动时右侧的滚动条指示器。
    • 是的,添加这两行后,它应该全是绿色的。顺便说一句,您的代码无法编译而无需编辑引号字符串
    • 对不起@Casey,我尝试为引号字符串输入新行以使其在 SO 上更具可读性,但我必须用多行字符串破坏它。我的坏,会更新。
    • 这行得通!看看我现在能不能把它和一些缓存机制结合起来!
    猜你喜欢
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-18
    • 2013-02-19
    • 2015-03-17
    • 1970-01-01
    相关资源
    最近更新 更多