0

Scenario:
1. UIViewContainer having one (1) child UIViewController.
2. I'm using a UIStoryboard.

Goal:
To animate the entrance of the child UIViewController's view from the left edge of the container (like a UIActionSheet).

I have initially set the member view to blue.

Problem: I can correctly animate the physical coordinates but not the width constraint (nor any other constraint).

enter image description here

Here's my code:

import UIKit

class HamburgerContainerViewController: UIViewController {

    @IBOutlet weak var containerView: UIView!
    var memberView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        memberView = containerView!.subviews.first!
    }

    override func viewDidLayoutSubviews() {
        memberView.backgroundColor = UIColor.blue
        memberView.frame = CGRect(x: 0, y: 0, width: 100, height: 400)
        return
    }


    @IBAction func DoSomething(_ sender: Any) {
        memberView.translatesAutoresizingMaskIntoConstraints = false

        UIView.animate(withDuration: 1.0) { 
             // self.memberView.frame.size.width = 345.0    // ... this works.
             self.memberView.widthAnchor.constraint(equalToConstant: 25.0)  // ...this makes the view slide somewhere else.
            self.view.layoutIfNeeded()
        }

    }
}

Running my code causes the blue member view to side to the upper left of the screen leaving its UIButton & UILabel in its wake.

enter image description here

Here's the storyboard: enter image description here

FYI: Here's a list of constraints upon the member UIViewController's view:

enter image description here

Frederick C. Lee
  • 9,019
  • 17
  • 64
  • 105

1 Answers1

0

In answer to your question, you could either animate the constraints of the container view itself, or put a subview within the child view controller's view and animate that. But don't try to animate the frame of the child view controller's root view if that view was created and managed by IB's "container view".

So I added the view to be resized as a subview of the child view controller's view, and then I could resize that. For example, add two constraints (one that is H:[expandingView]| (which I hooked up to an @IBOutlet for wideConstraint and another that is H:[expandingView(100@750)] that I hooked up to and @IBOutlet called narrowConstraint. Then the child view controller can do:

class ChildViewController: UIViewController {

    @IBOutlet var narrowConstraint: NSLayoutConstraint!
    @IBOutlet var wideConstraint: NSLayoutConstraint!

    @IBOutlet weak var expandingView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        wideConstraint.isActive = false  // disable the wide one when the child is first loaded
    }

    // when I want to, I just toggle the `isActive` for the two constraints

    func toggle() {
        narrowConstraint.isActive = !narrowConstraint.isActive
        wideConstraint.isActive   = !wideConstraint.isActive
        UIView.animate(withDuration: 0.5) {
            self.view.layoutIfNeeded()
        }
    }
}

Then the parent can tell the child to toggle the size of the expandingView when the button is tapped:

@IBAction func didTapButton(_ sender: Any) {
    if let child = childViewControllers.first as? ChildViewController {
        child.toggle()
    }
}

That yields:

enter image description here

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks for the great explanation. Could I merely animate the frame if I first deactivate all relevant constraints? – Frederick C. Lee May 31 '17 at 02:52
  • Technically that's right, but if you're just animating it like above, you only need to deactivate one constraint and activate another. In fact, often you don't even need to do any activating/deactivating, but instead just change one constraint's `constant`. You just need to make sure the constraints are unambiguous before and after. If you deactivate too many, it could be ambiguous unless you activate others that remove any ambiguity. – Rob May 31 '17 at 04:35