Collision Detection with UIKit Dynamics in iOS 7

With UIKit Dynamics you can specify collision behaviours to your objects. The dynamic items can collide with each other and/or any boundary you specify. In this tutorial we will create some custom boundaries and randomly let some squares fall down on to these boundaries.

Update: Apr 20, 2017. The rewritten version in Swift for iOS 10.3 and Xcode 8.3 is available here.

Open Xcode and create a new Single View Application. For product name, use iOS7CollisionDetectionTutorial and then fill out the Organization Name, Company Identifier and Class Prefix fields with your customary values. Make sure only iPhone is selected in Devices.

In this tutorial we will create custom boundaries using UIKit Dynamics. Take a look for a introduction to UIKit Dynamics in our previous tutorial

We will draw some lines using Core Graphics so we need to create a custom UIView, so we can alter the drawRect method. Create a new Objective-C class and make it a subclass of UIView. Name the class MyView. To draw the lines we will create a helper function drawLineFromPoint:toPoint:pointY

- (void)drawLineFromPoint:(CGFloat)fromX toPoint:(CGFloat)toX pointY:(CGFloat)y
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(currentContext, fromX, y);
CGContextAddLineToPoint(currentContext, toX, y);

The lines will be drawn with a width of 5 points. Next, we will alter the drawRect function

- (void)drawRect:(CGRect)rect
[self drawLineFromPoint:0 toPoint:self.bounds.size.width/3 pointY:self.bounds.size.height - 100.0f];
[self drawLineFromPoint:self.bounds.size.width/3 toPoint:self.bounds.size.width*0.67 pointY:self.bounds.size.height - 150.0f];
[self drawLineFromPoint:self.bounds.size.width*0.67 toPoint:self.bounds.size.width pointY:self.bounds.size.height - 100.0f];

We make three calls to our helper function creating three lines of the same width with different heights. We need to connect our MyView to the view controller. Go to the storyboard and select the view. Go to the Identity Inspector and change the class to MyView.

Build and Run, the view should look like this.

Next, drag a button to the storyboard and give it a title of "Next". The main view should look like this.

Select the assistant editor.Ctrl and drag from the button to the @interface section of ViewControlller.m and create the following Action.

In ViewController.m we need to declare some properties to keep track of our views. Add the following properties in the interface section

@property (nonatomic, strong) NSMutableArray *squareViews;
@property (nonatomic, strong) UIDynamicAnimator *animator;
@property (nonatomic, strong) NSArray *colors;
@property (nonatomic, strong) NSArray *centerPoint;
@property (nonatomic) CGSize sizeOfSquare;

The squareViews propery will contain our views. We need the colors array, the centerPoint array and the sizeOfSquare property  to allocate them to our views later on. The animator property is needed to dynamically allocate our behaviours later. Next add the following properties

@property (nonatomic) float leftBoundaryHeight;
@property (nonatomic) float middleBoundaryHeight;
@property (nonatomic) float rightBoundaryHeight;
@property (nonatomic) float leftBoundaryWidth;
@property (nonatomic) float middleBoundaryWidth;
@property (nonatomic) float leftSquareCenterPointX;
@property (nonatomic) float middleSquareCenterPointX;
@property (nonatomic) float rightSquareCenterPointX;
@property (nonatomic) float squareCenterPointY;

These properties are needed to set our custom boundaries and add a starting point for our squares. First, create a helper method setBoundaryValues to set these properties.

- (void)setBoundaryValues
self.leftBoundaryHeight = self.view.bounds.size.height - 100.0f;
self.middleBoundaryHeight = self.view.bounds.size.height - 150.0f;
self.rightBoundaryHeight = self.view.bounds.size.height - 100.0f;
self.leftBoundaryWidth = self.view.bounds.size.width/3;
self.middleBoundaryWidth = self.view.bounds.size.width * 0.67;
self.leftSquareCenterPointX = self.view.bounds.size.width/6;
self.middleSquareCenterPointX = self.view.bounds.size.width/2;
self.rightSquareCenterPointX = self.view.bounds.size.width * 0.84;
self.squareCenterPointY = self.view.bounds.size.height - 400;

In viewDidLoad, we will call our helper method and set the remainder of the square values.

- (void)viewDidLoad
[super viewDidLoad];

[self setBoundaryValues];

// Create the colors array
self.colors = @[[UIColor redColor], [UIColor blueColor], [UIColor greenColor], [UIColor purpleColor], [UIColor grayColor]];

// Create the centerpoint of our squares
CGPoint leftCenterPoint = CGPointMake(self.leftSquareCenterPointX, self.squareCenterPointY);
CGPoint middleCenterPoint = CGPointMake(self.middleSquareCenterPointX, self.squareCenterPointY);
CGPoint rightCenterPoint = CGPointMake(self.rightSquareCenterPointX, self.squareCenterPointY);
self.centerPoint = @[[NSValue valueWithCGPoint:leftCenterPoint],[NSValue valueWithCGPoint:middleCenterPoint],[NSValue valueWithCGPoint:rightCenterPoint]];

// set the size of our squares
self.sizeOfSquare = CGSizeMake(50.0f, 50.0f);

// initialize squareview array
self.squareViews = [[NSMutableArray alloc] initWithCapacity:100];

So, every view has a size of 50 and can be of 5 different colors. The main flow of our project happens in our releaseNextSquare IBAction method. Add the following lines in this method

UIView *newView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.sizeOfSquare.width, self.sizeOfSquare.height)];

int randomColorIndex = arc4random() %5;
newView.backgroundColor = self.colors[randomColorIndex];

int randomCenterPoint = arc4random() %3; = [self.centerPoint[randomCenterPoint] CGPointValue];

[self.squareViews addObject:newView];
[self.view addSubview:newView];

A view is created and a random centerpoint and color is assigned. The new view is added to the main view and to our array. Add the remainder of the lines

self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

// create gravity
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:self.squareViews];
[self.animator addBehavior:gravity];

// create collision detection
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:self.squareViews];

// set collision boundaries
[collision addBoundaryWithIdentifier:@"leftBoundary" fromPoint:CGPointMake(0.0f, self.leftBoundaryHeight) toPoint:CGPointMake(self.leftBoundaryWidth, self.leftBoundaryHeight)];
[collision addBoundaryWithIdentifier:@"middleBoundary" fromPoint:CGPointMake(self.view.bounds.size.width/3, self.middleBoundaryHeight) toPoint:CGPointMake(self.middleBoundaryWidth, self.middleBoundaryHeight)];
[collision addBoundaryWithIdentifier:@"rightBoundary" fromPoint:CGPointMake(self.middleBoundaryWidth, self.rightBoundaryHeight) toPoint:CGPointMake(self.view.bounds.size.width, self.rightBoundaryHeight)];

collision.collisionMode = UICollisionBehaviorModeEverything;

[self.animator addBehavior:collision];

First, we add a gravity behavior to our animator to let our squares fall. Next, a collision behaviour is added with our custom boundaries. This behaviour is also added to our animator. The default collision mode of UICollisionBehaviour is UICollisionModeBehaviourEverything, which means that all items and boundary can collide with each other. Build and Run, and keep pressing the Next button to let the squares fall down.

You can download the source code of the iOS7CollisionDetectionTutorial at the ioscreator repository on github.