diff --git a/Tusker/Views/Status/StatusContentContainer.swift b/Tusker/Views/Status/StatusContentContainer.swift index 9f41e7ea..c6f47df2 100644 --- a/Tusker/Views/Status/StatusContentContainer.swift +++ b/Tusker/Views/Status/StatusContentContainer.swift @@ -30,48 +30,84 @@ class StatusContentContainer: UIView { let pollView = StatusPollView() - private var lastSubviewBottomConstraint: NSLayoutConstraint! + private var arrangedSubviews: [UIView] { + [contentTextView, cardView, attachmentsView, pollView] + } + + private var isHiddenObservations: [NSKeyValueObservation] = [] + + private var verticalConstraints: [NSLayoutConstraint] = [] + private var lastSubviewBottomConstraint: NSLayoutConstraint? private var zeroHeightConstraint: NSLayoutConstraint! override init(frame: CGRect) { super.init(frame: frame) - let subviews = [contentTextView, cardView, attachmentsView, pollView] - for (index, subview) in subviews.enumerated() { + for subview in arrangedSubviews { subview.translatesAutoresizingMaskIntoConstraints = false addSubview(subview) - let topConstraint: NSLayoutConstraint - if index == 0 { - topConstraint = subview.topAnchor.constraint(equalTo: topAnchor) - } else { - topConstraint = subview.topAnchor.constraint(equalTo: subviews[index - 1].bottomAnchor, constant: 4) - } - NSLayoutConstraint.activate([ - topConstraint, subview.leadingAnchor.constraint(equalTo: leadingAnchor), subview.trailingAnchor.constraint(equalTo: trailingAnchor), ]) } - // these constraints need to have low priority so that during the collapse/expand animation, the content container is the view that shrinks/expands - lastSubviewBottomConstraint = subviews.last!.bottomAnchor.constraint(equalTo: bottomAnchor) - lastSubviewBottomConstraint.isActive = true - lastSubviewBottomConstraint.priority = .defaultLow + // this constraint needs to have low priority so that during the collapse/expand animation, the content container is the view that shrinks/expands zeroHeightConstraint = heightAnchor.constraint(equalToConstant: 0) zeroHeightConstraint.priority = .defaultLow + setNeedsUpdateConstraints() + // mask to bounds so that the during the expand/collapse animation, subviews are clipped layer.masksToBounds = true + + isHiddenObservations = arrangedSubviews.map { + $0.observe(\.isHidden) { [unowned self] _, _ in + self.setNeedsUpdateConstraints() + } + } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + override func updateConstraints() { + NSLayoutConstraint.deactivate(verticalConstraints) + verticalConstraints = [] + var lastVisibleSubview: UIView? + + for subview in arrangedSubviews { + guard !subview.isHidden else { + continue + } + + 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 + // 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 = true + lastSubviewBottomConstraint!.priority = .defaultLow + + super.updateConstraints() + } + func setCollapsed(_ collapsed: Bool) { - lastSubviewBottomConstraint.isActive = !collapsed + // ensure that we have a lastSubviewBottomConstraint + updateConstraintsIfNeeded() + // force unwrap because the content container should always have at least one view + lastSubviewBottomConstraint!.isActive = !collapsed zeroHeightConstraint.isActive = collapsed }