Refactor StatusContentContainer to use an array of subviews
This commit is contained in:
parent
141e8b96a5
commit
158940f8e6
|
@ -61,13 +61,34 @@ class StatusEditCollectionViewCell: UICollectionViewListCell {
|
||||||
$0.addTarget(self, action: #selector(collapseButtonPressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(collapseButtonPressed), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
private let contentContainer = StatusContentContainer<StatusEditContentTextView, StatusEditPollView>(useTopSpacer: false).configure {
|
private lazy var contentContainer = StatusContentContainer(arrangedSubviews: [
|
||||||
$0.contentTextView.defaultFont = TimelineStatusCollectionViewCell.contentFont
|
contentTextView,
|
||||||
$0.contentTextView.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
cardView,
|
||||||
$0.contentTextView.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
attachmentsView,
|
||||||
|
pollView,
|
||||||
|
] as! [any StatusContentView], useTopSpacer: false).configure {
|
||||||
$0.setContentHuggingPriority(.defaultLow, for: .vertical)
|
$0.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let contentTextView = StatusEditContentTextView().configure {
|
||||||
|
$0.adjustsFontForContentSizeCategory = true
|
||||||
|
$0.isScrollEnabled = false
|
||||||
|
$0.backgroundColor = nil
|
||||||
|
$0.isEditable = false
|
||||||
|
$0.isSelectable = false
|
||||||
|
$0.defaultFont = TimelineStatusCollectionViewCell.contentFont
|
||||||
|
$0.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
||||||
|
$0.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
private let cardView = StatusCardView().configure {
|
||||||
|
$0.heightAnchor.constraint(equalToConstant: StatusContentContainer.cardViewHeight).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private let attachmentsView = AttachmentsContainerView()
|
||||||
|
|
||||||
|
private let pollView = StatusEditPollView()
|
||||||
|
|
||||||
weak var delegate: StatusEditCollectionViewCellDelegate?
|
weak var delegate: StatusEditCollectionViewCellDelegate?
|
||||||
private var mastodonController: MastodonController! { delegate?.apiController }
|
private var mastodonController: MastodonController! { delegate?.apiController }
|
||||||
|
|
||||||
|
@ -108,7 +129,7 @@ class StatusEditCollectionViewCell: UICollectionViewListCell {
|
||||||
}
|
}
|
||||||
str += "collapsed"
|
str += "collapsed"
|
||||||
} else {
|
} else {
|
||||||
str += AttributedString(contentContainer.contentTextView.attributedText)
|
str += AttributedString(contentTextView.attributedText)
|
||||||
|
|
||||||
if edit.attachments.count > 0 {
|
if edit.attachments.count > 0 {
|
||||||
let includeDescriptions: Bool
|
let includeDescriptions: Bool
|
||||||
|
@ -170,13 +191,13 @@ class StatusEditCollectionViewCell: UICollectionViewListCell {
|
||||||
|
|
||||||
timestampLabel.text = ConversationMainStatusCollectionViewCell.dateFormatter.string(from: edit.createdAt)
|
timestampLabel.text = ConversationMainStatusCollectionViewCell.dateFormatter.string(from: edit.createdAt)
|
||||||
|
|
||||||
contentContainer.contentTextView.setTextFrom(edit: edit, index: index)
|
contentTextView.setTextFrom(edit: edit, index: index)
|
||||||
contentContainer.contentTextView.navigationDelegate = delegate
|
contentTextView.navigationDelegate = delegate
|
||||||
contentContainer.attachmentsView.delegate = self
|
attachmentsView.delegate = self
|
||||||
contentContainer.attachmentsView.updateUI(attachments: edit.attachments)
|
attachmentsView.updateUI(attachments: edit.attachments)
|
||||||
contentContainer.pollView.isHidden = edit.poll == nil
|
pollView.isHidden = edit.poll == nil
|
||||||
contentContainer.pollView.updateUI(poll: edit.poll, emojis: edit.emojis)
|
pollView.updateUI(poll: edit.poll, emojis: edit.emojis)
|
||||||
contentContainer.cardView.isHidden = true
|
cardView.isHidden = true
|
||||||
|
|
||||||
contentWarningLabel.text = edit.spoilerText
|
contentWarningLabel.text = edit.spoilerText
|
||||||
contentWarningLabel.isHidden = edit.spoilerText.isEmpty
|
contentWarningLabel.isHidden = edit.spoilerText.isEmpty
|
||||||
|
@ -219,9 +240,9 @@ extension StatusEditCollectionViewCell: AttachmentViewDelegate {
|
||||||
guard let delegate else {
|
guard let delegate else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let attachments = contentContainer.attachmentsView.attachments!
|
let attachments = attachmentsView.attachments!
|
||||||
let sourceViews = attachments.map {
|
let sourceViews = attachments.map {
|
||||||
contentContainer.attachmentsView.getAttachmentView(for: $0)
|
attachmentsView.getAttachmentView(for: $0)
|
||||||
}
|
}
|
||||||
let gallery = delegate.gallery(attachments: attachments, sourceViews: sourceViews, startIndex: index)
|
let gallery = delegate.gallery(attachments: attachments, sourceViews: sourceViews, startIndex: index)
|
||||||
return gallery
|
return gallery
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
class StatusEditPollView: UIStackView, StatusContentPollView {
|
class StatusEditPollView: UIStackView, StatusContentView {
|
||||||
|
|
||||||
private var titleLabels: [EmojiLabel] = []
|
private var titleLabels: [EmojiLabel] = []
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
class StatusPollView: UIView, StatusContentPollView {
|
class StatusPollView: UIView, StatusContentView {
|
||||||
|
|
||||||
private static let formatter: DateComponentsFormatter = {
|
private static let formatter: DateComponentsFormatter = {
|
||||||
let f = DateComponentsFormatter()
|
let f = DateComponentsFormatter()
|
||||||
|
|
|
@ -117,18 +117,38 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
||||||
$0.addTarget(self, action: #selector(collapseButtonPressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(collapseButtonPressed), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentContainer = StatusContentContainer<StatusContentTextView, StatusPollView>(useTopSpacer: true).configure {
|
private(set) lazy var contentContainer = StatusContentContainer(arrangedSubviews: [
|
||||||
$0.contentTextView.defaultFont = ConversationMainStatusCollectionViewCell.contentFont
|
contentTextView,
|
||||||
$0.contentTextView.monospaceFont = ConversationMainStatusCollectionViewCell.monospaceFont
|
cardView,
|
||||||
$0.contentTextView.paragraphStyle = ConversationMainStatusCollectionViewCell.contentParagraphStyle
|
attachmentsView,
|
||||||
$0.contentTextView.isSelectable = true
|
pollView,
|
||||||
$0.contentTextView.dataDetectorTypes = [.flightNumber, .address, .shipmentTrackingNumber, .phoneNumber]
|
] as! [any StatusContentView], useTopSpacer: true).configure {
|
||||||
if #available(iOS 16.0, *) {
|
|
||||||
$0.contentTextView.dataDetectorTypes.formUnion([.money, .physicalValue])
|
|
||||||
}
|
|
||||||
$0.setContentHuggingPriority(.defaultLow, for: .vertical)
|
$0.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let contentTextView = StatusContentTextView().configure {
|
||||||
|
$0.adjustsFontForContentSizeCategory = true
|
||||||
|
$0.isScrollEnabled = false
|
||||||
|
$0.backgroundColor = nil
|
||||||
|
$0.isEditable = false
|
||||||
|
$0.isSelectable = true
|
||||||
|
$0.defaultFont = ConversationMainStatusCollectionViewCell.contentFont
|
||||||
|
$0.monospaceFont = ConversationMainStatusCollectionViewCell.monospaceFont
|
||||||
|
$0.paragraphStyle = ConversationMainStatusCollectionViewCell.contentParagraphStyle
|
||||||
|
$0.dataDetectorTypes = [.flightNumber, .address, .shipmentTrackingNumber, .phoneNumber]
|
||||||
|
if #available(iOS 16.0, *) {
|
||||||
|
$0.dataDetectorTypes.formUnion([.money, .physicalValue])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cardView = StatusCardView().configure {
|
||||||
|
$0.heightAnchor.constraint(equalToConstant: StatusContentContainer.cardViewHeight).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let attachmentsView = AttachmentsContainerView()
|
||||||
|
|
||||||
|
let pollView = StatusPollView()
|
||||||
|
|
||||||
private lazy var favoritesCountButton = UIButton().configure {
|
private lazy var favoritesCountButton = UIButton().configure {
|
||||||
$0.titleLabel!.adjustsFontForContentSizeCategory = true
|
$0.titleLabel!.adjustsFontForContentSizeCategory = true
|
||||||
$0.addTarget(self, action: #selector(favoritesCountPressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(favoritesCountPressed), for: .touchUpInside)
|
||||||
|
@ -318,9 +338,9 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
||||||
accountDetailAccessibilityElement,
|
accountDetailAccessibilityElement,
|
||||||
contentWarningLabel,
|
contentWarningLabel,
|
||||||
collapseButton,
|
collapseButton,
|
||||||
contentContainer.contentTextView,
|
contentTextView,
|
||||||
contentContainer.attachmentsView,
|
attachmentsView,
|
||||||
contentContainer.pollView,
|
pollView,
|
||||||
favoritesCountButton,
|
favoritesCountButton,
|
||||||
reblogsCountButton,
|
reblogsCountButton,
|
||||||
timestampAndClientLabel,
|
timestampAndClientLabel,
|
||||||
|
|
|
@ -24,7 +24,11 @@ protocol StatusCollectionViewCell: UICollectionViewCell, AttachmentViewDelegate
|
||||||
var usernameLabel: UILabel { get }
|
var usernameLabel: UILabel { get }
|
||||||
var contentWarningLabel: EmojiLabel { get }
|
var contentWarningLabel: EmojiLabel { get }
|
||||||
var collapseButton: StatusCollapseButton { get }
|
var collapseButton: StatusCollapseButton { get }
|
||||||
var contentContainer: StatusContentContainer<StatusContentTextView, StatusPollView> { get }
|
var contentContainer: StatusContentContainer { get }
|
||||||
|
var contentTextView: StatusContentTextView { get }
|
||||||
|
var pollView: StatusPollView { get }
|
||||||
|
var cardView: StatusCardView { get }
|
||||||
|
var attachmentsView: AttachmentsContainerView { get }
|
||||||
var replyButton: UIButton { get }
|
var replyButton: UIButton { get }
|
||||||
var favoriteButton: ToggleableButton { get }
|
var favoriteButton: ToggleableButton { get }
|
||||||
var reblogButton: ToggleableButton { get }
|
var reblogButton: ToggleableButton { get }
|
||||||
|
@ -90,20 +94,20 @@ extension StatusCollectionViewCell {
|
||||||
|
|
||||||
updateAccountUI(account: status.account)
|
updateAccountUI(account: status.account)
|
||||||
|
|
||||||
contentContainer.contentTextView.setTextFrom(status: status, precomputed: precomputedContent)
|
contentTextView.setTextFrom(status: status, precomputed: precomputedContent)
|
||||||
contentContainer.contentTextView.navigationDelegate = delegate
|
contentTextView.navigationDelegate = delegate
|
||||||
self.updateAttachmentsUI(status: status)
|
self.updateAttachmentsUI(status: status)
|
||||||
contentContainer.pollView.isHidden = status.poll == nil
|
pollView.isHidden = status.poll == nil
|
||||||
contentContainer.pollView.mastodonController = mastodonController
|
pollView.mastodonController = mastodonController
|
||||||
contentContainer.pollView.delegate = delegate
|
pollView.delegate = delegate
|
||||||
contentContainer.pollView.updateUI(status: status, poll: status.poll)
|
pollView.updateUI(status: status, poll: status.poll)
|
||||||
if Preferences.shared.showLinkPreviews {
|
if Preferences.shared.showLinkPreviews {
|
||||||
contentContainer.cardView.updateUI(status: status)
|
cardView.updateUI(status: status)
|
||||||
contentContainer.cardView.isHidden = status.card == nil
|
cardView.isHidden = status.card == nil
|
||||||
contentContainer.cardView.navigationDelegate = delegate
|
cardView.navigationDelegate = delegate
|
||||||
contentContainer.cardView.actionProvider = delegate
|
cardView.actionProvider = delegate
|
||||||
} else {
|
} else {
|
||||||
contentContainer.cardView.isHidden = true
|
cardView.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUIForPreferences(status: status)
|
updateUIForPreferences(status: status)
|
||||||
|
@ -168,8 +172,8 @@ extension StatusCollectionViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateAttachmentsUI(status: StatusMO) {
|
func updateAttachmentsUI(status: StatusMO) {
|
||||||
contentContainer.attachmentsView.delegate = self
|
attachmentsView.delegate = self
|
||||||
contentContainer.attachmentsView.updateUI(attachments: status.attachments)
|
attachmentsView.updateUI(attachments: status.attachments)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateAccountUI(account: AccountMO) {
|
func updateAccountUI(account: AccountMO) {
|
||||||
|
@ -182,20 +186,20 @@ extension StatusCollectionViewCell {
|
||||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * Self.avatarImageViewSize
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * Self.avatarImageViewSize
|
||||||
|
|
||||||
let newCardHidden = !Preferences.shared.showLinkPreviews || status.card == nil
|
let newCardHidden = !Preferences.shared.showLinkPreviews || status.card == nil
|
||||||
if contentContainer.cardView.isHidden != newCardHidden {
|
if cardView.isHidden != newCardHidden {
|
||||||
delegate?.statusCellNeedsReconfigure(self, animated: false, completion: nil)
|
delegate?.statusCellNeedsReconfigure(self, animated: false, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch Preferences.shared.attachmentBlurMode {
|
switch Preferences.shared.attachmentBlurMode {
|
||||||
case .never:
|
case .never:
|
||||||
contentContainer.attachmentsView.contentHidden = false
|
attachmentsView.contentHidden = false
|
||||||
case .always:
|
case .always:
|
||||||
contentContainer.attachmentsView.contentHidden = true
|
attachmentsView.contentHidden = true
|
||||||
default:
|
default:
|
||||||
if status.sensitive {
|
if status.sensitive {
|
||||||
contentContainer.attachmentsView.contentHidden = status.spoilerText.isEmpty || Preferences.shared.blurMediaBehindContentWarning
|
attachmentsView.contentHidden = status.spoilerText.isEmpty || Preferences.shared.blurMediaBehindContentWarning
|
||||||
} else {
|
} else {
|
||||||
contentContainer.attachmentsView.contentHidden = false
|
attachmentsView.contentHidden = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,8 +215,8 @@ extension StatusCollectionViewCell {
|
||||||
// only called when isGrayscale does not match the pref
|
// only called when isGrayscale does not match the pref
|
||||||
func updateGrayscaleableUI(status: StatusMO) {
|
func updateGrayscaleableUI(status: StatusMO) {
|
||||||
isGrayscale = Preferences.shared.grayscaleImages
|
isGrayscale = Preferences.shared.grayscaleImages
|
||||||
if contentContainer.contentTextView.hasEmojis {
|
if contentTextView.hasEmojis {
|
||||||
contentContainer.contentTextView.setEmojis(status.emojis, identifier: status.id)
|
contentTextView.setEmojis(status.emojis, identifier: status.id)
|
||||||
}
|
}
|
||||||
displayNameLabel.updateForAccountDisplayName(account: status.account)
|
displayNameLabel.updateForAccountDisplayName(account: status.account)
|
||||||
}
|
}
|
||||||
|
@ -235,10 +239,10 @@ extension StatusCollectionViewCell {
|
||||||
// do not include reply action here, because the cell already contains a button for it
|
// do not include reply action here, because the cell already contains a button for it
|
||||||
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: delegate?.actionsForStatus(status, source: .view(moreButton), includeStatusButtonActions: false) ?? [])
|
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: delegate?.actionsForStatus(status, source: .view(moreButton), includeStatusButtonActions: false) ?? [])
|
||||||
|
|
||||||
contentContainer.pollView.isHidden = status.poll == nil
|
pollView.isHidden = status.poll == nil
|
||||||
contentContainer.pollView.mastodonController = mastodonController
|
pollView.mastodonController = mastodonController
|
||||||
contentContainer.pollView.delegate = delegate
|
pollView.delegate = delegate
|
||||||
contentContainer.pollView.updateUI(status: status, poll: status.poll)
|
pollView.updateUI(status: status, poll: status.poll)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setShowThreadLinks(prev: Bool, next: Bool) {
|
func setShowThreadLinks(prev: Bool, next: Bool) {
|
||||||
|
@ -327,7 +331,7 @@ extension StatusCollectionViewCell {
|
||||||
func attachmentViewGallery(startingAt index: Int) -> GalleryViewController? {
|
func attachmentViewGallery(startingAt index: Int) -> GalleryViewController? {
|
||||||
guard let delegate = delegate,
|
guard let delegate = delegate,
|
||||||
let status = mastodonController.persistentContainer.status(for: statusID) else { return nil }
|
let status = mastodonController.persistentContainer.status(for: statusID) else { return nil }
|
||||||
let sourceViews = status.attachments.map(contentContainer.attachmentsView.getAttachmentView(for:))
|
let sourceViews = status.attachments.map(attachmentsView.getAttachmentView(for:))
|
||||||
let gallery = delegate.gallery(attachments: status.attachments, sourceViews: sourceViews, startIndex: index)
|
let gallery = delegate.gallery(attachments: status.attachments, sourceViews: sourceViews, startIndex: index)
|
||||||
// TODO: PiP
|
// TODO: PiP
|
||||||
// gallery.avPlayerViewControllerDelegate = self
|
// gallery.avPlayerViewControllerDelegate = self
|
||||||
|
|
|
@ -8,45 +8,11 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
protocol StatusContentPollView: UIView {
|
class StatusContentContainer: UIView {
|
||||||
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat
|
// TODO: this is a weird place for this
|
||||||
}
|
static var cardViewHeight: CGFloat { 90 }
|
||||||
|
|
||||||
class StatusContentContainer<ContentView: ContentTextView, PollView: StatusContentPollView>: UIView {
|
private let arrangedSubviews: [any StatusContentView]
|
||||||
|
|
||||||
private let useTopSpacer: Bool
|
|
||||||
private lazy var topSpacer = UIView().configure {
|
|
||||||
$0.backgroundColor = .clear
|
|
||||||
// other 4pt is provided by this view's own spacing
|
|
||||||
$0.heightAnchor.constraint(equalToConstant: 4).isActive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let contentTextView = ContentView().configure {
|
|
||||||
$0.adjustsFontForContentSizeCategory = true
|
|
||||||
$0.isScrollEnabled = false
|
|
||||||
$0.backgroundColor = nil
|
|
||||||
$0.isEditable = false
|
|
||||||
$0.isSelectable = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private static var cardViewHeight: CGFloat { 90 }
|
|
||||||
let cardView = StatusCardView().configure {
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
$0.heightAnchor.constraint(equalToConstant: StatusContentContainer.cardViewHeight),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
let attachmentsView = AttachmentsContainerView()
|
|
||||||
|
|
||||||
let pollView = PollView()
|
|
||||||
|
|
||||||
private var arrangedSubviews: [UIView] {
|
|
||||||
if useTopSpacer {
|
|
||||||
return [topSpacer, contentTextView, cardView, attachmentsView, pollView]
|
|
||||||
} else {
|
|
||||||
return [contentTextView, cardView, attachmentsView, pollView]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isHiddenObservations: [NSKeyValueObservation] = []
|
private var isHiddenObservations: [NSKeyValueObservation] = []
|
||||||
|
|
||||||
|
@ -61,8 +27,12 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: StatusConte
|
||||||
subviews.filter { !$0.isHidden }.map(\.bounds.height).reduce(0, +)
|
subviews.filter { !$0.isHidden }.map(\.bounds.height).reduce(0, +)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(useTopSpacer: Bool) {
|
init(arrangedSubviews: [any StatusContentView], useTopSpacer: Bool) {
|
||||||
self.useTopSpacer = useTopSpacer
|
var arrangedSubviews = arrangedSubviews
|
||||||
|
if useTopSpacer {
|
||||||
|
arrangedSubviews.insert(TopSpacerView(), at: 0)
|
||||||
|
}
|
||||||
|
self.arrangedSubviews = arrangedSubviews
|
||||||
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
@ -83,7 +53,7 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: StatusConte
|
||||||
setNeedsUpdateConstraints()
|
setNeedsUpdateConstraints()
|
||||||
|
|
||||||
isHiddenObservations = arrangedSubviews.map {
|
isHiddenObservations = arrangedSubviews.map {
|
||||||
$0.observe(\.isHidden) { [unowned self] _, _ in
|
$0.observeIsHidden { [unowned self] in
|
||||||
self.setNeedsUpdateConstraints()
|
self.setNeedsUpdateConstraints()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,18 +117,60 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: StatusConte
|
||||||
// just roughly inline with the content height
|
// just roughly inline with the content height
|
||||||
func estimateVisibleSubviewHeight(effectiveWidth: CGFloat) -> CGFloat {
|
func estimateVisibleSubviewHeight(effectiveWidth: CGFloat) -> CGFloat {
|
||||||
var height: CGFloat = 0
|
var height: CGFloat = 0
|
||||||
height += contentTextView.sizeThatFits(CGSize(width: effectiveWidth, height: UIView.layoutFittingCompressedSize.height)).height
|
for view in arrangedSubviews where !view.isHidden {
|
||||||
if !cardView.isHidden {
|
height += view.estimateHeight(effectiveWidth: effectiveWidth)
|
||||||
height += StatusContentContainer.cardViewHeight
|
|
||||||
}
|
|
||||||
if !attachmentsView.isHidden {
|
|
||||||
height += effectiveWidth / attachmentsView.aspectRatio
|
|
||||||
}
|
|
||||||
if !pollView.isHidden {
|
|
||||||
let pollHeight = pollView.estimateHeight(effectiveWidth: effectiveWidth)
|
|
||||||
height += pollHeight
|
|
||||||
}
|
}
|
||||||
return height
|
return height
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension StatusContentContainer {
|
||||||
|
private class TopSpacerView: UIView, StatusContentView {
|
||||||
|
init() {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
backgroundColor = .clear
|
||||||
|
// other 4pt is provided by this view's own spacing
|
||||||
|
heightAnchor.constraint(equalToConstant: 4).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension UIView {
|
||||||
|
func observeIsHidden(_ f: @escaping () -> Void) -> NSKeyValueObservation {
|
||||||
|
self.observe(\.isHidden) { _, _ in
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol StatusContentView: UIView {
|
||||||
|
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ContentTextView: StatusContentView {
|
||||||
|
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
|
||||||
|
sizeThatFits(CGSize(width: effectiveWidth, height: UIView.layoutFittingCompressedSize.height)).height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusCardView: StatusContentView {
|
||||||
|
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
|
||||||
|
StatusContentContainer.cardViewHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AttachmentsContainerView: StatusContentView {
|
||||||
|
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
|
||||||
|
effectiveWidth / aspectRatio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -186,25 +186,34 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
$0.setContentCompressionResistancePriority(.required, for: .vertical)
|
$0.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentContainer = StatusContentContainer<StatusContentTextView, StatusPollView>(useTopSpacer: false).configure {
|
private(set) lazy var contentContainer = StatusContentContainer(arrangedSubviews: [
|
||||||
$0.contentTextView.defaultFont = TimelineStatusCollectionViewCell.contentFont
|
contentTextView,
|
||||||
$0.contentTextView.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
cardView,
|
||||||
$0.contentTextView.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
attachmentsView,
|
||||||
|
pollView,
|
||||||
|
] as! [any StatusContentView], useTopSpacer: false).configure {
|
||||||
$0.setContentHuggingPriority(.defaultLow, for: .vertical)
|
$0.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||||
}
|
}
|
||||||
private var contentTextView: StatusContentTextView {
|
|
||||||
contentContainer.contentTextView
|
let contentTextView = StatusContentTextView().configure {
|
||||||
|
$0.adjustsFontForContentSizeCategory = true
|
||||||
|
$0.isScrollEnabled = false
|
||||||
|
$0.backgroundColor = nil
|
||||||
|
$0.isEditable = false
|
||||||
|
$0.isSelectable = false
|
||||||
|
$0.defaultFont = TimelineStatusCollectionViewCell.contentFont
|
||||||
|
$0.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
||||||
|
$0.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||||
}
|
}
|
||||||
private var cardView: StatusCardView {
|
|
||||||
contentContainer.cardView
|
let cardView = StatusCardView().configure {
|
||||||
}
|
$0.heightAnchor.constraint(equalToConstant: StatusContentContainer.cardViewHeight).isActive = true
|
||||||
private var attachmentsView: AttachmentsContainerView {
|
|
||||||
contentContainer.attachmentsView
|
|
||||||
}
|
|
||||||
private var pollView: StatusPollView {
|
|
||||||
contentContainer.pollView
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let attachmentsView = AttachmentsContainerView()
|
||||||
|
|
||||||
|
let pollView = StatusPollView()
|
||||||
|
|
||||||
private var placeholderReplyButtonLeadingConstraint: NSLayoutConstraint!
|
private var placeholderReplyButtonLeadingConstraint: NSLayoutConstraint!
|
||||||
private lazy var actionsContainer = UIView().configure {
|
private lazy var actionsContainer = UIView().configure {
|
||||||
replyButton.translatesAutoresizingMaskIntoConstraints = false
|
replyButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
Loading…
Reference in New Issue