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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Make a Letter / Word Game with UIKit: Part 1/3
40 mins
- Getting Started: Introducing Anagrams
- The Starter Project
- 1) Load the Level Config File
- 2) Create a Game Controller Class
- 3) Create an Onscreen View
- 4) Create a Tile View
- 5) Dealing the Tiles on the Screen
- 6) Add the Letters
- 7) Slightly Rotate the Letters Randomly
- 8) Add the Targets
- Where to Go from Here?
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 particularUIView
. (AndUIView
s 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
: TheLevel
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:
- You check to make sure this method is only called after the
level
property is set and that itsLevel
object contains anagrams. - You generate a random index into the anagram list, then grab the anagram at this index.
- You store the two phrases into
anagram1
andanagram2
. - Then you store the number of characters in each phrase into
ana1len
andana2len
. You’ll do more with this information later. - 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 custominit
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.)
-
You override
initWithFrame:
. Since you have a custom initialization method, you don’t ever want to callinitWithFrame:
. Instead, you add anNSAssert
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:
andinitWithImage:highlightedImage:
. You can do those later by yourself.) -
initWithLetter:andSideLength:
is your custom method that will initialize tile instances. It loads the tile.png image and makes an instance ofTileView
by callinginitWithImage:
on the parent class (UIImageView
in this case). - 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 theTileView
.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.