Remove ambiguating constraint priorities, avoid removing and recreating the same constraints

Closes #407
This commit is contained in:
Shadowfacts 2023-07-05 20:30:55 -07:00
parent 50bfaf7236
commit 3c9692d5b2
7 changed files with 59 additions and 61 deletions

View File

@ -48,11 +48,7 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
$0.axis = .horizontal
$0.alignment = .fill
$0.spacing = 8
let heightConstraint = $0.heightAnchor.constraint(equalToConstant: 30)
// the collection view cell imposes a height constraint before it's calculated the actual height
// so let this constraint be broken temporarily to avoid unsatisfiable constraints log spam
heightConstraint.priority = .init(999)
heightConstraint.isActive = true
$0.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
private lazy var actionLabel = MultiSourceEmojiLabel().configure {

View File

@ -45,9 +45,7 @@ class FollowNotificationGroupCollectionViewCell: UICollectionViewListCell {
]).configure {
$0.axis = .horizontal
$0.alignment = .fill
let heightConstraint = $0.heightAnchor.constraint(equalToConstant: 30)
heightConstraint.priority = .init(999)
heightConstraint.isActive = true
$0.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
private lazy var actionLabel = MultiSourceEmojiLabel().configure {

View File

@ -48,9 +48,7 @@ class FollowRequestNotificationCollectionViewCell: UICollectionViewListCell {
]).configure {
$0.axis = .horizontal
$0.alignment = .fill
let heightConstraint = $0.heightAnchor.constraint(equalToConstant: 30)
heightConstraint.priority = .init(999)
heightConstraint.isActive = true
$0.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
private lazy var actionLabel = EmojiLabel().configure {

View File

@ -91,9 +91,6 @@ class PollFinishedNotificationCollectionViewCell: UICollectionViewListCell {
contentView.addSubview(iconView)
vStack.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(vStack)
let vStackBottomConstraint = vStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8)
// need something to break during intermediate layouts when the cell imposes a 44pt height :S
vStackBottomConstraint.priority = .init(999)
NSLayoutConstraint.activate([
iconView.topAnchor.constraint(equalTo: vStack.topAnchor),
iconView.trailingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16 + 50),
@ -101,7 +98,7 @@ class PollFinishedNotificationCollectionViewCell: UICollectionViewListCell {
vStack.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 8),
vStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
vStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
vStackBottomConstraint,
vStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
])
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)

View File

@ -56,13 +56,8 @@ class PollOptionView: UIView {
percentLabel.setContentHuggingPriority(.required, for: .horizontal)
addSubview(percentLabel)
let minHeightConstraint = heightAnchor.constraint(greaterThanOrEqualToConstant: PollOptionView.minHeight)
// on the first layout, something is weird and this becomes ambiguous even though it's fine on subsequent layouts
// this keeps autolayout from complaining
minHeightConstraint.priority = .required - 1
NSLayoutConstraint.activate([
minHeightConstraint,
heightAnchor.constraint(greaterThanOrEqualToConstant: PollOptionView.minHeight),
label.topAnchor.constraint(equalTo: topAnchor, constant: 4),
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),

View File

@ -50,8 +50,9 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: StatusConte
private var isHiddenObservations: [NSKeyValueObservation] = []
private var visibleSubviews = IndexSet()
private var verticalConstraints: [NSLayoutConstraint] = []
private var lastSubviewBottomConstraint: NSLayoutConstraint?
private var lastSubviewBottomConstraint: (UIView, NSLayoutConstraint)?
private var zeroHeightConstraint: NSLayoutConstraint!
private var isCollapsed = false
@ -93,31 +94,35 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: StatusConte
}
override func updateConstraints() {
let visibleSubviews = IndexSet(arrangedSubviews.indices.filter { !arrangedSubviews[$0].isHidden })
if self.visibleSubviews != visibleSubviews {
self.visibleSubviews = visibleSubviews
NSLayoutConstraint.deactivate(verticalConstraints)
verticalConstraints = []
var lastVisibleSubview: UIView?
for subview in arrangedSubviews {
guard !subview.isHidden else {
continue
}
for subviewIndex in visibleSubviews {
let subview = arrangedSubviews[subviewIndex]
if let lastVisibleSubview {
verticalConstraints.append(subview.topAnchor.constraint(equalTo: lastVisibleSubview.bottomAnchor, constant: 4))
} else {
verticalConstraints.append(subview.topAnchor.constraint(equalTo: topAnchor))
}
lastVisibleSubview = subview
}
NSLayoutConstraint.activate(verticalConstraints)
}
lastSubviewBottomConstraint?.isActive = false
if lastSubviewBottomConstraint == nil || arrangedSubviews[visibleSubviews.last!] !== lastSubviewBottomConstraint?.0 {
lastSubviewBottomConstraint?.1.isActive = false
// this constraint needs to have low priority so that during the collapse/expand animation, the content container is the view that shrinks/expands
lastSubviewBottomConstraint = subviews.last(where: { !$0.isHidden })!.bottomAnchor.constraint(equalTo: bottomAnchor)
lastSubviewBottomConstraint!.isActive = !isCollapsed
lastSubviewBottomConstraint!.priority = .defaultLow
let lastVisibleSubview = arrangedSubviews[visibleSubviews.last!]
let constraint = lastVisibleSubview.bottomAnchor.constraint(equalTo: bottomAnchor)
constraint.isActive = !isCollapsed
constraint.priority = .defaultLow
lastSubviewBottomConstraint = (lastVisibleSubview, constraint)
}
zeroHeightConstraint.isActive = isCollapsed
@ -133,7 +138,7 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: StatusConte
// don't call setNeedsUpdateConstraints b/c that destroys/recreates a bunch of other constraints
// if there is no lastSubviewBottomConstraint, then we already need a constraint update, so we don't need to do anything here
if let lastSubviewBottomConstraint {
lastSubviewBottomConstraint.isActive = !collapsed
lastSubviewBottomConstraint.1.isActive = !collapsed
zeroHeightConstraint.isActive = collapsed
}
}

View File

@ -35,9 +35,11 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
$0.layer.masksToBounds = true
$0.layer.cornerCurve = .continuous
$0.tintColor = .secondaryLabel
let heightConstraint = $0.heightAnchor.constraint(equalToConstant: TimelineStatusCollectionViewCell.timelineReasonIconSize)
heightConstraint.identifier = "TimelineReason-Height"
NSLayoutConstraint.activate([
// this needs to be lessThanOrEqualTo not just equalTo b/c otherwise intermediate layouts are broken
$0.heightAnchor.constraint(lessThanOrEqualToConstant: TimelineStatusCollectionViewCell.timelineReasonIconSize),
heightConstraint,
$0.widthAnchor.constraint(equalTo: $0.heightAnchor),
])
}
@ -60,16 +62,20 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
$0.addSubview(contentVStack)
metaIndicatorsView.translatesAutoresizingMaskIntoConstraints = false
$0.addSubview(metaIndicatorsView)
let avatarTopConstraint = avatarImageView.topAnchor.constraint(equalTo: $0.topAnchor)
avatarTopConstraint.identifier = "Avatar-Top"
let metaIndicatorsTopConstraint = metaIndicatorsView.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: 4)
metaIndicatorsTopConstraint.identifier = "MetaIndicators-Top"
NSLayoutConstraint.activate([
avatarImageView.leadingAnchor.constraint(equalTo: $0.leadingAnchor),
avatarImageView.topAnchor.constraint(equalTo: $0.topAnchor),
avatarTopConstraint,
contentVStack.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 8),
contentVStack.trailingAnchor.constraint(equalTo: $0.trailingAnchor),
contentVStack.topAnchor.constraint(equalTo: $0.topAnchor),
contentVStack.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
metaIndicatorsView.leadingAnchor.constraint(greaterThanOrEqualTo: $0.leadingAnchor),
metaIndicatorsView.trailingAnchor.constraint(equalTo: avatarImageView.trailingAnchor),
metaIndicatorsView.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: 4),
metaIndicatorsTopConstraint,
])
}
@ -177,6 +183,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
$0.tintAdjustmentMode = .normal
$0.setContentHuggingPriority(.defaultHigh, for: .vertical)
$0.addTarget(self, action: #selector(collapseButtonPressed), for: .touchUpInside)
$0.setContentCompressionResistancePriority(.required, for: .vertical)
}
let contentContainer = StatusContentContainer<StatusContentTextView, StatusPollView>(useTopSpacer: false).configure {
@ -316,17 +323,12 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
}
mainContainerTopToReblogLabelConstraint = mainContainer.topAnchor.constraint(equalTo: timelineReasonHStack.bottomAnchor, constant: 4)
mainContainerTopToReblogLabelConstraint.identifier = "MainContainerTopToReblog"
mainContainerTopToSelfConstraint = mainContainer.topAnchor.constraint(equalTo: statusContainer.topAnchor, constant: 8)
// when flipping between topToReblog and topToSelf constraints, the framework sometimes thinks both of them should be active simultaneously
// even though the code never does that; so let this one get broken temporarily
mainContainerTopToSelfConstraint.priority = .init(999)
mainContainerTopToSelfConstraint.identifier = "MainContainerTopToSelf"
mainContainerBottomToActionsConstraint = mainContainer.bottomAnchor.constraint(equalTo: actionsContainer.topAnchor, constant: -4)
mainContainerBottomToSelfConstraint = mainContainer.bottomAnchor.constraint(equalTo: statusContainer.bottomAnchor, constant: -6)
let metaIndicatorsBottomConstraint = metaIndicatorsView.bottomAnchor.constraint(lessThanOrEqualTo: statusContainer.bottomAnchor, constant: -6)
// sometimes during intermediate layouts, there are conflicting constraints, so let this one get broken temporarily, to avoid a bunch of printing
metaIndicatorsBottomConstraint.priority = .init(999)
NSLayoutConstraint.activate([
// why is this 4 but the mainContainerTopSelfConstraint constant 8? because this looks more balanced
timelineReasonHStack.topAnchor.constraint(equalTo: statusContainer.topAnchor, constant: 4),
@ -335,13 +337,14 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
mainContainer.leadingAnchor.constraint(equalTo: statusContainer.leadingAnchor, constant: 16),
mainContainer.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: -16),
mainContainerBottomToActionsConstraint,
actionsContainer.leadingAnchor.constraint(equalTo: statusContainer.leadingAnchor, constant: 16),
actionsContainer.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: -16),
// yes, this is deliberately 6. 4 looks to cramped, 8 looks uneven
actionsContainer.bottomAnchor.constraint(equalTo: statusContainer.bottomAnchor, constant: -6),
metaIndicatorsBottomConstraint,
metaIndicatorsView.bottomAnchor.constraint(lessThanOrEqualTo: statusContainer.bottomAnchor, constant: -6),
])
updateActionsVisibility()
@ -590,8 +593,14 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
}
timelineReasonHStack.isHidden = hideTimelineReason
mainContainerTopToReblogLabelConstraint.isActive = !hideTimelineReason
mainContainerTopToSelfConstraint.isActive = hideTimelineReason
// do this to make sure the currently active constraint is deactivated first
if hideTimelineReason {
mainContainerTopToReblogLabelConstraint.isActive = false
mainContainerTopToSelfConstraint.isActive = true
} else {
mainContainerTopToSelfConstraint.isActive = false
mainContainerTopToReblogLabelConstraint.isActive = true
}
doUpdateUI(status: status, precomputedContent: precomputedContent)
@ -690,11 +699,11 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
}
private func updateActionsVisibility() {
if Preferences.shared.hideActionsInTimeline {
if Preferences.shared.hideActionsInTimeline && !actionsContainer.isHidden {
actionsContainer.isHidden = true
mainContainerBottomToSelfConstraint.isActive = true
mainContainerBottomToActionsConstraint.isActive = false
} else {
mainContainerBottomToSelfConstraint.isActive = true
} else if !Preferences.shared.hideActionsInTimeline && actionsContainer.isHidden {
actionsContainer.isHidden = false
mainContainerBottomToSelfConstraint.isActive = false
mainContainerBottomToActionsConstraint.isActive = true