Skip to content

Commit

Permalink
Optimized Textview size calculations (#207)
Browse files Browse the repository at this point in the history
* Updated recalculation of grid cell bounds

* Moved size calculation of AutogrowingTextView to background
  • Loading branch information
rajdeep authored Jul 31, 2023
1 parent 34c484e commit 62fcf1c
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 23 deletions.
8 changes: 8 additions & 0 deletions Proton/Sources/Swift/Attachment/Attachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,14 @@ open class Attachment: NSTextAttachment, BoundsObserving {
public override func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect {
self.indexInContainer = charIndex

// When calculating size of EditorView, this may be called on the background thread. Since size of attachment depends on contained view,
// we need to put the bounds calculation back on main.
guard Thread.isMainThread else {
return DispatchQueue.main.sync {
attachmentBounds(for: textContainer, proposedLineFragment: lineFrag, glyphPosition: position, characterIndex: charIndex)
}
}

guard let textContainer = textContainer,
textContainer.size.height > 0,
textContainer.size.width > 0
Expand Down
16 changes: 10 additions & 6 deletions Proton/Sources/Swift/Base/AutogrowingTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ class AutogrowingTextView: UITextView {
private func recalculateHeight() {
let bounds = self.bounds.integral
let fittingSize = self.calculatedSize(attributedText: attributedText, frame: frame.size, textContainerInset: textContainerInset)

self.isScrollEnabled = (fittingSize.height > bounds.height) || (self.maxHeight > 0 && self.maxHeight < fittingSize.height)
heightAnchorConstraint.constant = min(fittingSize.height, contentSize.height)
self.heightAnchorConstraint.constant = min(fittingSize.height, self.contentSize.height)

}

override open func sizeThatFits(_ size: CGSize) -> CGSize {
Expand All @@ -90,11 +92,13 @@ class AutogrowingTextView: UITextView {
}

private func calculatedSize(attributedText: NSAttributedString, frame: CGSize, textContainerInset: UIEdgeInsets) -> CGSize {
// Adjust for horizontal paddings in textview to exclude from overall available width for attachment
let horizontalAdjustments = (textContainer.lineFragmentPadding * 2) + (textContainerInset.left + textContainerInset.right)
let boundingRect = attributedText.boundingRect(with: CGSize(width: frame.width - horizontalAdjustments, height: .greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], context: nil).integral
DispatchQueue.global(qos: .userInteractive).sync { [lineFragmentPadding = textContainer.lineFragmentPadding ] in
// Adjust for horizontal paddings in textview to exclude from overall available width for attachment
let horizontalAdjustments = (lineFragmentPadding * 2) + (textContainerInset.left + textContainerInset.right)
let boundingRect = attributedText.boundingRect(with: CGSize(width: frame.width - horizontalAdjustments, height: .greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], context: nil).integral

let insets = UIEdgeInsets(top: -textContainerInset.top, left: -textContainerInset.left, bottom: -textContainerInset.bottom, right: -textContainerInset.right)
return boundingRect.inset(by: insets).size
let insets = UIEdgeInsets(top: -textContainerInset.top, left: -textContainerInset.left, bottom: -textContainerInset.bottom, right: -textContainerInset.right)
return boundingRect.inset(by: insets).size
}
}
}
39 changes: 22 additions & 17 deletions Proton/Sources/Swift/Grid/View/GridContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -283,38 +283,43 @@ class GridContentView: UIScrollView {
recalculateCellBounds()
}

private func recalculateCellBounds() {
private func recalculateCellBounds(initiatingCell: GridCell? = nil) {
frozenRowsConstraints.forEach { $0.isActive = false }
frozenColumnsConstraints.forEach { $0.isActive = false }
removeConstraints(frozenRowsConstraints + frozenColumnsConstraints)

for c in grid.cells.reversed() {
var cells = grid.cells
if let initiatingCell {
cells = [initiatingCell]
}

for cell in cells {
// TODO: Optimize to recalculate frames for affected cells only i.e. row>=current

// Set the frame of the cell before adding to superview
// This is required to avoid breaking layout constraints
// as default size is 0
let frame = grid.frameForCell(c, basedOn: bounds.size)
c.frame = frame
c.contentView.frame = frame
c.widthAnchorConstraint.constant = frame.width
c.heightAnchorConstraint.constant = frame.height
let frame = grid.frameForCell(cell, basedOn: bounds.size)
cell.frame = frame
cell.contentView.frame = frame
cell.widthAnchorConstraint.constant = frame.width
cell.heightAnchorConstraint.constant = frame.height

// Add to grid if this is a newly inserted cell after initial setup.
// A new cell may exist as a result of inserting a new row/column
// or splitting an existing merged cell
if c.contentView.superview == nil {
addSubview(c.contentView)
c.topAnchorConstraint = c.contentView.topAnchor.constraint(equalTo: topAnchor, constant: frame.minY)
c.leadingAnchorConstraint = c.contentView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: frame.minX)
if cell.contentView.superview == nil {
addSubview(cell.contentView)
cell.topAnchorConstraint = cell.contentView.topAnchor.constraint(equalTo: topAnchor, constant: frame.minY)
cell.leadingAnchorConstraint = cell.contentView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: frame.minX)
} else {
c.topAnchorConstraint?.constant = frame.minY
c.leadingAnchorConstraint?.constant = frame.minX
cell.topAnchorConstraint?.constant = frame.minY
cell.leadingAnchorConstraint?.constant = frame.minX
}

freezeColumnCellIfRequired(c)
freezeRowCellIfRequired(c)
gridContentViewDelegate?.gridContentView(self, didLayoutCell: c)
freezeColumnCellIfRequired(cell)
freezeRowCellIfRequired(cell)
gridContentViewDelegate?.gridContentView(self, didLayoutCell: cell)
}

boundsObserver?.didChangeBounds(CGRect(origin: bounds.origin, size: frame.size), oldBounds: bounds)
Expand Down Expand Up @@ -404,7 +409,7 @@ extension GridContentView: GridCellDelegate {
grid.rowHeights[row].currentHeight = grid.maxContentHeightCellForRow(at: row)?.contentSize.height ?? 0
}

recalculateCellBounds()
recalculateCellBounds(initiatingCell: cell)
gridContentViewDelegate?.gridContentView(self, didChangeBounds: cell.frame, in: cell)
}

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 62fcf1c

Please sign in to comment.