Charlie
Charlie

Reputation: 11777

Auto Layout performance with nested relative widths?

To summarize my problem: when I have many child views with widths equal to the width of their parent, where the parent's width is equal to their parent's width, there is a huge performance hit when the window is horizontally resized.


I am retrieving a large number of comments from an API and decided to create an individual NSView for each comment and then vertically space them all within an NSScrollView. Each individual comment grows to the height of the comment's text and matches the width of the container. This works fine when there aren't many loaded comments, but when the comments routinely exceed 100, there is a huge performance hit when the window is horizontally resized.

I create each comment's view:

var lastComment:NSView = documentView // The last view to vertically position around
for (index, comment) in commentsArray.enumerate() {
    var data = comment
    data["attributedString"] = attributedString
    let commentView = NSCommentView(data: data)
    documentView.addSubview(commentView)
    let verticalSpacing = (index == 0) ? 0 : 10 // Position the first comment without any top spacing 
    let secondAttribute = (index == 0) ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom
    let widthConstraint = NSLayoutConstraint(item: commentView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: documentView, attribute: NSLayoutAttribute.Width, multiplier: 1, constant: 0)
    let topConstraint = NSLayoutConstraint(item: commentView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: lastComment, attribute: secondAttribute, multiplier: 1, constant: verticalSpacing)
    documentView.addConstraints([widthConstraint, topConstraint])
    lastComment = commentView
    if index == commentsArray.count - 1{
        let bottom = NSLayoutConstraint(item: commentView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: documentView, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
        documentView.addConstraint(bottom)
    }
}

Where NSCommentView is defined as:

class NSCommentView: NSView {

    func setupFrame(){
        self.wantsLayer = true
        self.translatesAutoresizingMaskIntoConstraints = false
        self.layer?.backgroundColor = NSColor.redColor().CGColor
        // Set height of comment, fixed for testing
        let heightConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 50)
        self.addConstraint(heightConstraint)
    }

    func setupTextView(attributedString: NSAttributedString){
        let textView = NSTextView()
        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.verticallyResizable = true
        textView.textStorage?.setAttributedString(attributedString)
        self.addSubview(textView)
        let heightConstraint = NSLayoutConstraint(item: textView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 50)
        textView.addConstraint(heightConstraint)

        // The performance hit occurs when I set a width equal to the parent width:
        let widthConstraint = NSLayoutConstraint(item: textView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Width, multiplier: 1, constant: 0)
        self.addConstraint(widthConstraint)
    }

    convenience init(data:[String: AnyObject?]){
        self.init()
        setupFrame()
        if data["isComment"] as! Bool {
            let attributedString = data["attributedString"] as! NSAttributedString
            setupTextView(attributedString)
        }
    }
}

As you can see from the code, the issue is when I attempt to set the width of the NSTextView to the width of the NSCommentView. If I simply set it as a constant, there is no real performance degradation.

Is my view hierarchy not setup correctly, or am I overlooking something which is causing this performance issue?

Upvotes: 0

Views: 183

Answers (1)

Ben
Ben

Reputation: 1127

I agree with Ken that you're re-inventing the table view (and all it's associated optimizations). Is there an overriding reason to not use table view or collection view?

The performance hit is because when resizing horizontally you're re-calculating the size of every single subview! Not just the ones that are visible.

If you must do it this way. Some optimization attempts would be to:

  • Not use width, but to bind the leading and trailing edges to the superview.
  • Don't bind all subviews to the superview. Bind only the first to the superview, and all others to that first view.
  • Duplicate table/collectionview strategies and only update the constants for visible views. (this would be kind of silly though when you could just use one of those!)

I also noticed that you're fixing the height of the comment views right now. Once you enable that to flex, the performance will suffer even more since the views will have to resize across two dimensions, in realtime, impacting all the views below them on the page.

TL;DR - Save yourself a lot of trouble. Use a table or collection view.

Upvotes: 2

Related Questions