自 iOS 9 以来 UITableView 的滚动性能不佳(之前效果很好)

Posted

技术标签:

【中文标题】自 iOS 9 以来 UITableView 的滚动性能不佳(之前效果很好)【英文标题】:Bad scrolling performance in UITableView since iOS 9 (worked great before) 【发布时间】:2016-01-15 10:27:28 【问题描述】:

自从升级到 ios 9 后,之前运行良好的代码开始在滚动时表现出非常糟糕的性能。

我尝试从头开始创建一个最小示例,省略了单元格的大部分配置以及各种缓存和内容生成代码。然而,代码仍然不能快速阅读,并且分析显示不同:/

根据 Apple 开发者论坛(编辑:我找到了 link),这可能是由于自动布局发生了变化,并且在它的 superView 后面放置了繁重的布局视图使其不那么糟糕,仍然不够好,无法发布。

我怀疑问题出在UIKit 中,尤其是在自动布局和UITextView 中,因为一旦我开始设置cell.textView.text,滚动就会立即变粗。

有人做过类似的观察吗?对于许多人来说,这似乎并不是一个问题。

有什么建议可以让这更顺利吗?使用UILabels 可以解决这个问题,但在现实生活中,属性字符串中的链接是可以点击的,所以我认为没有办法避免UITextView

我正在使用自定义的 UITableViewCell 子类,我也对其进行了简化,但由于程序化的自动布局,它们仍然很容易阅读:

class AbstractRegularTimelineCell: UITableViewCell 
    weak var iconView : UIImageView!
    weak var headerLabel : UILabel!
    weak var timeLabel : UILabel!
    weak var regularContentContainer : UIView!
    weak var contentHeightConstraint : NSLayoutConstraint!
    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        commonInit()
    

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

    private func commonInit() 
        self.selectionStyle = .None
        // add the UI elements needed for all regular cells.

        let iv = UIImageView(/*image: UIImage(named: "icon")*/)
        iv.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(iv)
        self.iconView = iv
        iv.setContentCompressionResistancePriority(1000, forAxis: .Horizontal)
        iv.setContentCompressionResistancePriority(1000, forAxis: .Vertical)


        let hl = UILabel()
        hl.numberOfLines = 0
        hl.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(hl)
        self.headerLabel = hl
        hl.font = UIFont(name: "OpenSans-Semibold", size: UIFont.systemFontSize())

        let tiv = UIImageView(/*image: UIImage(named: "timestamp")*/)
        tiv.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(tiv)


        let tl = UILabel()
        tl.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(tl)
        self.timeLabel = tl

        tl.textColor = UIColor.lightGrayColor()
        tl.font = UIFont(name: "OpenSans", size: UIFont.systemFontSize()-3)

        let rcc = UIView()
        rcc.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(rcc)
        self.regularContentContainer = rcc
        rcc.setContentCompressionResistancePriority(1000, forAxis: .Vertical)
        rcc.setContentCompressionResistancePriority(100, forAxis: .Horizontal)
        self.contentView.sendSubviewToBack(rcc) // this might help, according to Apple Dev Forums




        // now, stitch the constraints together.
        let views = ["iv":iv, "hl":hl, "tl":tl, "rcc":rcc, "tiv":tiv]



        self.contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-(15)-[iv]-(15)-[hl]-(>=15)-|", options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: [:], views: views))

        self.contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(15)-[iv]", options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: nil, views: views))

        self.contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(17)-[hl]-(8)-[rcc]", options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: nil, views: views))


        var otherConstraints = [NSLayoutConstraint]()
        var c = NSLayoutConstraint(item: tiv, attribute: .Top, relatedBy: .Equal, toItem: rcc, attribute: .Bottom, multiplier: 1, constant: 8)

        c.priority = 600
        otherConstraints.append(c)

        c = NSLayoutConstraint(item: tiv, attribute: .Top, relatedBy: .GreaterThanOrEqual, toItem: rcc, attribute: .Bottom, multiplier: 1, constant: 6)

        otherConstraints.append(c)

        c = NSLayoutConstraint(item: tl, attribute: .Leading, relatedBy: .Equal, toItem: tiv, attribute: .Trailing, multiplier: 1, constant: 6)

        otherConstraints.append(c)

        c = NSLayoutConstraint(item: tiv, attribute: .CenterY, relatedBy: .Equal, toItem: tl, attribute: .CenterY, multiplier: 1, constant: 1)

        otherConstraints.append(c)

        c = NSLayoutConstraint(item: self.contentView, attribute: .Bottom, relatedBy: .Equal, toItem: tiv, attribute: .Bottom, multiplier: 1, constant: 10)

        otherConstraints.append(c)

        c = NSLayoutConstraint(item: tiv, attribute: .Leading, relatedBy: .Equal, toItem: hl, attribute: .Leading, multiplier: 1, constant: 0)

        otherConstraints.append(c)


        c = NSLayoutConstraint(item: rcc, attribute: NSLayoutAttribute.Height, relatedBy: .LessThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 10000)

        otherConstraints.append(c)
        self.contentHeightConstraint = c

        c = NSLayoutConstraint(item: self.contentView, attribute: .Trailing, relatedBy: .GreaterThanOrEqual, toItem: rcc, attribute: .Trailing, multiplier: 1, constant: 15)
        otherConstraints.append(c)

        c = NSLayoutConstraint(item: rcc, attribute: .Leading, relatedBy: .Equal, toItem: hl, attribute: .Leading, multiplier: 1, constant: 0)
        otherConstraints.append(c)

        self.contentView.addConstraints(otherConstraints)
    





class ExampleCell: AbstractRegularTimelineCell 
    weak var textView : UITextView!
    override required init(style: UITableViewCellStyle, reuseIdentifier: String?) 
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.setupTextView()
    

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        self.setupTextView()
    
    private func setupTextView() 
        let t = UITextView()
        t.translatesAutoresizingMaskIntoConstraints = false
        self.regularContentContainer.addSubview(t)
        self.regularContentContainer.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[t]|", options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: [:], views: ["t":t]))
        self.regularContentContainer.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[t]|", options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: [:], views: ["t":t]))
        self.textView = t
        t.scrollEnabled = false
    

不太长的视图控制器代码:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate 
    weak var tableView : UITableView!
    override func viewDidLoad() 
        super.viewDidLoad()
        let t = UITableView()
        t.registerClass(ExampleCell.classForCoder(), forCellReuseIdentifier: "example")
        t.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(t)
        self.tableView = t
        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[t]|", options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: [:], views: ["t":t]))
        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[t]|", options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: [:], views: ["t":t]))
        t.delegate = self
        t.dataSource = self
        t.estimatedRowHeight = 350
    


    func numberOfSectionsInTableView(tableView: UITableView) -> Int 
        return 1
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return 300
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 
        let cell : ExampleCell = tableView.dequeueReusableCellWithIdentifier("example", forIndexPath: indexPath) as! ExampleCell
        cell.timeLabel.text = "probably never"
        switch indexPath.row % 3 
        case 0:
            cell.headerLabel.text = "First paragraph (Part one of two)"
            cell.textView.text = "As any dedicated reader can clearly see, the Ideal of practical reason is a representation of, as far as I know, the things in themselves; as I have shown elsewhere, the phenomena should only be used as a canon for our understanding. These paralogisms of practical reason are what first give rise to the architectonic of practical reason."
        case 1:
            cell.headerLabel.text = "First paragraph, 2/2"
            cell.textView.text = "As will easily be shown in the next section, reason would thereby be made to contradict, in view of these considerations, the Ideal of practical reason, yet the manifold depends on the phenomena. Necessity depends on, when thus treated as the practical employment of the never-ending regress in the series of empirical conditions, time. Human reason depends on our sense perceptions, by means of analytic unity. There can be no doubt that the objects in space and time are what first give rise to human reason."
        default:
            cell.headerLabel.text = "Second paragraph"
            cell.textView.text = "Let us suppose that the noumena have nothing to do with necessity, since knowledge of the Categories is a posteriori. Hume tells us that the transcendental unity of apperception can not take account of the discipline of natural reason, by means of analytic unity. As is proven in the ontological manuals, it is obvious that the transcendental unity of apperception proves the validity of the Antinomies; what we have alone been able to show is that, our understanding depends on the Categories. It remains a mystery why the Ideal stands in need of reason. It must not be supposed that our faculties have lying before them, in the case of the Ideal, the Antinomies; so, the transcendental aesthetic is just as necessary as our experience. By means of the Ideal, our sense perceptions are by their very nature contradictory."
        
    return cell
    

【问题讨论】:

您正在嵌套滚动视图,这样的视图预计表现不佳。如果您只需要可点击的链接,用标签替换文本视图并不难,以this answer 为例。 【参考方案1】:

正如@A-Live 在 cmets 中指出的那样,问题在于嵌套滚动视图。

为了让这个问题对任何人都有用,我最终将 UITextViews 替换为 TTTAttributedLabels,这显着提高了性能。

此外,无法弄清楚为什么在 iOS 9 更新后性能会显着下降;我怀疑对自动布局引擎进行了未记录的更改;有一个好的解决方法,没有必要进一步调查。

【讨论】:

以上是关于自 iOS 9 以来 UITableView 的滚动性能不佳(之前效果很好)的主要内容,如果未能解决你的问题,请参考以下文章

UITableView 自定义单元格更改值 iOS 9

iOS Bug 之 iOS 9.3.x UITableView 上方空出一块

iOS 9 UITableView分隔符插入(显着的左边距)

iOS 自定义 UITableView 单元格

iOS 9 UITableView 分隔符插入(显着的左边距)

支持iOS11UITableView左滑删除自定义 - 实现多选项并使用自定义图片