An iOS 10 UIKit Dynamics Tutorial

PreviousTable of ContentsNext
iOS 10 UIKit Dynamics – An OverviewAn Introduction to iOS 10 Sprite Kit Game Programming


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


With the basics of UIKit Dynamics covered in the previous chapter, this chapter will take this knowledge and apply it to the creation of an example application designed to show UIKit Dynamics in action. The example application created in this chapter will make use of the gravity, collision, elasticity and attachment features in conjunction with touch handling to demonstrate how these key features are implemented.

Creating the UIKit Dynamics Example Project

Begin by launching Xcode and selecting the option to create a new project from the welcome screen (or select the File -> New -> Project… menu option if the welcome screen is not visible). From the resulting template selection screen, choose the Single View Application option before clicking Next to proceed. Enter UIKitDynamics into the Product Name field and select Swift and Universal from the Language and Devices fields respectively. Click on Next once again and, in the file selection screen, navigate to a suitable location for the project files before clicking on Create.

Adding the Dynamic Items

The user interface for the application is going to consist of two view objects which will be drawn in the form of squares colored blue and red respectively. The first step in the tutorial, therefore, is to implement the code to create and draw these views. Within the project navigator panel, locate and select the ViewController.swift file and add variables for these two views so that the file reads as follows:

import UIKit

class ViewController: UIViewController {

    var blueBoxView: UIView?
    var redBoxView: UIView?

With the references declared, select the ViewController.swift file and add code to the viewDidLoad method to draw the views, color them appropriately and then add them to the parent view so that they appear within the user interface:

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

override func viewDidLoad() {
    super.viewDidLoad()

    var frameRect = CGRect(x: 10, y: 20, width: 80, height: 80)
    blueBoxView = UIView(frame: frameRect)
    blueBoxView?.backgroundColor = UIColor.blue

    frameRect = CGRect(x: 150, y: 20, width: 60, height: 60)
    redBoxView = UIView(frame: frameRect)
    redBoxView?.backgroundColor = UIColor.red

    self.view.addSubview(blueBoxView!)
    self.view.addSubview(redBoxView!)
}

Perform a test run of the application on either a simulator or physical iOS device and verify that the new views appear as expected within the user interface (Figure 66-1):


An iOS 10 UIKit Dynamics example app

Figure 66-1


Creating the Dynamic Animator Instance

As outlined in the previous chapter, a key element in implementing UIKit Dynamics is an instance of the UIDynamicAnimator class. Select the ViewController.swift file and add an instance variable for a UIDynamicAnimator object within the application code:

import UIKit

class ViewController: UIViewController {

    var blueBoxView: UIView?
    var redBoxView: UIView?
    var animator: UIDynamicAnimator?

Next, modify the viewDidLoad method within the ViewController.swift file once again to add code to create and initialize the instance, noting that the top level view of the view controller is passed through as the reference view:

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

override func viewDidLoad() {
    super.viewDidLoad()

    var frameRect = CGRect(x: 10, y: 20, width: 80, height: 80)
    blueBoxView = UIView(frame: frameRect)
    blueBoxView?.backgroundColor = UIColor.blue

    frameRect = CGRect(x: 150, y: 20, width: 60, height: 60)
    redBoxView = UIView(frame: frameRect)
    redBoxView?.backgroundColor = UIColor.red

    self.view.addSubview(blueBoxView!)
    self.view.addSubview(redBoxView!)

    animator = UIDynamicAnimator(referenceView: self.view)
}

With the dynamic items added to the user interface and an instance of the dynamic animator created and initialized, it is now time to begin creating dynamic behavior instances.

Adding Gravity to the Views

The first behavior to be added to the example application is going to be gravity. For the purposes of this tutorial, gravity will be added to both views such that a force of gravity of 1.0 UIKit Newton is applied directly downwards along the y axis of the parent view. To achieve this, the viewDidLoad method needs to be further modified to create a suitably configured instance of the UIGravityBehavior class and to add that instance to the dynamic animator:

override func viewDidLoad() {
    super.viewDidLoad()

    var frameRect = CGRect(x: 10, y: 20, width: 80, height: 80)
    blueBoxView = UIView(frame: frameRect)
    blueBoxView?.backgroundColor = UIColor.blue

    frameRect = CGRect(x: 150, y: 20, width: 60, height: 60)
    redBoxView = UIView(frame: frameRect)
    redBoxView?.backgroundColor = UIColor.red

    self.view.addSubview(blueBoxView!)
    self.view.addSubview(redBoxView!)

    animator = UIDynamicAnimator(referenceView: self.view)

    let gravity = UIGravityBehavior(items: [blueBoxView!,
                                                redBoxView!])
    let vector = CGVector(dx: 0.0, dy: 1.0)
    gravity.gravityDirection = vector

    animator?.addBehavior(gravity)

}

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Compile and run the application once again. Note that after launching, the gravity behavior causes the views to fall from the top of the reference view and out of view at the bottom of the device display. In order to keep the views within the bounds of the reference view, it is necessary to set up a collision behavior.

Implementing Collision Behavior

In terms of collision behavior, the example requires that collisions occur both when the views impact each other and when making contact with the boundaries of the reference view. With these requirements in mind, the collision behavior needs to be implemented as follows:

override func viewDidLoad() {
    super.viewDidLoad()

    var frameRect = CGRect(x: 10, y: 20, width: 80, height: 80)
    blueBoxView = UIView(frame: frameRect)
    blueBoxView?.backgroundColor = UIColor.blue

    frameRect = CGRect(x: 150, y: 20, width: 60, height: 60)
    redBoxView = UIView(frame: frameRect)
    redBoxView?.backgroundColor = UIColor.red

    self.view.addSubview(blueBoxView!)
    self.view.addSubview(redBoxView!)

    animator = UIDynamicAnimator(referenceView: self.view)

    let gravity = UIGravityBehavior(items: [blueBoxView!,
                                                        redBoxView!])
    let vector = CGVector(dx: 0.0, dy: 1.0)
    gravity.gravityDirection = vector

    let collision = UICollisionBehavior(items: [blueBoxView!,
                                                        redBoxView!])
    collision.translatesReferenceBoundsIntoBoundary = true

    animator?.addBehavior(collision)
    animator?.addBehavior(gravity)

}

Running the application should now cause the views to stop at the bottom edge of the reference view and bounce slightly after impact. The amount by which the views bounce in the event of a collision can be changed by creating a UIDynamicBehavior class instance and changing the elasticity property. The following code, for example, changes the elasticity of the blue box view so that it bounces to a higher degree than the red box:

override func viewDidLoad() {
    super.viewDidLoad()
.
.
.
    let collision = UICollisionBehavior(items: [blueBoxView!, 
							redBoxView!])
    collision.translatesReferenceBoundsIntoBoundary = true

    let behavior = UIDynamicItemBehavior(items: [blueBoxView!])
    behavior.elasticity = 0.5

    animator?.addBehavior(behavior)
    animator?.addBehavior(collision)
    animator?.addBehavior(gravity)
}

Attaching a View to an Anchor Point

So far in this tutorial we have added some behavior to the application but have not yet implemented any functionality that connects UIKit Dynamics to user interaction. In this section, however, the example will be modified such that an attachment is created between the blue box view and the point of contact of a touch on the screen. This anchor point will be continually updated as the user’s touch moves across the screen, thereby causing the blue box to follow the anchor point. The first step in this process is to declare within the ViewController.swift file some instance variables within which to store both the current location of the anchor point and a reference to a UIAttachmentBehavior instance:

import UIKit

class ViewController: UIViewController {

    var blueBoxView: UIView?
    var redBoxView: UIView?
    var animator: UIDynamicAnimator?
    var currentLocation: CGPoint?
    var attachment: UIAttachmentBehavior? 

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

As outlined in the chapter entitled An Overview of iOS 10 Multitouch, Taps and Gestures, touches can be detected by overriding the touchesBegan, touchesMoved and touchesEnded methods. The touchesBegan method in the ViewController.swift file now needs to be implemented to obtain the coordinates of the touch and to add an attachment behavior between that location and the blue box view to the animator instance:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    if let theTouch = touches.first {
        currentLocation = theTouch.location(in: self.view)

        attachment = UIAttachmentBehavior(item: blueBoxView!,
                                attachedToAnchor: currentLocation!)

        animator?.addBehavior(attachment!)
    }
}

As the touch moves around within the reference view, the anchorPoint property of the attachment behavior needs to be modified to track the motion. This involves overriding the touchesMoved method as follows:

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let theTouch = touches.first {

        currentLocation = theTouch.location(in: self.view)
        attachment?.anchorPoint = currentLocation!
    }
}

Finally, when the touch ends, the attachment needs to be removed so that the view will be pulled down to the bottom of the reference view by the previously defined gravity behavior. Remaining within the ViewController.swift file, implement the touchesEnded method as follows:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    animator?.removeBehavior(attachment!)
}

Compile and run the application and touch the display. As the touch is moved, note that the blue box view moves as though tethered to the touch point. Move the touch such that the blue and red boxes collide and observe that the red box will move in response to the collision while the blue box will rotate on the attachment point as illustrated in Figure 66-2:


An iOS 10 UIKit Dynamics Anchor Point in action

Figure 66-2


Release the touch and note that gravity causes the blue box to fall once again and settle at the bottom edge of the reference view.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

The code that creates the attachment currently attaches to the center point of the blue box view. Modify the touchesBegan method to adjust the attachment point so that it is off center:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    if let theTouch = touches.first {
        currentLocation = theTouch.location(in: self.view)
        let offset = UIOffsetMake(20, 20)
        attachment = UIAttachmentBehavior(item: blueBoxView!,
				offsetFromCenter: offset,  
				attachedToAnchor: currentLocation!)

        animator?.addBehavior(attachment!)
    }
}

When the blue box view is now suspended by the anchor point attachment, it will tilt in accordance with the offset attachment point.

Implementing a Spring Attachment Between two Views

The final step in this tutorial is to attach the two views together using a spring-style attachment. All that this involves is a few lines of code within the viewDidLoad method to create the attachment behavior, set the frequency and damping values to create the springing effect and then add the behavior to the animator instance:

override func viewDidLoad() {
    super.viewDidLoad()
.
.
.
    let behavior = UIDynamicItemBehavior(items: [blueBoxView!, 
							redBoxView!])
    behavior.elasticity = 0.5

    let boxAttachment = UIAttachmentBehavior(item: blueBoxView!,
                                       attachedTo: redBoxView!)
    boxAttachment.frequency = 4.0
    boxAttachment.damping = 0.0

    animator?.addBehavior(boxAttachment)
    animator?.addBehavior(behavior)
    animator?.addBehavior(collision)
    animator?.addBehavior(gravity)
}

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

When the application is now run, the red box will move in relation to the blue box as though connected by a spring (Figure 66 3). The views will even spring apart when pushed together before the touch is released.


An iOS 10 UIKit Dynamics example app running


Figure 66-3


Summary

The example created in this chapter has demonstrated the steps involved in implementing UIKit Dynamics within an iOS 10 application in the form of gravity, collision and attachment behaviors. Perhaps the most remarkable fact about the animation functionality implemented in this tutorial is that it was achieved in approximately 40 lines of UIKit Dynamics code, a fraction of the amount of code that would have been required to implement such behavior in the absence of UIKit Dynamics.



Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
iOS 10 UIKit Dynamics – An OverviewAn Introduction to iOS 10 Sprite Kit Game Programming