Keeping Things Simple: The Dynamic Box Model

6 min read

As app developers, solving problems is what we do, but often the approach is just as important as the solution. Today, we are going to run through an example of how a relatively complex problem can be solved by ‘thinking simple’, giving you a self-working, generic solution.

The problem

Whilst working on a project for a large UK newspaper, I noticed one of the developers there had spent several long hours trying to reproduce the icon layout provided by the iPhone and iPad, specifically the reordering of items. Upon showing me the code, he proceeded to run through a large amount of convoluted code, nested IF statements and specific logic to handle single item dragging as well as dual item dragging. He still hadn’t finished it and it didn’t work too well. I made the bold statement that I could start from scratch and in 30 minutes would have a simple, generic solution that worked with any number of fingers you could physically fit on the screen. This is the result.

Building Blocks

First things first, we need some things to move around, normally these would be icons or photos etc, but for the purposes of keeping things generic, we’ll call them boxes. We start by modeling our box, a UIView descendent, which needs to know when it is being touched, dragged and dropped. To handle this we override the following methods in UIView…

[objc] – (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
– (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
[/objc]

 

For the touches began and the touches moved we simply want to make our box follow the user’s touch so they can drag it around. As we are using the same update from both methods we create an update method as follows…

[objc] – (void) updatePosition:(NSSet *)touches {
CGPoint position = [[touches anyObject] locationInView:self.superview];

 

CGRect frame = [self frame];
frame.origin = position;
[self setFrame:frame];
[self.superview bringSubviewToFront:self];

[delegate box:self movedToPosition:position];
}
[/objc]

 

This simply finds the touch position in the boxes superview and moves the box to that position. It then brings the box to the front (so we can drag boxes over the top of static ones) and tells it’s delegate that it has moved to a new position in the view.

If you are unfamiliar with delegates and how they are used, check out the Apple documentation here

 

Whilst we are in the box class, the only other method we need is a method to determine if a box being dragged is within our box. We do this by simply having a method which takes a CGPoint and returns TRUE if the point is within the box and FALSE if it is outside it.

[objc] – (BOOL) containsPoint:(CGPoint)point {
return CGRectContainsPoint([self frame], point);
}
[/objc]

Let’s start building!

In our view controller, we need to achieve the following things:

  1. Layout our boxes in a grid pattern within our view
  2. Respond when the boxes are dragged and re-arrange the remaining boxes accordingly
  3. When a box is released, ensure it takes its new position and arrange the other boxes around it

The first point is easy enough to achieve, right? But first of all, we need to add some boxes to the layout! In our controller, we maintain an NSMutableArray, and this is where the self-working nature comes into play as we’ll discuss later, but for now in the viewDidLoad method we fill this array…

[objc] for(int i = 0; i < kBoxCount; i++) {
LOBox *box = [[LOBox alloc] initWithFrame:CGRectMake(0, 0, kBoxDimension, kBoxDimension)];
[box setBackgroundColor:[self randomColor]];
[self.view addSubview:box];
[box setDelegate:self];
[boxes addObject:box];
[box release];
}
[/objc]

 

We have compiler defines (to keep things clear and configurable from one place…) which define the number of boxes to use as well as the dimension of the box; these are true square boxes so we only need one dimension!

Now we have an array of boxes, we need to lay them out. We have a layout method specifically for this, as we need to call it during the viewDidLoad and also when boxes are moved. We provide a row count to determine how many boxes to fit on a row, but you could always work this out based on the screen width and the box width (plus spacing…)

[objc] – (void) layoutBoxesWithRowCount:(NSInteger)rowCount {
double xPos = kBoxSpacer;
double yPos = kBoxSpacer;
int boxCount = 0;

 

for(LOBox *box in boxes) {
CGRect frame = [box frame];
frame.origin.x = xPos;
frame.origin.y = yPos;
[box setFrame:frame];

xPos += kBoxDimension + kBoxSpacer;
boxCount++;

if(boxCount == rowCount) {
boxCount = 0;
xPos = kBoxSpacer;
yPos += kBoxDimension + kBoxSpacer;
}
}
}
[/objc]

 

What’s so good about this approach you ask?

Notice the heavy use of compiler defines and nicely named variables? This helps keep our code readable and manageable. Magic numbers (hardcoded values in the code with no discernible meaning or definition) will simply lead to confusion. Using numbers instead of these defines means that if we return to the code 6 months down the line we may have no idea where this 2 or that 5 comes from.

Don’t forget, the boxes are all in an array, with no knowledge of how they are laid out. As far as the box in concerned, it doesn’t care where it is, the controller will lay out all of the items in the array as appropriate. This will become more important later on when we start moving the boxes around. Which brings us onto….

Dragging a box around

So we now have a screen full of lovely boxes. But it’s all very static and boring! The next task is to make it interactive. To achieve this we need to be able to do a few things first. First, we need to know when a box is being touched and secondly respond to the movement of a box as it is being dragged. Remember the delegate we had on the box itself, the one that it notified when it is moving, well that delegate is our controller. As the box is moved, our controller will be notified that the box has moved and to which CGPoint it has moved to. So the first one is done.

By utilising this delegate approach the controller doesn’t need to keep track of the boxes and multiple touches. It can sit back and wait for a box to say “I’m moving!” and then perform the layout accordingly. If we were to try and track all of these touches and movements in the controller, it would lead to a mess of nested conditional statements, making the code extremely difficult to read.

Dynamic Layout

This is where the self-working and the generic code is; the dynamic layout of boxes. This is the implementation of our box delegate method and gets called by each box that is moving. Take a look and I’ll explain what it does…

[objc] – (void)box:(LOBox *)aBox movedToPosition:(CGPoint)position {
NSInteger destinationBoxIndex = -1;

 

for(LOBox *box in boxes) {
if([box isEqual:aBox]) {
continue;
}

if([box containsPoint:position]) {
destinationBoxIndex = [boxes indexOfObject:box];
break;
}
}

if(destinationBoxIndex == -1) {
return;
}

[boxes removeObject:aBox];
[boxes insertObject:aBox atIndex:destinationBoxIndex];

 

[self animateBoxLayout];
}
[/objc]

 

First, we set a sentinel value for the destination box index. This will act as a marker in case we don’t find any boxes we are moving over. We then iterate our boxes to check if our current box is over it, but first, we must check that the box we are checking against isn’t the box that is moving, that would just be silly. We check if the current box position is within each of the boxes, if we find one that returns TRUE, we set the destinationBoxIndex to be the index of the box that it is overlapping.

Now the two lines of self-working magic…. we simply remove our box being move from its current position and insert into the same index as the box we are overlapping. This has two effects. The first is that when we remove the box, due to the boxes being in an array, the rest of the boxes to the right of our box shuffle down a spot to takes its position. The second is that when we insert the box in its new position, all of the boxes to the right of it shuffle up a spot to accommodate it. Exactly what we want. Then all we need to do is lay out the boxes again (using our previous layout method) and the boxes all shuffle into their correct positions, wrapping this in a UIView animation block causes a nice animation to occur.

So there we have it. We have a simple, reusable component that can be slotted into any view, with boxes that can be laid out as they move. You can use this for images, icons, anything really. So, if you have an app idea and would love to get started, Talk To Us today!

Get Email Notifications

chatsimple