An iOS 17 UIKit Dynamics Tutorial

With the basics of UIKit Dynamics covered in the previous chapter, this chapter will apply this knowledge to create an example app designed to show UIKit Dynamics in action. The example app created in this chapter will use 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

Launch Xcode and create a new project using the iOS App template with the Swift and Storyboard options selected, entering UIKitDynamics as the product name.

Adding the Dynamic Items

The app’s user interface will consist of two view objects drawn as squares colored blue and red, respectively. Therefore, the first step in the tutorial 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?Code language: Swift (swift)

With the references declared, select the ViewController.swift file, add a new method (and call it from 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:

override func viewDidLoad() {
    super.viewDidLoad()
    initViews()
}

func initViews() {

    var frameRect = CGRect(x: 10, y: 50, width: 80, height: 80)
    blueBoxView = UIView(frame: frameRect)
    blueBoxView?.backgroundColor = UIColor.blue
    
    frameRect = CGRect(x: 150, y: 50, width: 60, height: 60)
    redBoxView = UIView(frame: frameRect)
    redBoxView?.backgroundColor = UIColor.red
    
    if let blueBox = blueBoxView, let redBox = redBoxView {
        self.view.addSubview(blueBox)
        self.view.addSubview(redBox)
    }
}Code language: Swift (swift)

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

 

You are reading a sample chapter from Building iOS 17 Apps using Xcode Storyboards.

Buy the full book now in eBook or Print format.

The full book contains 96 chapters and 760 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

Figure 63-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 app code:

import UIKit

class ViewController: UIViewController {

    var blueBoxView: UIView?
    var redBoxView: UIView?
    var animator: UIDynamicAnimator?Code language: Swift (swift)

Next, modify the initViews 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:

Code language: Swift (swift)

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

Adding Gravity to the Views

The first behavior to be added to the example app will be gravity. For 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 initViews 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:

func initViews() {
    
    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
    
    if let blueBox = blueBoxView, let redBox = redBoxView {
        self.view.addSubview(blueBox)
        self.view.addSubview(redBox)
        
        animator = UIDynamicAnimator(referenceView: self.view)
        
        let gravity = UIGravityBehavior(items: [blueBox,
                                                redBox])
        let vector = CGVector(dx: 0.0, dy: 1.0)
        gravity.gravityDirection = vector
        
        animator?.addBehavior(gravity)
    }
}Code language: Swift (swift)

Compile and run the app 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. To keep the views within the bounds of the reference view, we need to set up a collision behavior.

 

You are reading a sample chapter from Building iOS 17 Apps using Xcode Storyboards.

Buy the full book now in eBook or Print format.

The full book contains 96 chapters and 760 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

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:

func initViews() {
    
    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
    
    if let blueBox = blueBoxView, let redBox = redBoxView {
        self.view.addSubview(blueBox)
        self.view.addSubview(redBox)
        
        animator = UIDynamicAnimator(referenceView: self.view)
        
        let gravity = UIGravityBehavior(items: [blueBox,
                                                redBox])
        let vector = CGVector(dx: 0.0, dy: 1.0)
        gravity.gravityDirection = vector
        
        let collision = UICollisionBehavior(items: [blueBox,
                                                    redBox])
        
        collision.translatesReferenceBoundsIntoBoundary = true
        
        animator?.addBehavior(collision)
        animator?.addBehavior(gravity)
    }
}Code language: Swift (swift)

Running the app 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:

func initViews() {
.
.
.        
        collision.translatesReferenceBoundsIntoBoundary = true

        let behavior = UIDynamicItemBehavior(items: [blueBox])
        behavior.elasticity = 0.5
        
        animator?.addBehavior(behavior)
        animator?.addBehavior(collision)
        animator?.addBehavior(gravity)
    }
}Code language: Swift (swift)

Attaching a View to an Anchor Point

So far in this tutorial, we have added some behavior to the app but have not yet implemented any functionality that connects UIKit Dynamics to user interaction. In this section, however, the example will be modified to create an attachment 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?Code language: Swift (swift)

As outlined in the chapter entitled An Overview of iOS 17 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:

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:

 

You are reading a sample chapter from Building iOS 17 Apps using Xcode Storyboards.

Buy the full book now in eBook or Print format.

The full book contains 96 chapters and 760 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let theTouch = touches.first {
        
        currentLocation = theTouch.location(in: self.view)
        
        if let location = currentLocation {
            attachment?.anchorPoint = location
        }
    }
}Code language: Swift (swift)

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?) {
    
    if let attach = attachment {
        animator?.removeBehavior(attach)
    }
}Code language: Swift (swift)

Compile and run the app and touch the display. As the touch moves, 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 63-2:

Figure 63-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.

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, let blueBox = blueBoxView {
        
        currentLocation = theTouch.location(in: self.view) as CGPoint?
        
        if let location = currentLocation {
            let offset = UIOffset(horizontal: 20, vertical: 20)
            attachment = UIAttachmentBehavior(item: blueBox,
                                        offsetFromCenter: offset,
                                          attachedToAnchor: location)
        }
        
        if let attach = attachment {
            animator?.addBehavior(attach)
        }
    }
}Code language: Swift (swift)

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

 

You are reading a sample chapter from Building iOS 17 Apps using Xcode Storyboards.

Buy the full book now in eBook or Print format.

The full book contains 96 chapters and 760 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

Implementing a Spring Attachment Between two Views

The final step in this tutorial is to attach the two views 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:

func initViews() {
.
.
        let behavior = UIDynamicItemBehavior(items: [blueBox])
        behavior.elasticity = 0.5
        
        let boxAttachment = UIAttachmentBehavior(item: blueBox,
                                                 attachedTo: redBox)
        boxAttachment.frequency = 4.0
        boxAttachment.damping = 0.0
        
        animator?.addBehavior(boxAttachment)

        animator?.addBehavior(behavior)
        animator?.addBehavior(collision)
        animator?.addBehavior(gravity)
    }
}Code language: Swift (swift)

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

Figure 63-3

Summary

The example created in this chapter has demonstrated the steps involved in implementing UIKit Dynamics within an iOS app 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.


Categories