Updating Constraints Dynamically

The animated effects in IOS apps you probably took for granted

yiuwin
yiuwin
Sep 8, 2018 · 4 min read

The “animation” you see below is — rather anticlimactically — the effect of simultaneously inserting time stamps into a TableView, increasing the TableView’s height, and repositioning its relative Views.

More technically, let’s start by talking about constraints. A constraint is ultimately a linear expression that can either be set up using Xcode’s Interface Builder or written programmatically. When the values of this expression changes, or needs to be changed or removed, the system needs to be notified of the change. This is because the initial setup of the constraints are only done in the View Controller’s viewDidLoad method (run once in the View Controller’s lifetime) and will stay that way unless it is told otherwise. Thus, when a constraint change is made in your code, a layout pass is scheduled or forced immediately depending on the method you call.

1. Determine which constraints you will be changing

Changing a constraint means you must first have a handle of the constraint that is accessible to the function in which you will be making the change (in our case, we want our function addTimeStamp() to have access to this constraint handle). This usually means setting a variable in the global scope of your View Controller class. We will be updating our tableView’s height, so set a variable of type NSLayoutConstraint to nil. Add as many constraint handles as you will be manipulating.

var tableViewHeight: NSLayoutConstraint? = nil

2. Initial setup — viewDidLoad()

In your initial setup, take into account the changes that can happen to your View. When the updates take place, how will the Views around it lay out? For example, we want to make sure that our “Add Stamp” button does not stay fixed to the top of our View and cover our tableView.

let views = [
"view" : backgroundView,
"tableView": tableView,
"btn": btn
]

NSLayoutConstraint.activate(
NSLayoutConstraint.constraints(withVisualFormat: "H:|-15-[view]-15-|", options: [], metrics: nil, views: views) +
NSLayoutConstraint.constraints(withVisualFormat: "V:|-80-[view]-15-[btn(30)]", options: [], metrics: nil, views: views) +
NSLayoutConstraint.constraints(withVisualFormat: "H:|-15-[btn]-15-|", options: [], metrics: nil, views: views) +

NSLayoutConstraint.constraints(withVisualFormat: "H:|-15-[tableView]-15-|", options: [], metrics: nil, views: views) +
NSLayoutConstraint.constraints(withVisualFormat: "V:|[tableView]|", options: [], metrics: nil, views: views)
)
tableViewHeight = NSLayoutConstraint(item: tableView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: CGFloat(rowHeight*stamps.count))
view.addConstraint(tableViewHeight!)

3. The add button — WHERE THE MAGIC HAPPENS

Initialize your UIButton as follows so that it calls the function addTimeStamp() when tapped:

The addTimeStamp() function handles the animation block which updates our predefined tableViewHeight constraint over a duration of 0.7 seconds.

func addTimeStamp(_ sender : UIButton) {// Create the Time Stamp string
let date = Date()
let formatter = ISO8601DateFormatter()
stamps.append(formatter.string(from: date)) // append the time stamp string to our stamps array
tableView.reloadData() // update the tableView with the new stamps array

self.view.layoutIfNeeded() // make sure previously queued changes are completed

UIView.animate(withDuration: 0.7, animations: {
self.tableViewHeight?.constant = CGFloat(self.rowHeight*self.stamps.count)
self.view.layoutIfNeeded()
})

}

Choosing your update method

Two primary methods exist for the purpose of updating the layout of your View: layoutIfNeeded() and setNeededLayout(). Counterintuitive to their names, layoutIfNeeded is executed immediately, while setNeededLayout() queues the update for the main loop’s next update cycle. For this reason, the constraint update using the setNeededLayout() method will not appear animated even though it is inside an animate block.

layoutIfNeeded() Simplified Sequence Diagram
setNeededLayout() Simplified Sequence Diagram

Final result

You can find the complete code for this example here.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade