In this episode we'll attempt to create the board for the game Connect Four. We'll leverage what we've learned about auto layout and create the connect four board constraints, then we'll draw the view. We have to draw it filled with a bunch of holes, so that we can see objects passing behind it. Using Core Graphics and clipping paths we can accomplish this effect.
Episode Links Episode Source Code Quartz 2D Programming Guide - Essential reading on creating & filling paths, including a dense but important definition of the path fill rule we'll be using. 5 ways to draw a 2D shape with a hole in CoreGraphics - Oldie but goodie blog post by Matt Gallagher comparing the various methods we can use to draw shapes with a hole cut out in the middle Setting up the Layout of the Board Our board needs to be set up in two places. First, the class itself for its minimum size and aspect ratio: // GameBoardView.m - (id)initWithRows:(NSInteger)rows columns:(NSInteger)columns { self = [super init]; if (self) { self.rows = rows; self.columns = columns; self.translatesAutoresizingMaskIntoConstraints = NO; self.opaque = NO; } return self; } + (BOOL)requiresConstraintBasedLayout { return YES; } - (void)updateConstraints { [super updateConstraints]; CGFloat aspectRatio = self.columns / (CGFloat)self.rows; [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1/aspectRatio constant:0]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[self(%gt;=200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(self)]]; } Note that we indicate that we require constraint based layout, and we make sure to turn off translatesAutoResizingMasksIntoConstraints. Then in the containing view controller, we specify the rest of the constraints: // ViewController.m - (void)viewDidLoad { [super viewDidLoad]; self.gameBoardView = [[GameBoardView alloc] initWithRows:6 columns:7]; [self.view addSubview:self.gameBoardView]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-[_gameBoardView]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_gameBoardView)]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.gameBoardView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]]; } Drawing the Board with Holes - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSaveGState(context); CGContextBeginPath(context); CGContextAddRect(context, self.bounds); CGRect innerRect = CGRectInset(self.bounds, PADDING, PADDING); CGFloat squareSize = innerRect.size.width / self.columns; CGContextTranslateCTM(context, PADDING, PADDING); for (int y = 0; y < self.rows; y++) { for (int x = 0; x < self.columns; x++) { CGFloat holeSize = squareSize - PADDING * 2; CGRect holeRect = CGRectMake(PADDING, PADDING, holeSize, holeSize); CGContextAddEllipseInRect(context, holeRect); CGContextTranslateCTM(context, squareSize, 0); } CGContextTranslateCTM(context, - innerRect.size.width, squareSize); } CGContextSetFillColorWithColor(context, [UIColor colorWithRed:0.822 green:0.822 blue:0.000 alpha:1.000].CGColor); CGContextEOFillPath(context); CGContextRestoreGState(context); } Here we use CGContextEOFillPath to fill our background but not the holes. Take a look at the links above for an explanation of how this works. Dropping Pieces Behind the Board Next we can test out our effect by adding a piece behind the board when the user taps on it and dropping it past the bottom of the screen: // viewDidLoad [self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(spawnPiece:)]]; - (void)spawnPiece:(UITapGestureRecognizer *)tap { CGSize size = [self.gameBoardView pieceSize]; CGPoint tapLocation = [tap locationInView:self.view]; CGPoint point = CGPointMake(tapLocation.x - size.width / 2.0f, tapLocation.y - size.height / 2.0f); CGRect frame; frame.size = size; frame.origin = point; PieceView *piece = [[PieceView alloc] initWithFrame:frame pieceColor:PieceColorRed]; [self.view insertSubview:piece belowSubview:self.gameBoardView]; [UIView animateWithDuration:0.75 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ CGRect newFrame = frame; newFrame.origin.y = self.view.bounds.size.height; piece.frame = newFrame; } completion:^(BOOL finished) { [piece removeFromSuperview]; }]; }