- 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: "")
// Update text view constraints toggleButton.setTitle(title, for: .normal)
if isCollapsed {
textView.textContainer.maximumNumberOfLines = maximumNumberOfLines
// Create exclusion path for button // Make sure toggle button is only visible when needed
let buttonSize = toggleButton.sizeThatFits(CGSize(width: 1000, height: 1000)) toggleButton.isHidden = !needsCollapsing
let buttonY = (textView.font?.lineHeight ?? 0) * CGFloat(maximumNumberOfLines - 1)
let exclusionFrame = CGRect( // Update text view constraints
x: bounds.width - buttonSize.width - 5, // Add some padding if isCollapsed && needsCollapsing {
y: buttonY, textView.textContainer.maximumNumberOfLines = maximumNumberOfLines
width: buttonSize.width + 10, // Add padding around button
height: (textView.font?.lineHeight ?? 0) + 5 // Create exclusion path for button
) let buttonSize = toggleButton.sizeThatFits(CGSize(width: 1000, height: 1000))
let buttonY = (textView.font?.lineHeight ?? 0) * CGFloat(maximumNumberOfLines - 1)
let exclusionFrame = CGRect(
x: bounds.width - buttonSize.width - 5, // Add some padding
y: buttonY,
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 = []
}
textView.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionFrame)] // Force an immediate layout update
} else { textView.layoutIfNeeded()
textView.textContainer.maximumNumberOfLines = 0 self.layoutIfNeeded()
textView.textContainer.exclusionPaths = []
// Invalidate intrinsic content size to ensure proper sizing
self.invalidateIntrinsicContentSize()
} }
// Force layout update
textView.layoutIfNeeded()
self.invalidateIntrinsicContentSize()
} }
private func initialize() { private func initialize() {
@@ -182,40 +203,58 @@ final class CollapsingMarkdownView: UIView {
toggleButton.setTitle(title, for: .normal) toggleButton.setTitle(title, for: .normal)
} }
// MARK: - Layout // MARK: - Layout
override func layoutSubviews() { override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
textView.frame = bounds UIView.performWithoutAnimation {
textView.frame = bounds
// Position toggle button
let buttonSize = toggleButton.sizeThatFits(CGSize(width: 1000, height: 1000)) // Check if content needs collapsing when layout changes
if shouldResetLayout || previousSize?.width != bounds.width {
if isCollapsed { checkIfNeedsCollapsing()
let buttonY = (textView.font?.lineHeight ?? 0) * CGFloat(maximumNumberOfLines - 1) shouldResetLayout = false
toggleButton.frame = CGRect( previousSize = bounds.size
x: bounds.width - buttonSize.width, }
y: buttonY,
width: buttonSize.width, // Only position toggle button if it's needed
height: textView.font?.lineHeight ?? 0 if !toggleButton.isHidden {
) let buttonSize = toggleButton.sizeThatFits(CGSize(width: 1000, height: 1000))
} else {
// Position at the end of content when expanded if isCollapsed {
let textHeight = textView.sizeThatFits(bounds.size).height let buttonY = (textView.font?.lineHeight ?? 0) * CGFloat(maximumNumberOfLines - 1)
let lineHeight = textView.font?.lineHeight ?? 0 toggleButton.frame = CGRect(
toggleButton.frame = CGRect( x: bounds.width - buttonSize.width,
x: bounds.width - buttonSize.width, y: buttonY,
y: textHeight - lineHeight, width: buttonSize.width,
width: buttonSize.width, height: textView.font?.lineHeight ?? 0
height: lineHeight )
) } else {
// Position at the end of content when expanded
let textHeight = textView.sizeThatFits(bounds.size).height
let lineHeight = textView.font?.lineHeight ?? 0
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()
} }
} }