import UIKit
/// Show a distribution list in a list
///
/// Whenever there is a list of distribution list use this cell for a consistent appearance
public final class DistributionListCell: ThemedCodeTableViewCell {
// MARK: - Private configuration
private lazy var configuration = CellConfiguration(size: size)
// MARK: - Public properties
override public func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
guard hasCheckmark else {
return
}
checkMarkView.isChecked = selected
}
public var hasCheckmark = false {
didSet {
checkMarkView.isHidden = !hasCheckmark
}
}
public var size = CellConfiguration.Size.small {
didSet {
guard size != oldValue else {
return
}
configuration = CellConfiguration(size: size)
sizeDidChange()
}
}
/// Distribution list to show
public var distributionList: DistributionList? {
didSet {
guard let distributionList, let name = distributionList.displayName else {
return
}
nameLabel.text = name
topMetadataLabel.text = distributionList.recipientCountString
membersListLabel.text = distributionList.recipientsSummary
profilePictureView.info = .distributionList(distributionList)
}
}
// MARK: - Subviews
private var profilePictureSizeConstraint: NSLayoutConstraint?
private lazy var profilePictureView: ProfilePictureImageView = {
let imageView = ProfilePictureImageView()
profilePictureSizeConstraint = imageView.heightAnchor
.constraint(lessThanOrEqualToConstant: configuration.maxProfilePictureSize)
profilePictureSizeConstraint?.isActive = true
if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
imageView.isHidden = true
}
return imageView
}()
private lazy var nameLabel: UILabel = {
let label = UILabel()
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
label.numberOfLines = 2
}
return label
}()
private lazy var topMetadataLabel: UILabel = {
let label = UILabel()
label.textAlignment = .right
label.font = .preferredFont(forTextStyle: .footnote)
label.textColor = .secondaryLabel
label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
return label
}()
private lazy var membersListLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .footnote)
label.textColor = .secondaryLabel
if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
label.numberOfLines = 2
}
return label
}()
private lazy var checkMarkView: CustomCellCheckMarkAccessoryView = {
let view = CustomCellCheckMarkAccessoryView()
view.isHidden = !hasCheckmark
return view
}()
// MARK: Layout stacks
private lazy var firstLineStack: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [nameLabel, topMetadataLabel])
stackView.axis = .horizontal
stackView.alignment = .firstBaseline
stackView.spacing = 8
stackView.distribution = .equalSpacing
// As view cells are recreated for each content size change so we can just set this here
if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
stackView.axis = .vertical
stackView.alignment = .leading
}
return stackView
}()
private lazy var textStack: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [firstLineStack, membersListLabel])
stackView.spacing = configuration.verticalSpacing
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.alignment = .fill
return stackView
}()
private lazy var containerStack: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [profilePictureView, textStack, checkMarkView])
stackView.axis = .horizontal
stackView.distribution = .fill
stackView.alignment = .center
return stackView
}()
// MARK: - Lifecycle
override public func configureCell() {
super.configureCell()
sizeDidChange()
contentView.addSubview(containerStack)
containerStack.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
containerStack.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
containerStack.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
containerStack.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
containerStack.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
])
}
@objc public func updateDistributionList(_ distList: DistributionList?) {
guard let distList else {
distributionList = nil
return
}
distributionList = distList
}
// MARK: - Updates
private func sizeDidChange() {
nameLabel.font = configuration.nameLabelFont
containerStack.spacing = configuration.horizontalSpacing
// Note: We don't reload the profile picture here. So if the `content` is assigned before the `size`
// we might have a blurry profile picture.
profilePictureSizeConstraint?.constant = configuration.maxProfilePictureSize
updateSeparatorInset()
}
private func updateSeparatorInset() {
guard !traitCollection.preferredContentSizeCategory.isAccessibilityCategory else {
separatorInset = .zero
return
}
let leftSeparatorInset = configuration.maxProfilePictureSize + configuration.horizontalSpacing
separatorInset = UIEdgeInsets(top: 0, left: leftSeparatorInset, bottom: 0, right: 0)
}
// MARK: - Accessibility
override public var accessibilityLabel: String? {
get {
nameLabel.accessibilityLabel
}
set { }
}
override public var accessibilityValue: String? {
get {
[
topMetadataLabel.accessibilityLabel,
membersListLabel.accessibilityLabel,
]
.compactMap(\.self)
.filter { !$0.isEmpty }
.joined(separator: ". ")
}
set { }
}
}
// MARK: - Reusable
extension DistributionListCell: Reusable { }
¤ Dauer der Verarbeitung: 0.1 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland