How To Make a Letter / Word Game with UIKit: Part 1/3

This 3-part tutorial series will guide you through the process of building a board game for the iPad, where you create words with letters. You’ll also learn about best practices for creating solid, modular iOS apps. And as a bonus, you’ll get a crash course in audio-visual effects with UIKit! By Marin Todorov.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

2) Create a Game Controller Class

Often people who are not experienced with MVC before jumping into iOS development confuse a controller and a UIViewController.

  • A controller is simply a class that focuses on some part of your app’s logic, separate from your app’s data and the classes used to display that data.
  • A class that subclasses UIViewController is just a controller managing the presentation of a particular UIView. (And UIViews are used to display something on the screen.) You can read up more on the MVC pattern here.

For this tutorial, you are going to create a controller class to manages your game’s logic. Let’s try this out.

Create a new Objective-C class file in the Anagrams/Classes/controllers folder. Call the new class GameController and make it a subclass of NSObject.

Open GameController.h and add the following import:

#import "Level.h"

Then add the following to the GameController interface:

//the view to add game elements to
@property (weak, nonatomic) UIView* gameView;

//the current level
@property (strong, nonatomic) Level* level;

//display a new anagram on the screen
-(void)dealRandomAnagram;

I bet you’ve already spotted the building blocks of your current goal:

  • gameView: The view you’ll use to display game elements on the screen.
  • level: The Level object that stores the anagrams and other settings for the current game level.
  • dealRandomAnagram: The method you will call to display the current anagram on the screen.

The code so far is pretty simple. Switch to GameController.m and add this placeholder version of dealRandomAnagram:

//fetches a random anagram, deals the letter tiles and creates the targets
-(void)dealRandomAnagram
{
    //1
    NSAssert(self.level.anagrams, @"no level loaded");
    
    //2 random anagram
    int randomIndex = arc4random()%[self.level.anagrams count];
    NSArray* anaPair = self.level.anagrams[ randomIndex ];
    
    //3
    NSString* anagram1 = anaPair[0];
    NSString* anagram2 = anaPair[1];
    
    //4
    int ana1len = [anagram1 length];
    int ana2len = [anagram2 length];
    
    //5
    NSLog(@"phrase1[%i]: %@", ana1len, anagram1);
    NSLog(@"phrase2[%i]: %@", ana2len, anagram2);    
}

Here’s what happening in the code above:

  1. You check to make sure this method is only called after the level property is set and that its Level object contains anagrams.
  2. You generate a random index into the anagram list, then grab the anagram at this index.
  3. You store the two phrases into anagram1 and anagram2.
  4. Then you store the number of characters in each phrase into ana1len and ana2len. You’ll do more with this information later.
  5. Finally, you print the phrases to the console. This will suffice for testing.

Your app is already set up to load the storyboard MainStoryboard.storyboard at start up. This storyboard creates a single screen using the ViewController class.

Since your game will only have one screen, you will add all initialization inside the init method of ViewController. For games that include more than one view controller, you may want to do your initialization someplace else, such as the AppDelegate class.

Open up ViewController.m and at the top, add an import for your new class:

#import "GameController.h"

Replace the private class interface with the following:

@interface ViewController ()
@property (strong, nonatomic) GameController* controller;
@end

Finally, in the implementation add a new initializer method:

-(instancetype)initWithCoder:(NSCoder *)decoder
{
    self = [super initWithCoder:decoder];
    if (self != nil) {
        //create the game controller
        self.controller = [[GameController alloc] init];
    }
    return self;
}

The above method initWithCoder: is called automatically when the app initializes itself from its storyboard.

There it is! You’ve created a GameController class and you have an instance of it in your ViewController implementation, ready to go off and spill some letter tiles on the screen at any moment. :]

3) Create an Onscreen View

The next step is also quite easy: you need to create a view on the screen and connect your GameController to it.

Open up config.h and look at the first two defines inside the file: kScreenWidth and kScreenHeight. These are just two shortcuts, which also take care of the swapped dimensions of the screen in Landscape mode. You are going to use these defines to create your game view.

Switch back to ViewController.m and add the following code at the end of viewDidLoad::

//add one layer for all game elements
UIView* gameLayer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight)];
[self.view addSubview: gameLayer];

self.controller.gameView = gameLayer;

In the above code chunk, you create a new view with the dimensions of the screen and add it to the ViewController‘s view. Then you assign it to the gameView property of your GameController instance. This way the GameController will use this view for all game elements (except for the HUD – you’ll take care of that later).

Having now created your model and controller classes, you’ve come to the point where you need your first custom view class for the Anagrams game to display the tiles.

4) Create a Tile View

In this section, you are going to build a view to show tiles like these:

For now, you’ll focus on just creating the tile squares with the background image. Later, you’ll add the letters on top.

Inside the file group Anagram/Classes/views, create a new class called TileView and make it a subclass of UIImageView. This is already a good start – the UIImageView class provides you with the means to show an image, so you’ll only need to add a label on top of the image later on.

Open TileView.h and add the following to the interface:

@property (strong, nonatomic, readonly) NSString* letter;
@property (assign, nonatomic) BOOL isMatched;

-(instancetype)initWithLetter:(NSString*)letter andSideLength:(float)sideLength;

Here’s what you’ve just created:

  • letter: A property that will hold the letter assigned to the tile.
  • isMatched: A property that will hold a Boolean indicating whether this tile has already been successfully “matched” to a target on the top of the screen.
  • initWithLetter:andSideLength: a custom init method to set up an instance of the class with a given letter and tile size.

Why do you need to set the size of the tile, you might ask?

Look at the these two setups:

The first example shows a short word that requires large tiles to fill the screen. The latter example shows a phrase that requires smaller tiles because it has almost double the tile count. You can see why the tile view needs to be resize-able so that you can show different size tile depending on how many tiles you need on the screen at a time.

Your controller will calculate the phrase length, divide the screen width by the number of characters and decide on a tile size. You’ll implement that soon, but first, get cracking on the code for the tiles.

Switch to TileView.m and replace the contents with this basic class skeleton:

#import "TileView.h"
#import "config.h"

@implementation TileView

//1
- (id)initWithFrame:(CGRect)frame
{
    NSAssert(NO, @"Use initWithLetter:andSideLength instead");
    return nil;
}

//2 create new tile for a given letter
-(instancetype)initWithLetter:(NSString*)letter andSideLength:(float)sideLength
{
    //the tile background
    UIImage* img = [UIImage imageNamed:@"tile.png"];
    
    //create a new object
    self = [super initWithImage:img];
    
    if (self != nil) {
        
        //3 resize the tile
        float scale = sideLength/img.size.width;
        self.frame = CGRectMake(0,0,img.size.width*scale, img.size.height*scale);
        
        //more initialization here
    }
    
    return self;
}

@end

This is a long chunk of code, but if you look closely, you’ll see it doesn’t do much: it’s all about defining placeholders so you can build up the tile functionality later on. There are a few different sections in the code, so let’s go by the numbers:

You override initWithFrame:. Since you have a custom initialization method, you don’t ever want to call initWithFrame:. Instead, you add an NSAssert that will fail every single time. This means there is less of a chance that you will ever create a tile that isn’t properly initialized.

In such a small app, this is just a way to protect yourself from yourself. But in a larger app, or when working as part of a team, this sort of precaution can help reduce errors by keeping anyone from calling this method. (In order to really protect yourself from yourself, you would want to create similar implementations for each of the following methods: init, initWithCoder:, initWithImage: and initWithImage:highlightedImage:. You can do those later by yourself.)

  1. You override initWithFrame:. Since you have a custom initialization method, you don’t ever want to call initWithFrame:. Instead, you add an NSAssert that will fail every single time. This means there is less of a chance that you will ever create a tile that isn’t properly initialized.

    In such a small app, this is just a way to protect yourself from yourself. But in a larger app, or when working as part of a team, this sort of precaution can help reduce errors by keeping anyone from calling this method. (In order to really protect yourself from yourself, you would want to create similar implementations for each of the following methods: init, initWithCoder:, initWithImage: and initWithImage:highlightedImage:. You can do those later by yourself.)

  2. initWithLetter:andSideLength: is your custom method that will initialize tile instances. It loads the tile.png image and makes an instance of TileView by calling initWithImage: on the parent class (UIImageView in this case).
  3. You then calculate by how much you need to scale the default tile so that you get a size that will fit your board for the given word or phrase. To do that, you just adjust the frame of the TileView. UIImageView resizes its image automatically to match its frame size.

Sweet! That code is already enough to show tiles containing just the default empty tile.png image.

Now is a good time to implement more functionality of the game controller, so you can at least see some empty tiles lining up on the screen.

Contributors

Over 300 content creators. Join our team.