I have a set of AL constraints positioning a child vc that has two positions, expanded and collapsed.
I found that when I add the collapsed constraint, a top anchor to bottom anchor constraint with a constant, when the vc is first created, there seems to be additional spacing when I activate it. Seemingly because the actual height isn't available at the time.
When I add the constraint in viewDidLayoutSubviews there additional spacing is gone and the constraint behaves properly. Except the issue that now when I switch between the constraints in an animation, I cannot deactivate the collapsed constraint as I switch to the expanded constraint and the constraint breaks. Possibly because viewDidLayoutSubviews is called throughout the transition animation.
Here's an abstract of vc setup.
var foregroundExpandedConstraint: NSLayoutConstraint! var foregroundCollapsedConstraint: NSLayoutConstraint! var foregroundViewController: UIViewController? { didSet { setupforegroundViewController(foregroundViewController: foregroundViewController!) } } func setupforegroundViewController(foregroundViewController: UIViewController) { addChildViewController(foregroundViewController) foregroundViewController.didMove(toParentViewController: self) guard let foregroundView = foregroundViewController.view else { return } foregroundView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(foregroundView) foregroundExpandedConstraint = foregroundView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15) let height = view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15 let cellHeight = ((height) / 6) foregroundCollapsedConstraint = NSLayoutConstraint(item: foregroundView, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: (-cellHeight) * 2 - 50) let foregroundViewControllerViewConstraints = [ foregroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor), foregroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor), foregroundView.heightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.heightAnchor, constant: -50 - 15), foregroundExpandedConstraint! ] NSLayoutConstraint.activate(foregroundViewControllerViewConstraints) } And here the animations are preformed using UIViewPropertyAnimator.
func animateTransitionIfNeeded(state: ForegroundState, duration: TimeInterval) { let containerFrameAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 1) { [unowned self] in switch state { case .expanded: self.foregroundCollapsedConstraint?.isActive = false self.foregroundExpandedConstraint?.isActive = true self.view.layoutIfNeeded() case .collapsed: self.foregroundExpandedConstraint?.isActive = false self.foregroundCollapsedConstraint?.isActive = true self.view.layoutIfNeeded() } } containerFrameAnimator.addCompletion { [weak self] (position) in if position == .start { switch state { case .collapsed: self?.foregroundCollapsedConstraint?.isActive = false self?.foregroundExpandedConstraint?.isActive = true self?.foregroundIsExpanded = true self?.view.layoutIfNeeded() case .expanded: self?.foregroundExpandedConstraint?.isActive = false self?.foregroundCollapsedConstraint?.isActive = true self?.foregroundIsExpanded = false self?.view.layoutIfNeeded() } } else if position == .end { switch state { case .collapsed: self?.foregroundExpandedConstraint?.isActive = false self?.foregroundCollapsedConstraint?.isActive = true self?.foregroundIsExpanded = false case .expanded: self?.foregroundExpandedConstraint?.isActive = false self?.foregroundCollapsedConstraint?.isActive = true self?.foregroundIsExpanded = true } } self?.runningAnimations.removeAll() } Again to reiterate, when I use the following code, setting the constraint as the vc is added to the view hierarchy, it doesn't layout properly. Checking the constraints I see they change after view did layout subviews is called. Each constraint changes appropriately except for the collapsed constraint.
When I add the collapsed constraint in view did layout subviews it behaves properly however I am unable to deactivate it going forwards and the constraint breaks.
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let height = view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15 let cellHeight = ((height) / 6) if let v = foregroundViewController?.view { foregroundCollapsedConstraint = NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: (-cellHeight) * 2 - 50) } } Edit: I've created a repo demonstrating the issue: https://github.com/louiss98/UIViewPropertyAnimator-Layout-Test
Any suggestions?
1 Answers
Answers 1
You can eliminate the "broken" constraint by changing the constant instead of creating a new constraint.
In your viewDidLayoutSubviews() func,
change:
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let height = view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15 let cellHeight = ((height) / 6) foregroundCollapsedConstraint = NSLayoutConstraint(item: testViewController.view, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: (-cellHeight) * 2 - 50) } to:
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let height = view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15 let cellHeight = ((height) / 6) foregroundCollapsedConstraint.constant = (-cellHeight) * 2 - 50 }
0 comments:
Post a Comment