forked from shadowfacts/Tusker
Remove ambiguating constraint priorities, avoid removing and recreating the same constraints
Closes #407
This commit is contained in:
parent
50bfaf7236
commit
3c9692d5b2
|
@ -48,11 +48,7 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
||||||
$0.axis = .horizontal
|
$0.axis = .horizontal
|
||||||
$0.alignment = .fill
|
$0.alignment = .fill
|
||||||
$0.spacing = 8
|
$0.spacing = 8
|
||||||
let heightConstraint = $0.heightAnchor.constraint(equalToConstant: 30)
|
$0.heightAnchor.constraint(equalToConstant: 30).isActive = true
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private lazy var actionLabel = MultiSourceEmojiLabel().configure {
|
private lazy var actionLabel = MultiSourceEmojiLabel().configure {
|
||||||
|
|
|
@ -45,9 +45,7 @@ class FollowNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
||||||
]).configure {
|
]).configure {
|
||||||
$0.axis = .horizontal
|
$0.axis = .horizontal
|
||||||
$0.alignment = .fill
|
$0.alignment = .fill
|
||||||
let heightConstraint = $0.heightAnchor.constraint(equalToConstant: 30)
|
$0.heightAnchor.constraint(equalToConstant: 30).isActive = true
|
||||||
heightConstraint.priority = .init(999)
|
|
||||||
heightConstraint.isActive = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private lazy var actionLabel = MultiSourceEmojiLabel().configure {
|
private lazy var actionLabel = MultiSourceEmojiLabel().configure {
|
||||||
|
|
|
@ -48,9 +48,7 @@ class FollowRequestNotificationCollectionViewCell: UICollectionViewListCell {
|
||||||
]).configure {
|
]).configure {
|
||||||
$0.axis = .horizontal
|
$0.axis = .horizontal
|
||||||
$0.alignment = .fill
|
$0.alignment = .fill
|
||||||
let heightConstraint = $0.heightAnchor.constraint(equalToConstant: 30)
|
$0.heightAnchor.constraint(equalToConstant: 30).isActive = true
|
||||||
heightConstraint.priority = .init(999)
|
|
||||||
heightConstraint.isActive = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private lazy var actionLabel = EmojiLabel().configure {
|
private lazy var actionLabel = EmojiLabel().configure {
|
||||||
|
|
|
@ -91,9 +91,6 @@ class PollFinishedNotificationCollectionViewCell: UICollectionViewListCell {
|
||||||
contentView.addSubview(iconView)
|
contentView.addSubview(iconView)
|
||||||
vStack.translatesAutoresizingMaskIntoConstraints = false
|
vStack.translatesAutoresizingMaskIntoConstraints = false
|
||||||
contentView.addSubview(vStack)
|
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([
|
NSLayoutConstraint.activate([
|
||||||
iconView.topAnchor.constraint(equalTo: vStack.topAnchor),
|
iconView.topAnchor.constraint(equalTo: vStack.topAnchor),
|
||||||
iconView.trailingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16 + 50),
|
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.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 8),
|
||||||
vStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
|
vStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
|
||||||
vStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
|
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)
|
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||||
|
|
|
@ -56,13 +56,8 @@ class PollOptionView: UIView {
|
||||||
percentLabel.setContentHuggingPriority(.required, for: .horizontal)
|
percentLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
addSubview(percentLabel)
|
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([
|
NSLayoutConstraint.activate([
|
||||||
minHeightConstraint,
|
heightAnchor.constraint(greaterThanOrEqualToConstant: PollOptionView.minHeight),
|
||||||
|
|
||||||
label.topAnchor.constraint(equalTo: topAnchor, constant: 4),
|
label.topAnchor.constraint(equalTo: topAnchor, constant: 4),
|
||||||
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
|
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
|
||||||
|
|
|
@ -50,8 +50,9 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: StatusConte
|
||||||
|
|
||||||
private var isHiddenObservations: [NSKeyValueObservation] = []
|
private var isHiddenObservations: [NSKeyValueObservation] = []
|
||||||
|
|
||||||
|
private var visibleSubviews = IndexSet()
|
||||||
private var verticalConstraints: [NSLayoutConstraint] = []
|
private var verticalConstraints: [NSLayoutConstraint] = []
|
||||||
private var lastSubviewBottomConstraint: NSLayoutConstraint?
|
private var lastSubviewBottomConstraint: (UIView, NSLayoutConstraint)?
|
||||||
private var zeroHeightConstraint: NSLayoutConstraint!
|
private var zeroHeightConstraint: NSLayoutConstraint!
|
||||||
|
|
||||||
private var isCollapsed = false
|
private var isCollapsed = false
|
||||||
|
@ -93,31 +94,35 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: StatusConte
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateConstraints() {
|
override func updateConstraints() {
|
||||||
|
let visibleSubviews = IndexSet(arrangedSubviews.indices.filter { !arrangedSubviews[$0].isHidden })
|
||||||
|
if self.visibleSubviews != visibleSubviews {
|
||||||
|
self.visibleSubviews = visibleSubviews
|
||||||
NSLayoutConstraint.deactivate(verticalConstraints)
|
NSLayoutConstraint.deactivate(verticalConstraints)
|
||||||
verticalConstraints = []
|
verticalConstraints = []
|
||||||
var lastVisibleSubview: UIView?
|
var lastVisibleSubview: UIView?
|
||||||
|
|
||||||
for subview in arrangedSubviews {
|
for subviewIndex in visibleSubviews {
|
||||||
guard !subview.isHidden else {
|
let subview = arrangedSubviews[subviewIndex]
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if let lastVisibleSubview {
|
if let lastVisibleSubview {
|
||||||
verticalConstraints.append(subview.topAnchor.constraint(equalTo: lastVisibleSubview.bottomAnchor, constant: 4))
|
verticalConstraints.append(subview.topAnchor.constraint(equalTo: lastVisibleSubview.bottomAnchor, constant: 4))
|
||||||
} else {
|
} else {
|
||||||
verticalConstraints.append(subview.topAnchor.constraint(equalTo: topAnchor))
|
verticalConstraints.append(subview.topAnchor.constraint(equalTo: topAnchor))
|
||||||
}
|
}
|
||||||
|
|
||||||
lastVisibleSubview = subview
|
lastVisibleSubview = subview
|
||||||
}
|
}
|
||||||
|
|
||||||
NSLayoutConstraint.activate(verticalConstraints)
|
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
|
// 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)
|
let lastVisibleSubview = arrangedSubviews[visibleSubviews.last!]
|
||||||
lastSubviewBottomConstraint!.isActive = !isCollapsed
|
let constraint = lastVisibleSubview.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||||
lastSubviewBottomConstraint!.priority = .defaultLow
|
constraint.isActive = !isCollapsed
|
||||||
|
constraint.priority = .defaultLow
|
||||||
|
lastSubviewBottomConstraint = (lastVisibleSubview, constraint)
|
||||||
|
}
|
||||||
|
|
||||||
zeroHeightConstraint.isActive = isCollapsed
|
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
|
// 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 there is no lastSubviewBottomConstraint, then we already need a constraint update, so we don't need to do anything here
|
||||||
if let lastSubviewBottomConstraint {
|
if let lastSubviewBottomConstraint {
|
||||||
lastSubviewBottomConstraint.isActive = !collapsed
|
lastSubviewBottomConstraint.1.isActive = !collapsed
|
||||||
zeroHeightConstraint.isActive = collapsed
|
zeroHeightConstraint.isActive = collapsed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,11 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
$0.layer.masksToBounds = true
|
$0.layer.masksToBounds = true
|
||||||
$0.layer.cornerCurve = .continuous
|
$0.layer.cornerCurve = .continuous
|
||||||
$0.tintColor = .secondaryLabel
|
$0.tintColor = .secondaryLabel
|
||||||
|
let heightConstraint = $0.heightAnchor.constraint(equalToConstant: TimelineStatusCollectionViewCell.timelineReasonIconSize)
|
||||||
|
heightConstraint.identifier = "TimelineReason-Height"
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
// this needs to be lessThanOrEqualTo not just equalTo b/c otherwise intermediate layouts are broken
|
// 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),
|
$0.widthAnchor.constraint(equalTo: $0.heightAnchor),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -60,16 +62,20 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
$0.addSubview(contentVStack)
|
$0.addSubview(contentVStack)
|
||||||
metaIndicatorsView.translatesAutoresizingMaskIntoConstraints = false
|
metaIndicatorsView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
$0.addSubview(metaIndicatorsView)
|
$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([
|
NSLayoutConstraint.activate([
|
||||||
avatarImageView.leadingAnchor.constraint(equalTo: $0.leadingAnchor),
|
avatarImageView.leadingAnchor.constraint(equalTo: $0.leadingAnchor),
|
||||||
avatarImageView.topAnchor.constraint(equalTo: $0.topAnchor),
|
avatarTopConstraint,
|
||||||
contentVStack.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 8),
|
contentVStack.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 8),
|
||||||
contentVStack.trailingAnchor.constraint(equalTo: $0.trailingAnchor),
|
contentVStack.trailingAnchor.constraint(equalTo: $0.trailingAnchor),
|
||||||
contentVStack.topAnchor.constraint(equalTo: $0.topAnchor),
|
contentVStack.topAnchor.constraint(equalTo: $0.topAnchor),
|
||||||
contentVStack.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
|
contentVStack.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
|
||||||
metaIndicatorsView.leadingAnchor.constraint(greaterThanOrEqualTo: $0.leadingAnchor),
|
metaIndicatorsView.leadingAnchor.constraint(greaterThanOrEqualTo: $0.leadingAnchor),
|
||||||
metaIndicatorsView.trailingAnchor.constraint(equalTo: avatarImageView.trailingAnchor),
|
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.tintAdjustmentMode = .normal
|
||||||
$0.setContentHuggingPriority(.defaultHigh, for: .vertical)
|
$0.setContentHuggingPriority(.defaultHigh, for: .vertical)
|
||||||
$0.addTarget(self, action: #selector(collapseButtonPressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(collapseButtonPressed), for: .touchUpInside)
|
||||||
|
$0.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentContainer = StatusContentContainer<StatusContentTextView, StatusPollView>(useTopSpacer: false).configure {
|
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 = mainContainer.topAnchor.constraint(equalTo: timelineReasonHStack.bottomAnchor, constant: 4)
|
||||||
|
mainContainerTopToReblogLabelConstraint.identifier = "MainContainerTopToReblog"
|
||||||
mainContainerTopToSelfConstraint = mainContainer.topAnchor.constraint(equalTo: statusContainer.topAnchor, constant: 8)
|
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
|
mainContainerTopToSelfConstraint.identifier = "MainContainerTopToSelf"
|
||||||
// even though the code never does that; so let this one get broken temporarily
|
|
||||||
mainContainerTopToSelfConstraint.priority = .init(999)
|
|
||||||
mainContainerBottomToActionsConstraint = mainContainer.bottomAnchor.constraint(equalTo: actionsContainer.topAnchor, constant: -4)
|
mainContainerBottomToActionsConstraint = mainContainer.bottomAnchor.constraint(equalTo: actionsContainer.topAnchor, constant: -4)
|
||||||
mainContainerBottomToSelfConstraint = mainContainer.bottomAnchor.constraint(equalTo: statusContainer.bottomAnchor, constant: -6)
|
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([
|
NSLayoutConstraint.activate([
|
||||||
// why is this 4 but the mainContainerTopSelfConstraint constant 8? because this looks more balanced
|
// why is this 4 but the mainContainerTopSelfConstraint constant 8? because this looks more balanced
|
||||||
timelineReasonHStack.topAnchor.constraint(equalTo: statusContainer.topAnchor, constant: 4),
|
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.leadingAnchor.constraint(equalTo: statusContainer.leadingAnchor, constant: 16),
|
||||||
mainContainer.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: -16),
|
mainContainer.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: -16),
|
||||||
|
mainContainerBottomToActionsConstraint,
|
||||||
|
|
||||||
actionsContainer.leadingAnchor.constraint(equalTo: statusContainer.leadingAnchor, constant: 16),
|
actionsContainer.leadingAnchor.constraint(equalTo: statusContainer.leadingAnchor, constant: 16),
|
||||||
actionsContainer.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: -16),
|
actionsContainer.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: -16),
|
||||||
// yes, this is deliberately 6. 4 looks to cramped, 8 looks uneven
|
// yes, this is deliberately 6. 4 looks to cramped, 8 looks uneven
|
||||||
actionsContainer.bottomAnchor.constraint(equalTo: statusContainer.bottomAnchor, constant: -6),
|
actionsContainer.bottomAnchor.constraint(equalTo: statusContainer.bottomAnchor, constant: -6),
|
||||||
|
|
||||||
metaIndicatorsBottomConstraint,
|
metaIndicatorsView.bottomAnchor.constraint(lessThanOrEqualTo: statusContainer.bottomAnchor, constant: -6),
|
||||||
])
|
])
|
||||||
|
|
||||||
updateActionsVisibility()
|
updateActionsVisibility()
|
||||||
|
@ -590,8 +593,14 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineReasonHStack.isHidden = hideTimelineReason
|
timelineReasonHStack.isHidden = hideTimelineReason
|
||||||
mainContainerTopToReblogLabelConstraint.isActive = !hideTimelineReason
|
// do this to make sure the currently active constraint is deactivated first
|
||||||
mainContainerTopToSelfConstraint.isActive = hideTimelineReason
|
if hideTimelineReason {
|
||||||
|
mainContainerTopToReblogLabelConstraint.isActive = false
|
||||||
|
mainContainerTopToSelfConstraint.isActive = true
|
||||||
|
} else {
|
||||||
|
mainContainerTopToSelfConstraint.isActive = false
|
||||||
|
mainContainerTopToReblogLabelConstraint.isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
doUpdateUI(status: status, precomputedContent: precomputedContent)
|
doUpdateUI(status: status, precomputedContent: precomputedContent)
|
||||||
|
|
||||||
|
@ -690,11 +699,11 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateActionsVisibility() {
|
private func updateActionsVisibility() {
|
||||||
if Preferences.shared.hideActionsInTimeline {
|
if Preferences.shared.hideActionsInTimeline && !actionsContainer.isHidden {
|
||||||
actionsContainer.isHidden = true
|
actionsContainer.isHidden = true
|
||||||
mainContainerBottomToSelfConstraint.isActive = true
|
|
||||||
mainContainerBottomToActionsConstraint.isActive = false
|
mainContainerBottomToActionsConstraint.isActive = false
|
||||||
} else {
|
mainContainerBottomToSelfConstraint.isActive = true
|
||||||
|
} else if !Preferences.shared.hideActionsInTimeline && actionsContainer.isHidden {
|
||||||
actionsContainer.isHidden = false
|
actionsContainer.isHidden = false
|
||||||
mainContainerBottomToSelfConstraint.isActive = false
|
mainContainerBottomToSelfConstraint.isActive = false
|
||||||
mainContainerBottomToActionsConstraint.isActive = true
|
mainContainerBottomToActionsConstraint.isActive = true
|
||||||
|
|
Loading…
Reference in New Issue