// // ProfileFieldsView.swift // Tusker // // Created by Shadowfacts on 11/4/22. // Copyright © 2022 Shadowfacts. All rights reserved. // import UIKit import Pachyderm class ProfileFieldsView: UIView { weak var delegate: ProfileHeaderViewDelegate? private var fields = [Account.Field]() private var fieldViews: [(EmojiLabel, ProfileFieldValueView, UIView)] = [] private var fieldConstraints: [NSLayoutConstraint] = [] private lazy var dividerLayoutGuide: UILayoutGuide = { let guide = UILayoutGuide() addLayoutGuide(guide) guide.widthAnchor.constraint(equalToConstant: 8).isActive = true let centerDividerConstraint = guide.centerXAnchor.constraint(equalTo: centerXAnchor) centerDividerConstraint.priority = .defaultHigh centerDividerConstraint.isActive = true return guide }() private var dividerXConstraint: NSLayoutConstraint? private var boundsObservation: NSKeyValueObservation? private var isUsingSingleColumn: Bool = false private var needsSingleColumn: Bool { traitCollection.horizontalSizeClass == .compact && traitCollection.preferredContentSizeCategory > .extraLarge } override var accessibilityElements: [Any]? { get { fieldViews.flatMap { [$0.0, $0.1] } } set {} } override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { boundsObservation = observe(\.bounds, changeHandler: { [unowned self] _, _ in self.setNeedsUpdateConstraints() }) #if os(visionOS) registerForTraitChanges([UITraitHorizontalSizeClass.self, UITraitPreferredContentSizeCategory.self]) { (self: Self, previousTraitCollection) in if self.isUsingSingleColumn != self.needsSingleColumn { self.configureFields() } } #endif } #if !os(visionOS) override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) if isUsingSingleColumn != needsSingleColumn { configureFields() } } #endif func updateUI(account: AccountMO) { isHidden = account.fields.isEmpty guard !account.fields.isEmpty, fields != account.fields else { return } fields = account.fields for (name, value, fieldContainer) in fieldViews { name.removeFromSuperview() value.removeFromSuperview() fieldContainer.removeFromSuperview() } fieldViews = [] for (index, field) in account.fields.enumerated() { let nameLabel = EmojiLabel() nameLabel.text = field.name nameLabel.font = .preferredFont(forTextStyle: .body).withTraits(.traitBold)! nameLabel.adjustsFontForContentSizeCategory = true nameLabel.numberOfLines = 0 nameLabel.lineBreakMode = .byWordWrapping nameLabel.showsExpansionTextWhenTruncated = true nameLabel.setEmojis(account.emojis, identifier: account.id) nameLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) let valueView = ProfileFieldValueView(field: field, account: account) valueView.navigationDelegate = delegate valueView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) let container = UIView() container.translatesAutoresizingMaskIntoConstraints = false addSubview(container) nameLabel.translatesAutoresizingMaskIntoConstraints = false container.addSubview(nameLabel) valueView.translatesAutoresizingMaskIntoConstraints = false container.addSubview(valueView) if index % 2 == 0 { container.backgroundColor = .secondarySystemFill } else { container.backgroundColor = .quaternarySystemFill } if index == 0 || index == fields.count - 1 { if fields.count > 1 && index == 0 { container.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] } else if fields.count > 1 && index == fields.count - 1 { container.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] } container.layer.cornerRadius = 8 container.layer.cornerCurve = .continuous } fieldViews.append((nameLabel, valueView, container)) } configureFields() } @objc private func configureFields() { guard !isHidden else { return } isUsingSingleColumn = needsSingleColumn NSLayoutConstraint.deactivate(fieldConstraints) fieldConstraints = [] var prevContainer: UIView? for (name, value, container) in fieldViews { fieldConstraints.append(contentsOf: [ container.leadingAnchor.constraint(equalTo: leadingAnchor), container.trailingAnchor.constraint(equalTo: trailingAnchor), ]) if needsSingleColumn { name.textAlignment = .natural value.setTextAlignment(.natural) fieldConstraints.append(contentsOf: [ name.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 4), name.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -4), name.topAnchor.constraint(equalTo: container.topAnchor, constant: 4), value.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 4), value.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -4), value.topAnchor.constraint(equalTo: name.bottomAnchor), value.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -4), ]) } else { name.textAlignment = .right value.setTextAlignment(.left) fieldConstraints.append(contentsOf: [ container.heightAnchor.constraint(greaterThanOrEqualToConstant: 32), name.leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor, constant: 4), name.trailingAnchor.constraint(equalTo: dividerLayoutGuide.leadingAnchor), name.topAnchor.constraint(equalTo: container.topAnchor, constant: 4), name.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -4), value.leadingAnchor.constraint(equalTo: dividerLayoutGuide.trailingAnchor), value.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -4), value.topAnchor.constraint(equalTo: container.topAnchor, constant: 4), value.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -4), ]) } let containerTopConstraint = container.topAnchor.constraint(equalTo: prevContainer?.bottomAnchor ?? topAnchor) fieldConstraints.append(containerTopConstraint) prevContainer = container } if let prevContainer { let lastContainerBottomConstraint = prevContainer.bottomAnchor.constraint(equalTo: bottomAnchor) fieldConstraints.append(lastContainerBottomConstraint) } NSLayoutConstraint.activate(fieldConstraints) } override func updateConstraints() { if !needsSingleColumn, !fieldViews.isEmpty { let maxNameWidth = fieldViews.map { $0.0.sizeThatFits(UIView.layoutFittingCompressedSize).width }.max()! let maxValueWidth = fieldViews.map { $0.1.sizeThatFits(UIView.layoutFittingCompressedSize).width }.max()! let defaultWidth = (bounds.width - 8) / 2 dividerXConstraint?.isActive = false if maxNameWidth > defaultWidth && maxValueWidth < defaultWidth { dividerXConstraint = dividerLayoutGuide.leadingAnchor.constraint(equalTo: leadingAnchor, constant: min(maxNameWidth, bounds.width * 2 / 3)) dividerXConstraint!.isActive = true } else if maxNameWidth < defaultWidth && maxValueWidth > defaultWidth { dividerXConstraint = dividerLayoutGuide.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -min(maxValueWidth, bounds.width * 2 / 3)) dividerXConstraint!.isActive = true } } super.updateConstraints() } }