Estimate height when resolving status collapse state
This commit is contained in:
parent
735659dee6
commit
8c27a9368f
|
@ -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
|
||||
|
|
|
@ -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!)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue