- Fixes: disabled animations for the CollapsingMarkdownView when expanding/collapsing due to visual glitches

This commit is contained in:
mahee96
2025-02-28 01:02:41 +05:30
parent 2bea980d1f
commit cfaf79f878

View File

@@ -101,35 +101,56 @@ final class CollapsingMarkdownView: UIView {
initialize() initialize()
} }
private var needsCollapsing = false
private func checkIfNeedsCollapsing() {
let textSize = textView.sizeThatFits(CGSize(width: bounds.width, height: .greatestFiniteMagnitude))
let lineHeight = textView.font?.lineHeight ?? 0
let actualLines = textSize.height / lineHeight
needsCollapsing = actualLines > CGFloat(maximumNumberOfLines)
// Hide toggle button if no collapsing needed
toggleButton.isHidden = !needsCollapsing
}
private func updateCollapsedState() { private func updateCollapsedState() {
// Update the button title // Disable animations for this update to prevent gradual rearrangement
let title = isCollapsed ? NSLocalizedString("More", comment: "") : NSLocalizedString("Less", comment: "") UIView.performWithoutAnimation {
toggleButton.setTitle(title, for: .normal) // Update the button title
let title = isCollapsed ? NSLocalizedString("More", comment: "") : NSLocalizedString("Less", comment: "")
toggleButton.setTitle(title, for: .normal)
// Update text view constraints // Make sure toggle button is only visible when needed
if isCollapsed { toggleButton.isHidden = !needsCollapsing
textView.textContainer.maximumNumberOfLines = maximumNumberOfLines
// Create exclusion path for button // Update text view constraints
let buttonSize = toggleButton.sizeThatFits(CGSize(width: 1000, height: 1000)) if isCollapsed && needsCollapsing {
let buttonY = (textView.font?.lineHeight ?? 0) * CGFloat(maximumNumberOfLines - 1) textView.textContainer.maximumNumberOfLines = maximumNumberOfLines
let exclusionFrame = CGRect( // Create exclusion path for button
x: bounds.width - buttonSize.width - 5, // Add some padding let buttonSize = toggleButton.sizeThatFits(CGSize(width: 1000, height: 1000))
y: buttonY, let buttonY = (textView.font?.lineHeight ?? 0) * CGFloat(maximumNumberOfLines - 1)
width: buttonSize.width + 10, // Add padding around button
height: (textView.font?.lineHeight ?? 0) + 5
)
textView.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionFrame)] let exclusionFrame = CGRect(
} else { x: bounds.width - buttonSize.width - 5, // Add some padding
textView.textContainer.maximumNumberOfLines = 0 y: buttonY,
textView.textContainer.exclusionPaths = [] width: buttonSize.width + 10, // Add padding around button
height: (textView.font?.lineHeight ?? 0) + 5
)
textView.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionFrame)]
} else {
textView.textContainer.maximumNumberOfLines = 0
textView.textContainer.exclusionPaths = []
}
// Force an immediate layout update
textView.layoutIfNeeded()
self.layoutIfNeeded()
// Invalidate intrinsic content size to ensure proper sizing
self.invalidateIntrinsicContentSize()
} }
// Force layout update
textView.layoutIfNeeded()
self.invalidateIntrinsicContentSize()
} }
private func initialize() { private func initialize() {
@@ -187,35 +208,53 @@ final class CollapsingMarkdownView: UIView {
override func layoutSubviews() { override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
textView.frame = bounds UIView.performWithoutAnimation {
textView.frame = bounds
// Position toggle button // Check if content needs collapsing when layout changes
let buttonSize = toggleButton.sizeThatFits(CGSize(width: 1000, height: 1000)) if shouldResetLayout || previousSize?.width != bounds.width {
checkIfNeedsCollapsing()
shouldResetLayout = false
previousSize = bounds.size
}
if isCollapsed { // Only position toggle button if it's needed
let buttonY = (textView.font?.lineHeight ?? 0) * CGFloat(maximumNumberOfLines - 1) if !toggleButton.isHidden {
toggleButton.frame = CGRect( let buttonSize = toggleButton.sizeThatFits(CGSize(width: 1000, height: 1000))
x: bounds.width - buttonSize.width,
y: buttonY, if isCollapsed {
width: buttonSize.width, let buttonY = (textView.font?.lineHeight ?? 0) * CGFloat(maximumNumberOfLines - 1)
height: textView.font?.lineHeight ?? 0 toggleButton.frame = CGRect(
) x: bounds.width - buttonSize.width,
} else { y: buttonY,
// Position at the end of content when expanded width: buttonSize.width,
let textHeight = textView.sizeThatFits(bounds.size).height height: textView.font?.lineHeight ?? 0
let lineHeight = textView.font?.lineHeight ?? 0 )
toggleButton.frame = CGRect( } else {
x: bounds.width - buttonSize.width, // Position at the end of content when expanded
y: textHeight - lineHeight, let textHeight = textView.sizeThatFits(bounds.size).height
width: buttonSize.width, let lineHeight = textView.font?.lineHeight ?? 0
height: lineHeight toggleButton.frame = CGRect(
) x: bounds.width - buttonSize.width,
y: textHeight - lineHeight,
width: buttonSize.width,
height: lineHeight
)
}
}
} }
} }
@objc private func toggleCollapsed(_ sender: UIButton) { @objc private func toggleCollapsed(_ sender: UIButton) {
// Toggle the state instantly
isCollapsed.toggle() isCollapsed.toggle()
updateToggleButtonTitle()
// Update the UI without animation
UIView.performWithoutAnimation {
updateToggleButtonTitle()
updateCollapsedState()
}
// Notify any observer that a toggle occurred // Notify any observer that a toggle occurred
didToggleCollapse?() didToggleCollapse?()
} }
@@ -248,6 +287,9 @@ final class CollapsingMarkdownView: UIView {
) )
textView.attributedText = mutableAttributedString textView.attributedText = mutableAttributedString
// Check if content needs collapsing after setting text
checkIfNeedsCollapsing()
} }
} }