Estimate height when resolving status collapse state

This commit is contained in:
Shadowfacts 2023-05-13 15:00:03 -04:00
parent 735659dee6
commit 8c27a9368f
11 changed files with 93 additions and 29 deletions

View File

@ -13,6 +13,7 @@ protocol Configurable {
func configure(_ closure: (T) -> Void) -> T
}
extension Configurable where Self: UIView {
@inline(__always)
func configure(_ closure: (Self) -> Void) -> Self {
closure(self)
return self

View File

@ -117,8 +117,8 @@ class StatusEditCollectionViewCell: UICollectionViewListCell {
}
_ = state.resolveFor(status: edit, height: {
layoutIfNeeded()
return contentContainer.visibleSubviewHeight
let width = self.bounds.width - 2*16
return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: width)
})
collapseButton.isHidden = !state.collapsible!
contentContainer.setCollapsed(state.collapsed!)

View File

@ -9,7 +9,9 @@
import UIKit
import Pachyderm
class StatusEditPollView: UIStackView {
class StatusEditPollView: UIStackView, StatusContentPollView {
private var titleLabels: [EmojiLabel] = []
init() {
super.init(frame: .zero)
@ -25,6 +27,7 @@ class StatusEditPollView: UIStackView {
func updateUI(poll: StatusEdit.Poll?, emojis: [Emoji]) {
arrangedSubviews.forEach { $0.removeFromSuperview() }
titleLabels = []
for option in poll?.options ?? [] {
// the edit poll doesn't actually include the multiple value
@ -33,6 +36,7 @@ class StatusEditPollView: UIStackView {
let label = EmojiLabel()
label.text = option.title
label.setEmojis(emojis, identifier: Optional<String>.none)
titleLabels.append(label)
let stack = UIStackView(arrangedSubviews: [
icon,
label,
@ -44,4 +48,14 @@ class StatusEditPollView: UIStackView {
}
}
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
var height: CGFloat = 0
height += CGFloat(arrangedSubviews.count - 1) * 4
let labelWidth = effectiveWidth /* checkbox size: */ - 20 /* spacing: */ - 8
for titleLabel in titleLabels {
height += titleLabel.sizeThatFits(CGSize(width: labelWidth, height: UIView.layoutFittingCompressedSize.height)).height
}
return height
}
}

View File

@ -22,6 +22,15 @@ class AttachmentsContainerView: UIView {
let attachmentStacks: NSHashTable<UIStackView> = .weakObjects()
var moreView: UIView?
private var aspectRatioConstraint: NSLayoutConstraint?
private(set) var aspectRatio: CGFloat = 16/9 {
didSet {
if aspectRatio != aspectRatioConstraint?.multiplier {
aspectRatioConstraint?.isActive = false
aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: aspectRatio)
aspectRatioConstraint!.isActive = true
}
}
}
var blurView: UIVisualEffectView?
var hideButtonView: UIVisualEffectView?
@ -93,7 +102,8 @@ class AttachmentsContainerView: UIView {
fillView(attachmentView)
sendSubviewToBack(attachmentView)
accessibilityElements.append(attachmentView)
if let attachmentAspectRatio = attachmentView.attachmentAspectRatio {
if Preferences.shared.showUncroppedMediaInline,
let attachmentAspectRatio = attachmentView.attachmentAspectRatio {
aspectRatio = attachmentAspectRatio
}
case 2:
@ -266,18 +276,7 @@ class AttachmentsContainerView: UIView {
accessibilityElements.append(moreView)
}
if Preferences.shared.showUncroppedMediaInline {
if aspectRatioConstraint?.multiplier != aspectRatio {
aspectRatioConstraint?.isActive = false
aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: aspectRatio)
aspectRatioConstraint!.isActive = true
}
} else {
if aspectRatioConstraint == nil {
aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: 16/9)
aspectRatioConstraint!.isActive = true
}
}
self.aspectRatio = aspectRatio
} else {
self.isHidden = true
}

View File

@ -11,18 +11,18 @@ import Pachyderm
class PollOptionView: UIView {
private let unselectedBackgroundColor = UIColor(white: 0.5, alpha: 0.25)
private static let unselectedBackgroundColor = UIColor(white: 0.5, alpha: 0.25)
private(set) var label: EmojiLabel!
private(set) var checkbox: PollOptionCheckboxView?
init(poll: Poll, option: Poll.Option, mastodonController: MastodonController) {
super.init(frame: .zero)
let minHeight: CGFloat = 35
layer.cornerRadius = 0.1 * minHeight
layer.cornerCurve = .continuous
backgroundColor = unselectedBackgroundColor
backgroundColor = PollOptionView.unselectedBackgroundColor
let showCheckbox = poll.ownVotes?.isEmpty == false || !poll.effectiveExpired
if showCheckbox {
@ -31,7 +31,7 @@ class PollOptionView: UIView {
addSubview(checkbox!)
}
let label = EmojiLabel()
label = EmojiLabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = .preferredFont(forTextStyle: .callout)
@ -91,7 +91,6 @@ class PollOptionView: UIView {
NSLayoutConstraint.activate([
minHeightConstraint,
label.topAnchor.constraint(equalTo: topAnchor, constant: 4),
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
label.trailingAnchor.constraint(equalTo: percentLabel.leadingAnchor, constant: -4),

View File

@ -77,6 +77,16 @@ class PollOptionsView: UIControl {
accessibilityElements = options
}
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
var height: CGFloat = 0
height += CGFloat(options.count - 1) * stack.spacing
for option in options {
// this isn't the actual width, but it's close enough for the estimate
height += option.label.sizeThatFits(CGSize(width: effectiveWidth, height: UIView.layoutFittingCompressedSize.height)).height
}
return height
}
private func selectOption(_ option: PollOptionView) {
if poll.multiple {
option.checkbox?.isChecked.toggle()

View File

@ -9,7 +9,7 @@
import UIKit
import Pachyderm
class StatusPollView: UIView {
class StatusPollView: UIView, StatusContentPollView {
private static let formatter: DateComponentsFormatter = {
let f = DateComponentsFormatter()
@ -140,6 +140,11 @@ class StatusPollView: UIView {
voteButton.isEnabled = false
}
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
guard let poll else { return 0 }
return optionsView.estimateHeight(effectiveWidth: effectiveWidth) + infoLabel.sizeThatFits(UIView.layoutFittingExpandedSize).height
}
private func checkedOptionsChanged() {
voteButton.isEnabled = optionsView.checkedOptionIndices.count > 0
}

View File

@ -398,6 +398,11 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
reblogsCountButton.configuration = reblogsConfig
}
func estimateContentHeight() -> CGFloat {
let width = bounds.width - 2*16
return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: width)
}
func updateUIForPreferences(status: StatusMO) {
baseUpdateUIForPreferences(status: status)
}

View File

@ -46,6 +46,7 @@ protocol StatusCollectionViewCell: UICollectionViewCell, AttachmentViewDelegate
func updateUIForPreferences(status: StatusMO)
func updateStatusState(status: StatusMO)
func estimateContentHeight() -> CGFloat
}
// MARK: UI Configuration
@ -117,11 +118,13 @@ extension StatusCollectionViewCell {
replyButton.isEnabled = mastodonController.loggedIn
favoriteButton.isEnabled = mastodonController.loggedIn
let didResolve = statusState.resolveFor(status: status) {
// layout so that we can take the content height into consideration when deciding whether to collapse
layoutIfNeeded()
return contentContainer.visibleSubviewHeight
}
let didResolve = statusState.resolveFor(status: status, height: self.estimateContentHeight)
// let didResolve = statusState.resolveFor(status: status) {
//// // layout so that we can take the content height into consideration when deciding whether to collapse
//// layoutIfNeeded()
//// return contentContainer.visibleSubviewHeight
// return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: )
// }
if didResolve {
if statusState.collapsible! && showStatusAutomatically {
statusState.collapsed = false

View File

@ -8,7 +8,11 @@
import UIKit
class StatusContentContainer<ContentView: ContentTextView, PollView: UIView>: UIView {
protocol StatusContentPollView: UIView {
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat
}
class StatusContentContainer<ContentView: ContentTextView, PollView: StatusContentPollView>: UIView {
private var useTopSpacer = false
private let topSpacer = UIView().configure {
@ -25,9 +29,10 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: UIView>: UI
$0.isSelectable = false
}
private static var cardViewHeight: CGFloat { 90 }
let cardView = StatusCardView().configure {
NSLayoutConstraint.activate([
$0.heightAnchor.constraint(equalToConstant: 90),
$0.heightAnchor.constraint(equalToConstant: StatusContentContainer.cardViewHeight),
])
}
@ -132,4 +137,22 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: UIView>: UI
zeroHeightConstraint.isActive = collapsed
}
// used only for collapsing automatically based on height, doesn't need to be accurate
// just roughly inline with the content height
func estimateVisibleSubviewHeight(effectiveWidth: CGFloat) -> CGFloat {
var height: CGFloat = 0
height += contentTextView.sizeThatFits(CGSize(width: effectiveWidth, height: UIView.layoutFittingCompressedSize.height)).height
if !cardView.isHidden {
height += StatusContentContainer.cardViewHeight
}
if !attachmentsView.isHidden {
height += effectiveWidth / attachmentsView.aspectRatio
}
if !pollView.isHidden {
let pollHeight = pollView.estimateHeight(effectiveWidth: effectiveWidth)
height += pollHeight
}
return height
}
}

View File

@ -629,6 +629,11 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
baseUpdateStatusState(status: status)
}
func estimateContentHeight() -> CGFloat {
let width = bounds.width /* leading spacing: */ - 16 /* avatar: */ - 50 /* spacing: 8 */ - 8 /* trailing spacing: */ - 16
return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: width)
}
private func updateTimestamp() {
guard let mastodonController,
let status = mastodonController.persistentContainer.status(for: statusID) else {