Skip to content

Commit

Permalink
Fix layout for compact status cards and missing images
Browse files Browse the repository at this point in the history
Key points:
- hide image view if there is no image to fetch or if none arrives.
- authorStackView needed to always be full width across the bottom, and needed alignment .fill to avoid having its contents squished to zero height sometimes
- new mainContentStackView seems to need .center alignment when the axis is horizontal in order to size correctly.

Fixes IOS-317
  • Loading branch information
whattherestimefor committed Dec 9, 2024
1 parent a255820 commit 3e2c637
Showing 1 changed file with 67 additions and 33 deletions.
100 changes: 67 additions & 33 deletions MastodonSDK/Sources/MastodonUI/View/Content/StatusCardControl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ public final class StatusCardControl: UIControl {
private var disposeBag = Set<AnyCancellable>()

private let containerStackView = UIStackView()
private let headerContentStackView = UIStackView()
private let mainContentStackView = UIStackView()
private let labelStackView = UIStackView()

private let highlightView = UIView()
private let dividerView = UIView()
private let imageDividerView = UIView()
private let imageView = UIImageView()

private let publisherDateStackView: UIStackView
Expand Down Expand Up @@ -69,7 +69,7 @@ public final class StatusCardControl: UIControl {

private var layout: Layout?
private var layoutConstraints: [NSLayoutConstraint] = []
private var dividerConstraint: NSLayoutConstraint?
private var imageDividerConstraint: NSLayoutConstraint?
private var authorDividerConstraint: NSLayoutConstraint?

private var author: Mastodon.Entity.Account?
Expand All @@ -88,6 +88,10 @@ public final class StatusCardControl: UIControl {
}

public override init(frame: CGRect) {

// containerStackView - always vertical, holds mainContentStackView and authorStackView
// mainContentStackView - vertical in .large, horizontal in .compact, holds imageView and labelStackView
// authorStackView - always vertical, has an avatar button if the author has an account, otherwise shows author information as text

let mastodonLogo = Asset.Scene.Sidebar.logo.image.withRenderingMode(.alwaysTemplate)
mastodonLogoImageView = UIImageView(image: mastodonLogo)
Expand All @@ -102,11 +106,13 @@ public final class StatusCardControl: UIControl {

authorLabel = UILabel()
authorLabel.numberOfLines = 1
authorLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
authorLabel.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
authorLabel.textColor = .secondaryLabel

publisherLabel.numberOfLines = 1
publisherLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
publisherLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
publisherLabel.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .regular))
publisherLabel.textColor = .secondaryLabel

Expand All @@ -128,7 +134,7 @@ public final class StatusCardControl: UIControl {
authorAccountButton = StatusCardAuthorControl()

authorStackView = UIStackView(arrangedSubviews: [mastodonLogoImageView, byLabel, authorLabel, authorAccountButton, UIView()])
authorStackView.alignment = .center
authorStackView.alignment = .fill//.center
authorStackView.layoutMargins = .init(top: 10, left: 16, bottom: 10, right: 16)
authorStackView.isLayoutMarginsRelativeArrangement = true
authorStackView.spacing = 8
Expand Down Expand Up @@ -173,15 +179,16 @@ public final class StatusCardControl: UIControl {
labelStackView.axis = .vertical
labelStackView.spacing = 2

headerContentStackView.addArrangedSubview(imageView)
headerContentStackView.addArrangedSubview(dividerView)
headerContentStackView.addArrangedSubview(labelStackView)
headerContentStackView.isUserInteractionEnabled = true
headerContentStackView.axis = .vertical
headerContentStackView.spacing = 2
headerContentStackView.setCustomSpacing(0, after: imageView)

containerStackView.addArrangedSubview(headerContentStackView)
mainContentStackView.addArrangedSubview(imageView)
mainContentStackView.setCustomSpacing(0, after: imageView)
mainContentStackView.addArrangedSubview(imageDividerView)
mainContentStackView.addArrangedSubview(labelStackView)
mainContentStackView.isUserInteractionEnabled = true
mainContentStackView.axis = .vertical
mainContentStackView.spacing = 2

containerStackView.axis = .vertical
containerStackView.addArrangedSubview(mainContentStackView)
containerStackView.addArrangedSubview(authorDivider)
containerStackView.addArrangedSubview(authorStackView)
containerStackView.distribution = .fill
Expand All @@ -191,9 +198,14 @@ public final class StatusCardControl: UIControl {
addSubview(showEmbedButton)

containerStackView.translatesAutoresizingMaskIntoConstraints = false
mainContentStackView.translatesAutoresizingMaskIntoConstraints = false
labelStackView.translatesAutoresizingMaskIntoConstraints = false
authorLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
highlightView.translatesAutoresizingMaskIntoConstraints = false
showEmbedButton.translatesAutoresizingMaskIntoConstraints = false
dividerView.translatesAutoresizingMaskIntoConstraints = false
imageDividerView.translatesAutoresizingMaskIntoConstraints = false

containerStackView.pinToParent()
highlightView.pinToParent()
Expand Down Expand Up @@ -247,7 +259,7 @@ public final class StatusCardControl: UIControl {

authorAccountButton.addTarget(self, action: #selector(StatusCardControl.profileTapped(_:)), for: .touchUpInside)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(StatusCardControl.contentTapped(_:)))
headerContentStackView.addGestureRecognizer(tapGestureRecognizer)
mainContentStackView.addGestureRecognizer(tapGestureRecognizer)
} else {
if let author = card.authors?.first, let authorName = author.name, authorName.isEmpty == false {
authorLabel.text = L10n.Common.Controls.Status.Card.byAuthor(authorName)
Expand All @@ -269,7 +281,7 @@ public final class StatusCardControl: UIControl {

titleLabel.text = title
descriptionLabel.text = card.description
imageView.contentMode = .center
imageView.contentMode = .scaleAspectFill

imageView.sd_setImage(
with: {
Expand All @@ -280,6 +292,9 @@ public final class StatusCardControl: UIControl {
) { [weak self] image, _, _, _ in
if image != nil {
self?.imageView.contentMode = .scaleAspectFill
} else {
self?.imageView.isHidden = true
self?.imageDividerView.isHidden = true
}

self?.containerStackView.setNeedsLayout()
Expand All @@ -303,7 +318,7 @@ public final class StatusCardControl: UIControl {
super.didMoveToWindow()

if let window {
dividerConstraint?.constant = window.screen.pixelSize
imageDividerConstraint?.constant = window.screen.pixelSize
authorDividerConstraint?.constant = window.screen.pixelSize
}
}
Expand All @@ -313,14 +328,20 @@ public final class StatusCardControl: UIControl {
self.layout = layout

NSLayoutConstraint.deactivate(layoutConstraints)
dividerConstraint?.deactivate()
imageDividerConstraint?.deactivate()
authorDividerConstraint?.deactivate()

let pixelSize = (window?.screen.pixelSize ?? 1)
authorDividerConstraint = authorDivider.heightAnchor.constraint(equalToConstant: pixelSize).activate()

switch layout {
case .large(let aspectRatio):
imageView.isHidden = false
imageDividerView.isHidden = false
containerStackView.alignment = .fill
containerStackView.axis = .vertical
mainContentStackView.axis = .vertical
mainContentStackView.alignment = .fill
layoutConstraints = [
imageView.widthAnchor.constraint(
equalTo: imageView.heightAnchor,
Expand All @@ -332,22 +353,28 @@ public final class StatusCardControl: UIControl {
// set a reasonable max height for very tall images
imageView.heightAnchor
.constraint(lessThanOrEqualToConstant: 400),
authorDivider.widthAnchor.constraint(equalTo: containerStackView.widthAnchor),
imageDividerView.widthAnchor.constraint(equalTo: mainContentStackView.widthAnchor)
]
dividerConstraint = dividerView.heightAnchor.constraint(equalToConstant: pixelSize).activate()
authorDividerConstraint = authorDivider.heightAnchor.constraint(equalToConstant: pixelSize).priority(.defaultLow - 1).activate()
case .compact:
containerStackView.alignment = .center
containerStackView.axis = .horizontal
imageDividerConstraint = imageDividerView.heightAnchor.constraint(equalToConstant: pixelSize).priority(.defaultLow - 1).activate()
case .compact, .noPreviewImage:
mainContentStackView.axis = .horizontal
mainContentStackView.alignment = .center
imageView.isHidden = false
imageDividerView.isHidden = false
containerStackView.alignment = .fill //.center
containerStackView.axis = .vertical
layoutConstraints = [
imageView.heightAnchor.constraint(equalTo: heightAnchor),
imageView.heightAnchor.constraint(equalTo: mainContentStackView.heightAnchor),
imageView.widthAnchor.constraint(equalToConstant: 85),
heightAnchor.constraint(equalToConstant: 85).priority(.defaultLow - 1),
heightAnchor.constraint(greaterThanOrEqualToConstant: 85),
dividerView.heightAnchor.constraint(equalTo: containerStackView.heightAnchor),
authorDivider.heightAnchor.constraint(equalTo: containerStackView.heightAnchor),
// heightAnchor.constraint(equalToConstant: 85).priority(.defaultLow - 1),
// heightAnchor.constraint(greaterThanOrEqualToConstant: 85),
imageDividerView.heightAnchor.constraint(equalTo: mainContentStackView.heightAnchor),
authorDivider.widthAnchor.constraint(equalTo: containerStackView.widthAnchor)
]
dividerConstraint = dividerView.widthAnchor.constraint(equalToConstant: pixelSize).activate()
authorDividerConstraint = authorDivider.widthAnchor.constraint(equalToConstant: pixelSize).activate()
imageDividerConstraint = imageDividerView.widthAnchor.constraint(equalToConstant: pixelSize).priority(.defaultLow - 1).activate()
imageView.isHidden = layout == .noPreviewImage
imageDividerView.isHidden = layout == .noPreviewImage
}

layoutConstraints.append(contentsOf: [
Expand All @@ -356,10 +383,13 @@ public final class StatusCardControl: UIControl {
])

NSLayoutConstraint.activate(layoutConstraints)
invalidateIntrinsicContentSize()
}

private func icon(for layout: Layout) -> UIImage? {
switch layout {
case .noPreviewImage:
return nil
case .compact:
return UIImage(systemName: "newspaper.fill")
case .large:
Expand All @@ -369,7 +399,7 @@ public final class StatusCardControl: UIControl {
}

private func applyBranding() {
dividerView.backgroundColor = SystemTheme.separator
imageDividerView.backgroundColor = SystemTheme.separator
imageView.backgroundColor = UIColor.tertiarySystemFill
}

Expand Down Expand Up @@ -465,19 +495,23 @@ extension StatusCardControl {

private extension StatusCardControl {
enum Layout: Equatable {
case noPreviewImage
case compact
case large(aspectRatio: CGFloat)
}
}

private extension Mastodon.Entity.Card {
var layout: StatusCardControl.Layout {
if (image == nil || image!.isEmpty) && (html == nil || html!.isEmpty) {
return .noPreviewImage
}
var aspectRatio = CGFloat(width ?? 1) / CGFloat(height ?? 1)
if !aspectRatio.isFinite {
aspectRatio = 1
}

if (abs(aspectRatio - 1) < 0.05 || image == nil) && html == nil {
if (abs(aspectRatio - 1) < 0.05 || image == nil) && (html == nil || html!.isEmpty) {
return .compact
} else {
return .large(aspectRatio: aspectRatio)
Expand Down

0 comments on commit 3e2c637

Please sign in to comment.