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)
|
||||
}
|
||||
|
||||
private let contentContainer = StatusContentContainer<StatusEditContentTextView, StatusEditPollView>(useTopSpacer: false).configure {
|
||||
$0.contentTextView.defaultFont = TimelineStatusCollectionViewCell.contentFont
|
||||
$0.contentTextView.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
||||
$0.contentTextView.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
private lazy var contentContainer = StatusContentContainer(arrangedSubviews: [
|
||||
contentTextView,
|
||||
cardView,
|
||||
attachmentsView,
|
||||
pollView,
|
||||
] as! [any StatusContentView], useTopSpacer: false).configure {
|
||||
$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?
|
||||
private var mastodonController: MastodonController! { delegate?.apiController }
|
||||
|
||||
|
@ -108,7 +129,7 @@ class StatusEditCollectionViewCell: UICollectionViewListCell {
|
|||
}
|
||||
str += "collapsed"
|
||||
} else {
|
||||
str += AttributedString(contentContainer.contentTextView.attributedText)
|
||||
str += AttributedString(contentTextView.attributedText)
|
||||
|
||||
if edit.attachments.count > 0 {
|
||||
let includeDescriptions: Bool
|
||||
|
@ -170,13 +191,13 @@ class StatusEditCollectionViewCell: UICollectionViewListCell {
|
|||
|
||||
timestampLabel.text = ConversationMainStatusCollectionViewCell.dateFormatter.string(from: edit.createdAt)
|
||||
|
||||
contentContainer.contentTextView.setTextFrom(edit: edit, index: index)
|
||||
contentContainer.contentTextView.navigationDelegate = delegate
|
||||
contentContainer.attachmentsView.delegate = self
|
||||
contentContainer.attachmentsView.updateUI(attachments: edit.attachments)
|
||||
contentContainer.pollView.isHidden = edit.poll == nil
|
||||
contentContainer.pollView.updateUI(poll: edit.poll, emojis: edit.emojis)
|
||||
contentContainer.cardView.isHidden = true
|
||||
contentTextView.setTextFrom(edit: edit, index: index)
|
||||
contentTextView.navigationDelegate = delegate
|
||||
attachmentsView.delegate = self
|
||||
attachmentsView.updateUI(attachments: edit.attachments)
|
||||
pollView.isHidden = edit.poll == nil
|
||||
pollView.updateUI(poll: edit.poll, emojis: edit.emojis)
|
||||
cardView.isHidden = true
|
||||
|
||||
contentWarningLabel.text = edit.spoilerText
|
||||
contentWarningLabel.isHidden = edit.spoilerText.isEmpty
|
||||
|
@ -219,9 +240,9 @@ extension StatusEditCollectionViewCell: AttachmentViewDelegate {
|
|||
guard let delegate else {
|
||||
return nil
|
||||
}
|
||||
let attachments = contentContainer.attachmentsView.attachments!
|
||||
let attachments = attachmentsView.attachments!
|
||||
let sourceViews = attachments.map {
|
||||
contentContainer.attachmentsView.getAttachmentView(for: $0)
|
||||
attachmentsView.getAttachmentView(for: $0)
|
||||
}
|
||||
let gallery = delegate.gallery(attachments: attachments, sourceViews: sourceViews, startIndex: index)
|
||||
return gallery
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class StatusEditPollView: UIStackView, StatusContentPollView {
|
||||
class StatusEditPollView: UIStackView, StatusContentView {
|
||||
|
||||
private var titleLabels: [EmojiLabel] = []
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class StatusPollView: UIView, StatusContentPollView {
|
||||
class StatusPollView: UIView, StatusContentView {
|
||||
|
||||
private static let formatter: DateComponentsFormatter = {
|
||||
let f = DateComponentsFormatter()
|
||||
|
|
|
@ -117,18 +117,38 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
|||
$0.addTarget(self, action: #selector(collapseButtonPressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
let contentContainer = StatusContentContainer<StatusContentTextView, StatusPollView>(useTopSpacer: true).configure {
|
||||
$0.contentTextView.defaultFont = ConversationMainStatusCollectionViewCell.contentFont
|
||||
$0.contentTextView.monospaceFont = ConversationMainStatusCollectionViewCell.monospaceFont
|
||||
$0.contentTextView.paragraphStyle = ConversationMainStatusCollectionViewCell.contentParagraphStyle
|
||||
$0.contentTextView.isSelectable = true
|
||||
$0.contentTextView.dataDetectorTypes = [.flightNumber, .address, .shipmentTrackingNumber, .phoneNumber]
|
||||
if #available(iOS 16.0, *) {
|
||||
$0.contentTextView.dataDetectorTypes.formUnion([.money, .physicalValue])
|
||||
}
|
||||
private(set) lazy var contentContainer = StatusContentContainer(arrangedSubviews: [
|
||||
contentTextView,
|
||||
cardView,
|
||||
attachmentsView,
|
||||
pollView,
|
||||
] as! [any StatusContentView], useTopSpacer: true).configure {
|
||||
$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 {
|
||||
$0.titleLabel!.adjustsFontForContentSizeCategory = true
|
||||
$0.addTarget(self, action: #selector(favoritesCountPressed), for: .touchUpInside)
|
||||
|
@ -318,9 +338,9 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
|||
accountDetailAccessibilityElement,
|
||||
contentWarningLabel,
|
||||
collapseButton,
|
||||
contentContainer.contentTextView,
|
||||
contentContainer.attachmentsView,
|
||||
contentContainer.pollView,
|
||||
contentTextView,
|
||||
attachmentsView,
|
||||
pollView,
|
||||
favoritesCountButton,
|
||||
reblogsCountButton,
|
||||
timestampAndClientLabel,
|
||||
|
|
|
@ -24,7 +24,11 @@ protocol StatusCollectionViewCell: UICollectionViewCell, AttachmentViewDelegate
|
|||
var usernameLabel: UILabel { get }
|
||||
var contentWarningLabel: EmojiLabel { 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 favoriteButton: ToggleableButton { get }
|
||||
var reblogButton: ToggleableButton { get }
|
||||
|
@ -90,20 +94,20 @@ extension StatusCollectionViewCell {
|
|||
|
||||
updateAccountUI(account: status.account)
|
||||
|
||||
contentContainer.contentTextView.setTextFrom(status: status, precomputed: precomputedContent)
|
||||
contentContainer.contentTextView.navigationDelegate = delegate
|
||||
contentTextView.setTextFrom(status: status, precomputed: precomputedContent)
|
||||
contentTextView.navigationDelegate = delegate
|
||||
self.updateAttachmentsUI(status: status)
|
||||
contentContainer.pollView.isHidden = status.poll == nil
|
||||
contentContainer.pollView.mastodonController = mastodonController
|
||||
contentContainer.pollView.delegate = delegate
|
||||
contentContainer.pollView.updateUI(status: status, poll: status.poll)
|
||||
pollView.isHidden = status.poll == nil
|
||||
pollView.mastodonController = mastodonController
|
||||
pollView.delegate = delegate
|
||||
pollView.updateUI(status: status, poll: status.poll)
|
||||
if Preferences.shared.showLinkPreviews {
|
||||
contentContainer.cardView.updateUI(status: status)
|
||||
contentContainer.cardView.isHidden = status.card == nil
|
||||
contentContainer.cardView.navigationDelegate = delegate
|
||||
contentContainer.cardView.actionProvider = delegate
|
||||
cardView.updateUI(status: status)
|
||||
cardView.isHidden = status.card == nil
|
||||
cardView.navigationDelegate = delegate
|
||||
cardView.actionProvider = delegate
|
||||
} else {
|
||||
contentContainer.cardView.isHidden = true
|
||||
cardView.isHidden = true
|
||||
}
|
||||
|
||||
updateUIForPreferences(status: status)
|
||||
|
@ -168,8 +172,8 @@ extension StatusCollectionViewCell {
|
|||
}
|
||||
|
||||
func updateAttachmentsUI(status: StatusMO) {
|
||||
contentContainer.attachmentsView.delegate = self
|
||||
contentContainer.attachmentsView.updateUI(attachments: status.attachments)
|
||||
attachmentsView.delegate = self
|
||||
attachmentsView.updateUI(attachments: status.attachments)
|
||||
}
|
||||
|
||||
func updateAccountUI(account: AccountMO) {
|
||||
|
@ -182,20 +186,20 @@ extension StatusCollectionViewCell {
|
|||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * Self.avatarImageViewSize
|
||||
|
||||
let newCardHidden = !Preferences.shared.showLinkPreviews || status.card == nil
|
||||
if contentContainer.cardView.isHidden != newCardHidden {
|
||||
if cardView.isHidden != newCardHidden {
|
||||
delegate?.statusCellNeedsReconfigure(self, animated: false, completion: nil)
|
||||
}
|
||||
|
||||
switch Preferences.shared.attachmentBlurMode {
|
||||
case .never:
|
||||
contentContainer.attachmentsView.contentHidden = false
|
||||
attachmentsView.contentHidden = false
|
||||
case .always:
|
||||
contentContainer.attachmentsView.contentHidden = true
|
||||
attachmentsView.contentHidden = true
|
||||
default:
|
||||
if status.sensitive {
|
||||
contentContainer.attachmentsView.contentHidden = status.spoilerText.isEmpty || Preferences.shared.blurMediaBehindContentWarning
|
||||
attachmentsView.contentHidden = status.spoilerText.isEmpty || Preferences.shared.blurMediaBehindContentWarning
|
||||
} else {
|
||||
contentContainer.attachmentsView.contentHidden = false
|
||||
attachmentsView.contentHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,8 +215,8 @@ extension StatusCollectionViewCell {
|
|||
// only called when isGrayscale does not match the pref
|
||||
func updateGrayscaleableUI(status: StatusMO) {
|
||||
isGrayscale = Preferences.shared.grayscaleImages
|
||||
if contentContainer.contentTextView.hasEmojis {
|
||||
contentContainer.contentTextView.setEmojis(status.emojis, identifier: status.id)
|
||||
if contentTextView.hasEmojis {
|
||||
contentTextView.setEmojis(status.emojis, identifier: status.id)
|
||||
}
|
||||
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
|
||||
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: delegate?.actionsForStatus(status, source: .view(moreButton), includeStatusButtonActions: false) ?? [])
|
||||
|
||||
contentContainer.pollView.isHidden = status.poll == nil
|
||||
contentContainer.pollView.mastodonController = mastodonController
|
||||
contentContainer.pollView.delegate = delegate
|
||||
contentContainer.pollView.updateUI(status: status, poll: status.poll)
|
||||
pollView.isHidden = status.poll == nil
|
||||
pollView.mastodonController = mastodonController
|
||||
pollView.delegate = delegate
|
||||
pollView.updateUI(status: status, poll: status.poll)
|
||||
}
|
||||
|
||||
func setShowThreadLinks(prev: Bool, next: Bool) {
|
||||
|
@ -327,7 +331,7 @@ extension StatusCollectionViewCell {
|
|||
func attachmentViewGallery(startingAt index: Int) -> GalleryViewController? {
|
||||
guard let delegate = delegate,
|
||||
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)
|
||||
// TODO: PiP
|
||||
// gallery.avPlayerViewControllerDelegate = self
|
||||
|
|
|
@ -8,45 +8,11 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
protocol StatusContentPollView: UIView {
|
||||
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat
|
||||
}
|
||||
|
||||
class StatusContentContainer<ContentView: ContentTextView, PollView: StatusContentPollView>: UIView {
|
||||
class StatusContentContainer: UIView {
|
||||
// TODO: this is a weird place for this
|
||||
static var cardViewHeight: CGFloat { 90 }
|
||||
|
||||
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 let arrangedSubviews: [any StatusContentView]
|
||||
|
||||
private var isHiddenObservations: [NSKeyValueObservation] = []
|
||||
|
||||
|
@ -61,8 +27,12 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: StatusConte
|
|||
subviews.filter { !$0.isHidden }.map(\.bounds.height).reduce(0, +)
|
||||
}
|
||||
|
||||
init(useTopSpacer: Bool) {
|
||||
self.useTopSpacer = useTopSpacer
|
||||
init(arrangedSubviews: [any StatusContentView], useTopSpacer: Bool) {
|
||||
var arrangedSubviews = arrangedSubviews
|
||||
if useTopSpacer {
|
||||
arrangedSubviews.insert(TopSpacerView(), at: 0)
|
||||
}
|
||||
self.arrangedSubviews = arrangedSubviews
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
|
@ -83,7 +53,7 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: StatusConte
|
|||
setNeedsUpdateConstraints()
|
||||
|
||||
isHiddenObservations = arrangedSubviews.map {
|
||||
$0.observe(\.isHidden) { [unowned self] _, _ in
|
||||
$0.observeIsHidden { [unowned self] in
|
||||
self.setNeedsUpdateConstraints()
|
||||
}
|
||||
}
|
||||
|
@ -147,18 +117,60 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: StatusConte
|
|||
// 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
|
||||
for view in arrangedSubviews where !view.isHidden {
|
||||
height += view.estimateHeight(effectiveWidth: effectiveWidth)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
let contentContainer = StatusContentContainer<StatusContentTextView, StatusPollView>(useTopSpacer: false).configure {
|
||||
$0.contentTextView.defaultFont = TimelineStatusCollectionViewCell.contentFont
|
||||
$0.contentTextView.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont
|
||||
$0.contentTextView.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
private(set) lazy var contentContainer = StatusContentContainer(arrangedSubviews: [
|
||||
contentTextView,
|
||||
cardView,
|
||||
attachmentsView,
|
||||
pollView,
|
||||
] as! [any StatusContentView], useTopSpacer: false).configure {
|
||||
$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
|
||||
}
|
||||
private var attachmentsView: AttachmentsContainerView {
|
||||
contentContainer.attachmentsView
|
||||
}
|
||||
private var pollView: StatusPollView {
|
||||
contentContainer.pollView
|
||||
|
||||
let cardView = StatusCardView().configure {
|
||||
$0.heightAnchor.constraint(equalToConstant: StatusContentContainer.cardViewHeight).isActive = true
|
||||
}
|
||||
|
||||
let attachmentsView = AttachmentsContainerView()
|
||||
|
||||
let pollView = StatusPollView()
|
||||
|
||||
private var placeholderReplyButtonLeadingConstraint: NSLayoutConstraint!
|
||||
private lazy var actionsContainer = UIView().configure {
|
||||
replyButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
|
Loading…
Reference in New Issue