Creating a CALayer grid using CAConstraintLayoutManager

The last few days I’ve been poring over the Core Animation Programming Guide. In particular, I’ve been trying to figure out how to create a grid of equally-sized cells. My initial attempt just saw me calculating the frames for each cell in the grid manually, and explicitly setting the frame for each cell. This worked, after a fashion, but was pretty ugly. And, if I resized the grid the cells didn’t follow.

Enter CAConstraintLayoutManager. I initially tried to constrain each cell to neighbouring cells, and to the super layer for the “outer” cells. Not only was this messy; I couldn’t make it work at all—not even with a 1 by 5 “grid”. I suspect that the layout manager is getting a bit confused because I don’t specify the size of any dimension of any cell.

Then I discovered the scale-argument form of the NSConstraint. With this I can specify the size of a cell and its position in the grid only in terms of the super layer. This is much much simpler. Have a look at the example code below. Paste it into a custom view, and it should display a gray grid on a pleasant blue background:

- (void)awakeFromNib {
    self.wantsLayer = YES;
    CALayer *grid = self.layer;

    grid.backgroundColor = CGColorCreateGenericRGB(0.1, 0.1, 0.4, .8);
    grid.layoutManager = [CAConstraintLayoutManager layoutManager];

    int rows = 8;
    int columns = 8;
    
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < columns; c++) {
            CALayer *cell = [CALayer layer];
            cell.borderColor = CGColorCreateGenericGray(0.8, 0.8);
            cell.borderWidth = 1;
            cell.cornerRadius = 4;
            cell.name = [NSString stringWithFormat:@"%u@%u", c, r];
            
            [cell addConstraint:
             [CAConstraint constraintWithAttribute: kCAConstraintWidth
                                        relativeTo: @"superlayer"
                                         attribute: kCAConstraintWidth
                                             scale: 1.0 / columns
                                            offset: 0]];
            
            [cell addConstraint:
             [CAConstraint constraintWithAttribute: kCAConstraintHeight
                                        relativeTo: @"superlayer"
                                         attribute: kCAConstraintHeight
                                             scale: 1.0 / rows
                                            offset: 0]];
            
            [cell addConstraint:
             [CAConstraint constraintWithAttribute: kCAConstraintMinX
                                        relativeTo: @"superlayer"
                                         attribute: kCAConstraintMaxX
                                             scale: c / (float)columns
                                            offset: 0]];
            
            
            [cell addConstraint:
             [CAConstraint constraintWithAttribute: kCAConstraintMinY
                                        relativeTo: @"superlayer"
                                         attribute: kCAConstraintMaxY
                                             scale: r / (float)rows
                                            offset: 0]];
            
            [grid addSublayer:cell];
        }
    }
}

Things aren’t so simple if you want to have some spacing between the cells. I suspect you have to pack a cell within an intermediate CALayer, or add some custom drawing to each cell to draw the border a bit inset from the edge of the cell.

Update: It turns out that using this method to create a grid with spacing between the cells is not much harder after all. The below replacements for the constraints above give you a 2-pixel spacing between the tiles. I’ve highlighted the differences in bold text.

[cell addConstraint:
 [CAConstraint constraintWithAttribute: kCAConstraintWidth
                            relativeTo: @"superlayer"
                             attribute: kCAConstraintWidth
                                 scale: 1.0 / columns
                                offset: -2]];

[cell addConstraint:
 [CAConstraint constraintWithAttribute: kCAConstraintHeight
                            relativeTo: @"superlayer"
                             attribute: kCAConstraintHeight
                                 scale: 1.0 / rows
                                offset: -2]];

[cell addConstraint:
 [CAConstraint constraintWithAttribute: kCAConstraintMidX
                            relativeTo: @"superlayer"
                             attribute: kCAConstraintMaxX
                                 scale: (c + 0.5) / (float)columns
                                offset: 0]];


[cell addConstraint:
 [CAConstraint constraintWithAttribute: kCAConstraintMidY
                            relativeTo: @"superlayer"
                             attribute: kCAConstraintMaxY
                                 scale: (r + 0.5) / (float)rows
                                offset: 0]];            

7 Comments »

  1. Dunk said

    I wish iPhone had CALayer’s…. :-(

    (I can freely say that now since the NDA is gone!)

  2. Dunk said

    Oh, no wait – iPhone DOES have CALayers

    (I can say that too!).

  3. [...] elements of the game. It took some time before I managed to wrap my head around how to make a re-sizeable grid of CALayer instances, but once I finally did the rest has been pretty smooth sailing. It’s really satisfying [...]

  4. Mike said

    Thanks — just what I was looking for!

  5. [...] a little digging around I was able to find an amazing Hot Chocolate blog post which showed a fantastic trick when laying out the Cells without having to reference the prior [...]

  6. Alex said

    Thanx for this example!
    It’s helpfull to understand CALayers layout managment.
    Now i think: how to animate dragging cells between it’s places?..

  7. Adrian said

    Great post! helped me a lot. How could you adapt this so you had a Finder-like thumbnail view? when i resize the window it resizes all the cells.

RSS feed for comments on this post · TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.