mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 09:41:00 +00:00
Estimate status cell heights
This commit is contained in:
parent
b164a6265e
commit
083b52c802
10 changed files with 209 additions and 5 deletions
|
@ -25,4 +25,17 @@ extension CollectionItem {
|
|||
return ConversationListCell.self
|
||||
}
|
||||
}
|
||||
|
||||
func estimatedHeight(width: CGFloat, identification: Identification) -> CGFloat {
|
||||
switch self {
|
||||
case let .status(status, configuration):
|
||||
return StatusView.estimatedHeight(
|
||||
width: width,
|
||||
identification: identification,
|
||||
status: status,
|
||||
configuration: configuration)
|
||||
default:
|
||||
return UITableView.automaticDimension
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,15 @@ import Mastodon
|
|||
import UIKit
|
||||
|
||||
extension String {
|
||||
func height(width: CGFloat, font: UIFont) -> CGFloat {
|
||||
(self as NSString).boundingRect(
|
||||
with: CGSize(width: width, height: .greatestFiniteMagnitude),
|
||||
options: .usesLineFragmentOrigin,
|
||||
attributes: [.font: font],
|
||||
context: nil)
|
||||
.height
|
||||
}
|
||||
|
||||
func countEmphasizedAttributedString(count: Int, highlighted: Bool = false) -> NSAttributedString {
|
||||
let countRange = (self as NSString).range(of: String.localizedStringWithFormat("%ld", count))
|
||||
|
||||
|
|
|
@ -110,7 +110,9 @@ class TableViewController: UITableViewController {
|
|||
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else { return UITableView.automaticDimension }
|
||||
|
||||
return cellHeightCaches[tableView.frame.width]?[item] ?? UITableView.automaticDimension
|
||||
return cellHeightCaches[tableView.frame.width]?[item]
|
||||
?? item.estimatedHeight(width: tableView.readableContentGuide.layoutFrame.width,
|
||||
identification: identification)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import Mastodon
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
|
@ -80,6 +81,20 @@ final class AttachmentsView: UIView {
|
|||
}
|
||||
|
||||
extension AttachmentsView {
|
||||
static func estimatedHeight(width: CGFloat,
|
||||
identification: Identification,
|
||||
status: Status,
|
||||
configuration: CollectionItem.StatusConfiguration) -> CGFloat {
|
||||
let height: CGFloat
|
||||
if status.displayStatus.mediaAttachments.count == 1,
|
||||
let aspectRatio = status.mediaAttachments.first?.aspectRatio {
|
||||
height = width / max(CGFloat(aspectRatio), 16 / 9)
|
||||
} else {
|
||||
height = width / (16 / 9)
|
||||
}
|
||||
|
||||
return height
|
||||
}
|
||||
var shouldAutoplay: Bool {
|
||||
guard !isHidden, let viewModel = viewModel, viewModel.shouldShowAttachments else { return false }
|
||||
|
||||
|
|
|
@ -41,6 +41,12 @@ final class PollOptionButton: UIButton {
|
|||
}
|
||||
}
|
||||
|
||||
extension PollOptionButton {
|
||||
static func estimatedHeight(width: CGFloat, title: String) -> CGFloat {
|
||||
title.height(width: width, font: .preferredFont(forTextStyle: .callout))
|
||||
}
|
||||
}
|
||||
|
||||
private extension PollOptionButton {
|
||||
static let titleEdgeInsets = UIEdgeInsets(top: 0, left: .compactSpacing, bottom: 0, right: .compactSpacing)
|
||||
}
|
||||
|
|
|
@ -74,6 +74,14 @@ final class PollResultView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
extension PollResultView {
|
||||
static func estimatedHeight(width: CGFloat, title: String) -> CGFloat {
|
||||
title.height(width: width, font: .preferredFont(forTextStyle: .callout))
|
||||
+ .compactSpacing
|
||||
+ 4 // progress view height
|
||||
}
|
||||
}
|
||||
|
||||
private extension PollResultView {
|
||||
private static var percentFormatter: NumberFormatter = {
|
||||
let percentageFormatter = NumberFormatter()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import Mastodon
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
|
@ -112,6 +113,34 @@ final class PollView: UIView {
|
|||
}
|
||||
|
||||
extension PollView {
|
||||
static func estimatedHeight(width: CGFloat,
|
||||
identification: Identification,
|
||||
status: Status,
|
||||
configuration: CollectionItem.StatusConfiguration) -> CGFloat {
|
||||
if let poll = status.displayStatus.poll {
|
||||
var height: CGFloat = 0
|
||||
let open = !poll.expired && !poll.voted
|
||||
|
||||
for option in poll.options {
|
||||
height += open ? PollOptionButton.estimatedHeight(width: width, title: option.title)
|
||||
: PollResultView.estimatedHeight(width: width, title: option.title)
|
||||
height += .defaultSpacing
|
||||
}
|
||||
|
||||
if open {
|
||||
height += .minimumButtonDimension + .defaultSpacing
|
||||
}
|
||||
|
||||
height += .minimumButtonDimension / 2
|
||||
|
||||
return height
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension PollView {
|
||||
func initialSetup() {
|
||||
addSubview(stackView)
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -146,11 +175,20 @@ extension PollView {
|
|||
|
||||
bottomStackView.addArrangedSubview(UIView())
|
||||
|
||||
let voteButtonHeightConstraint = voteButton.heightAnchor.constraint(equalToConstant: .minimumButtonDimension)
|
||||
let refreshButtonHeightConstraint = refreshButton.heightAnchor.constraint(
|
||||
equalToConstant: .minimumButtonDimension / 2)
|
||||
|
||||
refreshButtonHeightConstraint.priority = .justBelowMax
|
||||
refreshButtonHeightConstraint.priority = .justBelowMax
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
stackView.topAnchor.constraint(equalTo: topAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
voteButtonHeightConstraint,
|
||||
refreshButtonHeightConstraint
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Kingfisher
|
||||
import Mastodon
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
|
@ -44,6 +45,23 @@ final class CardView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
extension CardView {
|
||||
static func estimatedHeight(width: CGFloat,
|
||||
identification: Identification,
|
||||
status: Status,
|
||||
configuration: CollectionItem.StatusConfiguration) -> CGFloat {
|
||||
if status.displayStatus.card != nil {
|
||||
return round(UIFont.preferredFont(forTextStyle: .headline).lineHeight
|
||||
+ UIFont.preferredFont(forTextStyle: .subheadline).lineHeight
|
||||
+ UIFont.preferredFont(forTextStyle: .footnote).lineHeight
|
||||
+ .defaultSpacing * 2
|
||||
+ .compactSpacing * 2)
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension CardView {
|
||||
// swiftlint:disable:next function_body_length
|
||||
func initialSetup() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Mastodon
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
|
@ -49,7 +50,7 @@ final class StatusBodyView: UIView {
|
|||
attachmentsView.isHidden = viewModel.attachmentViewModels.isEmpty
|
||||
attachmentsView.viewModel = viewModel
|
||||
|
||||
pollView.isHidden = viewModel.pollOptions.isEmpty
|
||||
pollView.isHidden = viewModel.pollOptions.isEmpty || !viewModel.shouldShowContent
|
||||
pollView.viewModel = viewModel
|
||||
|
||||
cardView.viewModel = viewModel.cardViewModel
|
||||
|
@ -69,6 +70,64 @@ final class StatusBodyView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
extension StatusBodyView {
|
||||
static func estimatedHeight(width: CGFloat,
|
||||
identification: Identification,
|
||||
status: Status,
|
||||
configuration: CollectionItem.StatusConfiguration) -> CGFloat {
|
||||
let contentFont = UIFont.preferredFont(forTextStyle: configuration.isContextParent ? .title3 : .callout)
|
||||
var height: CGFloat = 0
|
||||
|
||||
var contentHeight = status.displayStatus.content.attributed.string.height(
|
||||
width: width,
|
||||
font: contentFont)
|
||||
|
||||
if status.displayStatus.card != nil {
|
||||
contentHeight += .compactSpacing
|
||||
contentHeight += CardView.estimatedHeight(
|
||||
width: width,
|
||||
identification: identification,
|
||||
status: status,
|
||||
configuration: configuration)
|
||||
}
|
||||
|
||||
if status.displayStatus.poll != nil {
|
||||
contentHeight += .defaultSpacing
|
||||
contentHeight += PollView.estimatedHeight(
|
||||
width: width,
|
||||
identification: identification,
|
||||
status: status,
|
||||
configuration: configuration)
|
||||
}
|
||||
|
||||
if status.displayStatus.spoilerText.isEmpty {
|
||||
height += contentHeight
|
||||
} else {
|
||||
height += status.displayStatus.spoilerText.height(width: width, font: contentFont)
|
||||
height += .compactSpacing
|
||||
height += NSLocalizedString("status.show-more", comment: "").height(
|
||||
width: width,
|
||||
font: .preferredFont(forTextStyle: .headline))
|
||||
|
||||
if configuration.showContentToggled && !identification.identity.preferences.readingExpandSpoilers {
|
||||
height += .compactSpacing
|
||||
height += contentHeight
|
||||
}
|
||||
}
|
||||
|
||||
if !status.displayStatus.mediaAttachments.isEmpty {
|
||||
height += .compactSpacing
|
||||
height += AttachmentsView.estimatedHeight(
|
||||
width: width,
|
||||
identification: identification,
|
||||
status: status,
|
||||
configuration: configuration)
|
||||
}
|
||||
|
||||
return height
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusBodyView: UITextViewDelegate {
|
||||
func textView(
|
||||
_ textView: UITextView,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
// swiftlint:disable file_length
|
||||
import Kingfisher
|
||||
import Mastodon
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
|
@ -56,6 +57,36 @@ final class StatusView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
extension StatusView {
|
||||
static func estimatedHeight(width: CGFloat,
|
||||
identification: Identification,
|
||||
status: Status,
|
||||
configuration: CollectionItem.StatusConfiguration) -> CGFloat {
|
||||
var height = CGFloat.defaultSpacing * 2
|
||||
let bodyWidth = width - .defaultSpacing - .avatarDimension
|
||||
|
||||
if status.reblog != nil || configuration.isPinned {
|
||||
height += UIFont.preferredFont(forTextStyle: .caption1).lineHeight + .compactSpacing
|
||||
}
|
||||
|
||||
if configuration.isContextParent {
|
||||
height += .avatarDimension + .minimumButtonDimension * 2.5 + .hairline * 2 + .compactSpacing * 4
|
||||
} else {
|
||||
height += UIFont.preferredFont(forTextStyle: .headline).lineHeight
|
||||
+ .compactSpacing + .minimumButtonDimension / 2
|
||||
}
|
||||
|
||||
height += StatusBodyView.estimatedHeight(
|
||||
width: bodyWidth,
|
||||
identification: identification,
|
||||
status: status,
|
||||
configuration: configuration)
|
||||
+ .compactSpacing
|
||||
|
||||
return height
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusView: UIContentView {
|
||||
var configuration: UIContentConfiguration {
|
||||
get { statusConfiguration }
|
||||
|
@ -142,6 +173,7 @@ private extension StatusView {
|
|||
mainStackView.addArrangedSubview(nameAccountContainerStackView)
|
||||
|
||||
mainStackView.addArrangedSubview(bodyView)
|
||||
bodyView.tag = 666
|
||||
|
||||
contextParentTimeLabel.font = .preferredFont(forTextStyle: .footnote)
|
||||
contextParentTimeLabel.adjustsFontForContentSizeCategory = true
|
||||
|
@ -264,7 +296,10 @@ private extension StatusView {
|
|||
avatarButton.leadingAnchor.constraint(equalTo: avatarImageView.leadingAnchor),
|
||||
avatarButton.topAnchor.constraint(equalTo: avatarImageView.topAnchor),
|
||||
avatarButton.bottomAnchor.constraint(equalTo: avatarImageView.bottomAnchor),
|
||||
avatarButton.trailingAnchor.constraint(equalTo: avatarImageView.trailingAnchor)
|
||||
avatarButton.trailingAnchor.constraint(equalTo: avatarImageView.trailingAnchor),
|
||||
contextParentTimeApplicationStackView.heightAnchor.constraint(
|
||||
greaterThanOrEqualToConstant: .minimumButtonDimension / 2),
|
||||
interactionsStackView.heightAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension)
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -374,7 +409,8 @@ private extension StatusView {
|
|||
if isContextParent {
|
||||
button.heightAnchor.constraint(equalToConstant: .minimumButtonDimension).isActive = true
|
||||
} else {
|
||||
button.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true
|
||||
button.heightAnchor.constraint(
|
||||
greaterThanOrEqualToConstant: .minimumButtonDimension / 2).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue