banner



How To Make A Card Game App

This is a post by iOS Tutorial Team member Matthijs Hollemans, an experienced iOS developer and designer. You can find him on Google+ and Twitter.

Card games are quite popular on the App Store – over 2,500 apps and counting – so it's about time that raywenderlich.com shows you how to make one!

In addition, this monster 7-part tutorial will demonstrate how to make the game multiplayer, so you can play against your friends over Bluetooth or Wi-Fi using the peer-to-peer features of Game Kit.

Even though you're making a game in this tutorial, you won't be using OpenGL or a game framework like Cocos2D. Instead, you'll be making it with nothing more than standard UIImageViews and UIView-based animation!

The reason for not using OpenGL or Cocos2D is that you don't really need them! UIKit is fast enough for what you're going to do here, and excels for simple card/board games like this. Most of the content on the screen is static and you'll only be animating a few views at a time.

To follow along with this tutorial, you will need Xcode 4.3 or later. If you still have Xcode 4.2, then it's time to upgrade!

Also, to test the multiplayer functionality, you will need at least two devices running iOS 5 or better. If you have a home Wi-Fi network, then you can make do with a single device, but ideally you'll have more than one (I used four different devices while writing the code for this tutorial).

If you are new to this series, check out the introduction first. There you can see a video of the game, and we'll invite you to our special Reader's Challenge!

Then keep reading to impress your friends with the best card trick of all – your own multiplayer card game app!

Introducing: Snap!

The card game that you'll be making is a children's game called Snap!. This is what it looks like when it's done:

The finished game of Snap!

In case you're not familiar with the rules of Snap!, the game is played with 2 to 4 players using a standard 52-card deck. The goal is to win all the cards. You win cards by spotting a matching pair.

At the start of each round, the dealer shuffles the deck and deals out the cards clockwise around the table until there are none left in the deck. The cards are placed face down in front of the players.

The players take turns in clockwise order. If it's your turn, you turn over the top card from your pile. The idea is to yell "Snap!" as quickly as possible when you see that any of the open cards form a matching pair. Two cards match if they have the same value, for example two kings. The suit does not matter.

The player who yells "Snap!" the quickest wins both piles and adds them to his own stack of face-down cards. This continues until one player has all the cards. If a player yells "Snap!" when there is no match on the table, he'll have to pay one card to each of the other players.

The Application Flow

This is a multiplayer game that can be played over Bluetooth or Wi-Fi, and you'll be using the Game Kit framework to make this happen. Note that you're only going to use the peer-to-peer connectivity features of Game Kit – this app will not use Game Center at all. In fact, this tutorial restricts itself to a single class from Game Kit, GKSession.

In the first part of this tutorial, you'll learn how to connect the players' devices so that they can communicate over Bluetooth or Wi-Fi. With Snap! there will always be one player who hosts the game, also referred to as the "server." The other players will join the session that this host has set up. These players are the "clients."

The flow of the application is roughly as follows:

Mockup of the main screen

Above is the main screen of the app, and the first thing a player sees. She can decide to host a game that others can join, join a game hosted by someone else, or play a single-player game against the computer.

Mockup of the Host Game screen

The "Host Game" screen contains a table view that lists the players that have joined this session. Pressing the Start button will begin the game; from that point on, no new players can join. Usually the players decide between themselves beforehand who will host the game, and everybody else then joins that game.

Mockup of the Join Game screen

The "Join Game" screen looks very similar to the Host Game screen, except that there is no Start button. The table view lists the available games (there may very well be multiple people hosting a game). Here you tap on the game you want to join and wait until the host taps his Start button.

Mockup of the game screen

The game screen shows the players sitting around the table with their piles of face-up and face-down cards. The button in the lower-right corner lets the local player yell "Snap!" (there's no actual need to run around the room screaming when you're playing the iPhone game). If any of the other players yell "Snap!", you'll see a speech bubble next to their name.

Getting Started

To save you some time, I've already prepared a basic Xcode project that contains all the resources (such as images and nib files) that you'll need for this first part. Download the starter code here and open Snap.xcodeproj in Xcode.

If you look at the source code, you'll see that the project contains a single view controller, MainViewController. Run the app and it doesn't look like much yet:

The starter version of the app

The main screen contains five UIImageViews for the logo cards (S, N, A, P and the joker) and three UIButtons. You can make this look a bit more lively by animating the image views, but first you'll improve the look of those buttons.

The download also includes a file named Action_Man.ttf. This is a custom font that you're going to use instead of the standard Helvetica, or any of the iPhone's built-in fonts. If you double-click the Action_Man.ttf file on your Mac, it will open in Font Book:

The Action Man font in Font Book

That looks a bit more exciting than the standard fonts, if you ask me. Unfortunately, you can't simply install this font on the Mac and then put it on your buttons in Interface Builder. You have to write some code to make this happen. First, however, you need to tell UIKit about this font, so your app can load it.

Open Snap-Info.plist in Xcode. Add a new row and give it the key "Fonts provided by application." This is an array type. Give the first item the name of the font file, Action_Man.ttf:

Adding font to Info plist

You also need to add the actual TTF file to the project. Simply drag it into the Supporting Files section:

The TTF file added to the project

Note that you need to check the Snap target in the Add to Targets section of the dialog box that pops up. Otherwise, the font file will not be included in the application bundle:

Adding the new file to the target

Now you can simply set this font on your buttons and labels by doing:

UIFont *font = [UIFont fontWithName:@"Action Man" size:16.0f]; someLabel.font = font;        

To avoid having to repeat this code over and over, make a category for it. From the File menu, select the New->File… option and choose the "Objective-C category" template. Fill in "SnapAdditions" for the category name and "UIFont" as the class that will take the category:

Creating a category on UIFont

This creates two new files, UIFont+SnapAdditions.h and UIFont+SnapAdditions.m. Just to keep things tidy, I placed these two files into a new group named Categories:

Placing the source files in the Categories group

Replace the contents of UIFont+SnapAdditions.h with:

@interface UIFont (SnapAdditions)  + (id)rw_snapFontWithSize:(CGFloat)size;  @end        

Then replace the contents of the .m file with:

#import "UIFont+SnapAdditions.h"  @implementation UIFont (SnapAdditions)  + (id)rw_snapFontWithSize:(CGFloat)size { 	return [UIFont fontWithName:@"Action Man" size:size]; }  @end        

It's a pretty simple category. You've just added a single class method, rw_snapFontWithSize:, which will create a new UIFont object with the Action Man font.

Notice that the file name of the font is Action_Man.ttf, with an underscore. But the name of the font has no underscore. You should always use the name of the font itself, not its filename. To find out the name of a font, double-click it from Finder to open it in Font Book. (Notice in the Font Book screenshot above that it does say Action Man and not Action_Man.)

The second thing to notice here is that I have prefixed the method name with "rw_". It's always a good idea to prefix methods that you add to categories on standard framework classes with your initials (or some other unique identifier), just to make sure the method name won't clash with a built-in method, or a method that Apple may add in the future. It's unlikely that Apple would actually add a method named "snapFontWithSize:", but it's better to be safe than sorry.

With these preparations in place, you can now set this new font on the buttons from your Main View Controller. At the top of MainViewController.m, add an import for your new category:

#import "UIFont+SnapAdditions.h"        

Add the viewDidLoad method inside the @implementation block:

- (void)viewDidLoad { 	[super viewDidLoad];  	self.hostGameButton.titleLabel.font = [UIFont rw_snapFontWithSize:20.0f]; 	self.joinGameButton.titleLabel.font = [UIFont rw_snapFontWithSize:20.0f]; 	self.singlePlayerGameButton.titleLabel.font = [UIFont rw_snapFontWithSize:20.0f]; }        

That should do it. Run the app again. The button titles will show up in their new font:

The buttons with the new font

If you're still seeing the old font, make sure the Action_Man.ttf file is truly being placed in the application bundle. Select the file and make sure the box in the Target Membership section is checked (in the File Inspector in the righthand pane of the Xcode window):

Target membership checkbox

Note: Be careful to read the font's license agreement when you embed a custom TTF file into an app. Font files are protected by copyright and often require license fees if you want to distribute them as part of your app. Fortunately for you, the Action Man font is free to use and distribute.

The button titles look a lot better already, but a proper button has a border. To add borders, you'll use a set of stretchable images. These images have already been added to the project and are named Button.png and ButtonPressed.png.

Because you have several different screens that all need to use similar-looking buttons, you'll place the code for customizing the appearance of the buttons into a category as well.

Add a new category to the project. Again name it "SnapAdditions," but this time, make it a category on UIButton. This creates two new files, UIButton+SnapAdditions.h and UIButton+SnapAdditions.m. Put these into the Categories group as well, and then replace the contents of the .h file with:

@interface UIButton (SnapAdditions)  - (void)rw_applySnapStyle;  @end        

Replace the contents of the .m file with:

#import "UIButton+SnapAdditions.h" #import "UIFont+SnapAdditions.h"  @implementation UIButton (SnapAdditions)  - (void)rw_applySnapStyle { 	self.titleLabel.font = [UIFont rw_snapFontWithSize:20.0f];  	UIImage *buttonImage = [[UIImage imageNamed:@"Button"] stretchableImageWithLeftCapWidth:15 topCapHeight:0]; 	[self setBackgroundImage:buttonImage forState:UIControlStateNormal];  	UIImage *pressedImage = [[UIImage imageNamed:@"ButtonPressed"] stretchableImageWithLeftCapWidth:15 topCapHeight:0]; 	[self setBackgroundImage:pressedImage forState:UIControlStateHighlighted]; }  @end        

Once again, there's only one method (with the "rw_" prefix). When you call this method on a UIButton object, it will give the button a new background image, and it will also set the font.

Add an import for this new category in MainViewController.m:

#import "UIButton+SnapAdditions.h"        

You can now replace viewDidLoad with the following:

- (void)viewDidLoad { 	[super viewDidLoad];  	[self.hostGameButton rw_applySnapStyle]; 	[self.joinGameButton rw_applySnapStyle]; 	[self.singlePlayerGameButton rw_applySnapStyle]; }        

Run the app again. Now the buttons look like real buttons:

The buttons now have borders

Animating the Intro

Now you'll liven up the main screen a little. How about when the app starts, the logo cards fly into the screen?

To create this effect, add the following methods to MainViewController.m:

- (void)prepareForIntroAnimation { 	self.sImageView.hidden = YES; 	self.nImageView.hidden = YES; 	self.aImageView.hidden = YES; 	self.pImageView.hidden = YES; 	self.jokerImageView.hidden = YES; }  - (void)performIntroAnimation { 	self.sImageView.hidden = NO; 	self.nImageView.hidden = NO; 	self.aImageView.hidden = NO; 	self.pImageView.hidden = NO; 	self.jokerImageView.hidden = NO;  	CGPoint point = CGPointMake(self.view.bounds.size.width / 2.0f, self.view.bounds.size.height * 2.0f);  	self.sImageView.center = point; 	self.nImageView.center = point; 	self.aImageView.center = point; 	self.pImageView.center = point; 	self.jokerImageView.center = point;  	[UIView animateWithDuration:0.65f 		delay:0.5f 		options:UIViewAnimationOptionCurveEaseOut 		animations:^ 		{ 			self.sImageView.center = CGPointMake(80.0f, 108.0f); 			self.sImageView.transform = CGAffineTransformMakeRotation(-0.22f); 			 			self.nImageView.center = CGPointMake(160.0f, 93.0f); 			self.nImageView.transform = CGAffineTransformMakeRotation(-0.1f);  			self.aImageView.center = CGPointMake(240.0f, 88.0f);  			self.pImageView.center = CGPointMake(320.0f, 93.0f); 			self.pImageView.transform = CGAffineTransformMakeRotation(0.1f);  			self.jokerImageView.center = CGPointMake(400.0f, 108.0f); 			self.jokerImageView.transform = CGAffineTransformMakeRotation(0.22f); 		} 		completion:nil]; }        

The first method, prepareForIntroAnimation, simply hides the five UIImageViews that hold the logo cards. The actual animation happens in performIntroAnimation. First, you place the cards off-screen, horizontally centered but vertically below the bottom of the screen. Then you start a UIView animation block that places the UIImageViews at their final positions, to look like they're fanned out from the center.

You'll call these methods from viewWillAppear: and viewDidAppear:

- (void)viewWillAppear:(BOOL)animated { 	[super viewWillAppear:animated];  	[self prepareForIntroAnimation]; }  - (void)viewDidAppear:(BOOL)animated { 	[super viewDidAppear:animated];  	[self performIntroAnimation]; }        

Now when you run the app, the cards fly up into the screen and fan out. Pretty cool.

Logo with fanned out cards

The animation is not perfect yet, though. It would be nicer if the buttons subtly faded into view while the cards were flying to their final positions. Add the following lines to the bottom of prepareForIntroAnimation:

- (void)prepareForIntroAnimation { 	. . . 	 	self.hostGameButton.alpha = 0.0f; 	self.joinGameButton.alpha = 0.0f; 	self.singlePlayerGameButton.alpha = 0.0f;  	_buttonsEnabled = NO; }        

That makes the buttons fully transparent. Also add a second UIView-animation block to the end of performIntroAnimation:

- (void)performIntroAnimation { 	. . .  	[UIView animateWithDuration:0.5f 		delay:1.0f 		options:UIViewAnimationOptionCurveEaseOut 		animations:^ 		{ 			self.hostGameButton.alpha = 1.0f; 			self.joinGameButton.alpha = 1.0f; 			self.singlePlayerGameButton.alpha = 1.0f; 		} 		completion:^(BOOL finished) 		{ 			_buttonsEnabled = YES; 		}]; }        

Here you simply animate the buttons back to fully opaque. But what is that _buttonsEnabled variable? You don't want the user to tap the buttons while they're still fading in. Only after the animations have completed should the buttons become available for tapping.

You'll use the _buttonsEnabled instance variable to ignore taps on the buttons while the animation is still taking place. For now, just add this new instance variable to the @implementation section:

@implementation MainViewController { 	BOOL _buttonsEnabled; }        

Run the app and check out the animation. Pretty smooth!

Game Kit and Multiplayer Games

Game Kit is a standard framework that comes with the iOS SDK. Its main features are for use in Game Center (which you won't use in this tutorial) and voice chat, but it also has a peer-to-peer connectivity feature that connects devices over a Bluetooth connection. If all devices are on a local Wi-Fi network, Game Kit can also use that instead of Bluetooth. (There is also a provision for peer-to-peer matchmaking over the Internet, but you'll have to write most of the code for that yourself – and these days it's probably easier to use Game Center.)

Game Kit's peer-to-peer feature is great for multiplayer games where the players are in the same room together and all using their own devices. Reportedly, the players cannot be more than about 10 meters (or 30 feet) away from each other when using Bluetooth.

So what does peer-to-peer connectivity mean? Each device that participates in a Game Kit networking session is named a "peer." A device can act as a "server" that broadcasts a particular service, as a "client" that looks for servers that provide a particular service, or as both client and server at the same time. To do this, Game Kit uses Bonjour technology behind the scenes, but you don't need to work directly with Bonjour in order to use Game Kit.

When using Bluetooth, devices don't need to be paired, the way you'd need to pair a Bluetooth mouse or keyboard with your device. Game Kit simply lets clients discover servers, and once this connection is made, the devices can send messages to each other over the local network.

You don't have the option to choose between Bluetooth or Wi-Fi; GameKit makes this decision for you. Bluetooth is also not supported in the Simulator, but Wi-Fi is.

While developing and testing for this tutorial, I found it easiest to use the Simulator and one or two physical devices, and play over the local Wi-Fi network. If you want to play over Bluetooth, you'll need to have at least two physical devices that both have Bluetooth enabled.

Note: It's possible to do network communications with Bonjour and Bluetooth without Game Kit, but if you're building a multiplayer game, using Game Kit is a lot easier. It hides all the nasty networking stuff from you and gives you a single class to use, GKSession. That will be the only Game Kit class you're going to be working with in this tutorial (and its delegate, GKSessionDelegate).

If you're making a two-player multiplayer game that uses Bluetooth or Wi-Fi, then you can use Game Kit's GKPeerPickerController to establish the connection between the two devices. It looks like this:

The GKPeerPickerController user interface

The GKPeerPickerController is pretty easy to use, but it's limited to establishing a connection between two devices. Snap! can have up to four players, so this tutorial takes you through writing your own matchmaking code.

The "Host Game" Screen

In this section, you'll add the Host Game screen to the app. This screen lets a player host a gaming session that other players can join. When you're done, it will look like this:

The Host Game screen

The table view lists the players who have connected to this host, and the Start button begins the game. There is also a text field that allows you to name your player (by default it will use the name of your device).

Add a new UIViewController subclass to the project, named HostViewController. Disable the "With XIB for user interface" option. The starter code already comes with a fully-prepared nib file for the Host Game screen. You can find it in the "Snap/en.lproj" folder. Drag the HostViewController.xib file into the project.

The nib file looks like this:

The Host Game nib

Most of the UI elements are hooked up to properties and action methods, so you should add these to your HostViewController class. Otherwise, the app will crash when you try to load this nib.

In HostViewController.m, add the following lines to the class extension (at the top of the file):

@interface HostViewController () @property (nonatomic, weak) IBOutlet UILabel *headingLabel; @property (nonatomic, weak) IBOutlet UILabel *nameLabel; @property (nonatomic, weak) IBOutlet UITextField *nameTextField; @property (nonatomic, weak) IBOutlet UILabel *statusLabel; @property (nonatomic, weak) IBOutlet UITableView *tableView; @property (nonatomic, weak) IBOutlet UIButton *startButton; @end        

Notice that you're putting the IBOutlet properties inside the .m file, not in the .h file. This is one of the new features of the LLVM compiler that ships with the latest versions of Xcode (4.2 and up). It helps to keep your .h files really clean so that you only expose the properties and methods that other objects need to see.

Of course, you need to synthesize these properties, so add the following lines below @implementation:

@synthesize headingLabel = _headingLabel; @synthesize nameLabel = _nameLabel; @synthesize nameTextField = _nameTextField; @synthesize statusLabel = _statusLabel; @synthesize tableView = _tableView; @synthesize startButton = _startButton;        

Tip: It's rumored that in the next version of Xcode (which you may already be using by the time you read this tutorial), you can simply leave out the @synthesize lines. But if you're on Xcode 4.3, it's still necessary to put them in.

Replace shouldAutorotateToInterfaceOrientation: so that it will only support the landscape orientation:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { 	return UIInterfaceOrientationIsLandscape(interfaceOrientation); }        

Also, add the following placeholder methods to the bottom of the file:

- (IBAction)startAction:(id)sender { }  - (IBAction)exitAction:(id)sender { }  #pragma mark - UITableViewDataSource  - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 	return 0; }  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 	return nil; }        

Finally, replace the @interface line in HostViewController.h with:

@interface HostViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>        

That's enough to get a basic version of the Host Game screen working, but you still have to display it when the user taps the corresponding button on the main screen. Add an import statement to MainViewController.h:

#import "HostViewController.h"        

And replace the hostGameAction: method in MainViewController.m with the following:

- (IBAction)hostGameAction:(id)sender { 	if (_buttonsEnabled) 	{ 		HostViewController *controller = [[HostViewController alloc] initWithNibName:@"HostViewController" bundle:nil];  		[self presentViewController:controller animated:NO completion:nil]; 	} }        

If you run the app now, you'll see that tapping the Host Game button instantly brings up the Host Game screen. It works, although it doesn't look particularly pretty. You're presenting this new view controller modally, but because you passed NO to the animated: parameter, there is no standard "slide up" animation for this new screen.

Such an animation wouldn't look particularly good here – you'd see the felt background of the Host Game screen slide up over the felt from the main screen – so instead, create a new animation by adding the following method to MainViewController.m:

- (void)performExitAnimationWithCompletionBlock:(void (^)(BOOL))block { 	_buttonsEnabled = NO;  	[UIView animateWithDuration:0.3f 		delay:0.0f 		options:UIViewAnimationOptionCurveEaseOut 		animations:^ 		{ 			self.sImageView.center = self.aImageView.center; 			self.sImageView.transform = self.aImageView.transform;  			self.nImageView.center = self.aImageView.center; 			self.nImageView.transform = self.aImageView.transform;  			self.pImageView.center = self.aImageView.center; 			self.pImageView.transform = self.aImageView.transform;  			self.jokerImageView.center = self.aImageView.center; 			self.jokerImageView.transform = self.aImageView.transform; 		} 		completion:^(BOOL finished) 		{ 			CGPoint point = CGPointMake(self.aImageView.center.x, self.view.frame.size.height * -2.0f);  			[UIView animateWithDuration:1.0f 				delay:0.0f 				options:UIViewAnimationOptionCurveEaseOut 				animations:^ 				{ 					self.sImageView.center = point; 					self.nImageView.center = point; 					self.aImageView.center = point; 					self.pImageView.center = point; 					self.jokerImageView.center = point; 				} 				completion:block];  			[UIView animateWithDuration:0.3f 				delay:0.3f 				options:UIViewAnimationOptionCurveEaseOut 				animations:^ 				{ 					self.hostGameButton.alpha = 0.0f; 					self.joinGameButton.alpha = 0.0f; 					self.singlePlayerGameButton.alpha = 0.0f; 				} 				completion:nil]; 		}]; }        

Tip: It doesn't really matter where you add this method (as long as it's between the @implementation and @end directives). Previously you had to declare the method in the .h file, and put its signature in a class extension at the top of the .m file, or make sure that any method you call is higher up in the source file. That's no longer necessary thanks to the LLVM compiler that ships with Xcode 4.3. The compiler is now smart enough to find the method, no matter where you put it in the source file, and even if you didn't forward-declare it previously.

The animation in performExitAnimationWithCompletionBlock: slides the logo cards off the screen and at the same time fades out the buttons. When the animation is done, it executes the code from the block that you pass in as a parameter.

Now change hostGameAction: to:

- (IBAction)hostGameAction:(id)sender { 	if (_buttonsEnabled) 	{ 		[self performExitAnimationWithCompletionBlock:^(BOOL finished) 		{	 			HostViewController *controller = [[HostViewController alloc] initWithNibName:@"HostViewController" bundle:nil];  			[self presentViewController:controller animated:NO completion:nil]; 		}]; 	} }        

It's almost the same as before, but now the logic that creates and presents the Host Game screen is wrapped in a block that gets performed when the exit animation completes. Run the app and see for yourself. You put the animation code in a separate method so that you can also use it when the user taps the other buttons.

As you can see in the nib, the Host Game screen also uses the default Helvetica font, and its Start button doesn't have a border. This is easily fixed. Add these two imports to HostViewController.m:

#import "UIButton+SnapAdditions.h" #import "UIFont+SnapAdditions.h"        

And replace viewDidLoad with the following:

- (void)viewDidLoad { 	[super viewDidLoad];  	self.headingLabel.font = [UIFont rw_snapFontWithSize:24.0f];; 	self.nameLabel.font = [UIFont rw_snapFontWithSize:16.0f]; 	self.statusLabel.font = [UIFont rw_snapFontWithSize:16.0f]; 	self.nameTextField.font = [UIFont rw_snapFontWithSize:20.0f];  	[self.startButton rw_applySnapStyle]; }        

Because you created these convenient categories to add your font and style your buttons, it's a snap (ha ha) to make the screen look good. Run the app to see for yourself.

There are a few more tweaks to make. The screen has a UITextField that allows the player to type in his name. When you tap in this text field, the on-screen keyboard slides up. The keyboard takes up about half of the screen space, and there is currently no way to dismiss it.

The on-screen keyboard

The first way to dismiss the keyboard is with the big blue Done button. Currently this does nothing when tapped, but adding the following method to HostViewController.m will solve that:

#pragma mark - UITextFieldDelegate  - (BOOL)textFieldShouldReturn:(UITextField *)textField { 	[textField resignFirstResponder]; 	return NO; }        

The second way to dismiss the keyboard is to add the following code to the end of viewDidLoad:

- (void)viewDidLoad { 	. . .  	UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self.nameTextField action:@selector(resignFirstResponder)]; 	gestureRecognizer.cancelsTouchesInView = NO; 	[self.view addGestureRecognizer:gestureRecognizer]; }        

You're creating a gesture recognizer that responds to simple taps and adding it to the view controller's main view. Now when the user taps outside of the text field, the gesture recognizer sends the "resignFirstResponder" message to the text field, which will make the keyboard disappear.

Note that you need to set the cancelsTouchesInView property to NO, otherwise it will no longer be possible to tap on anything else in the screen, such as the table view and the buttons.

Exiting the Host Game Screen

Speaking of the buttons, they don't do anything yet. Leave the Start button alone for now, but it would be nice if you could return to the main screen by tapping the X in the bottom-left corner.

This button is hooked up to exitAction:, which is currently empty. It should close the screen, and you'll implement this using a delegate protocol. There will be several view controllers in this app, and they will communicate with one another through delegates to keep the dependencies minimal and clean.

Add the following to HostViewController.h, above the @interface line:

@class HostViewController;  @protocol HostViewControllerDelegate <NSObject>  - (void)hostViewControllerDidCancel:(HostViewController *)controller;  @end        

Inside the @interface section, add a new property:

@property (nonatomic, weak) id <HostViewControllerDelegate> delegate;        

Properties need to be synthesized, so in HostViewController.m, do:

@synthesize delegate = _delegate;        

Finally, replace exitAction: with:

- (IBAction)exitAction:(id)sender { 	[self.delegate hostViewControllerDidCancel:self]; }        

The idea should be clear: you've declared a delegate protocol for the HostViewController. When the exit button is tapped, the HostViewController tells its delegate that the Host Game screen has been cancelled. The delegate is then responsible for closing the screen.

In this case, the role of the delegate is played by the MainViewController, of course. Change the following line in MainViewController.h:

@interface MainViewController : UIViewController <HostViewControllerDelegate>        

In MainViewController.m, the hostGameAction: method becomes:

- (IBAction)hostGameAction:(id)sender { 	if (_buttonsEnabled) 	{ 		[self performExitAnimationWithCompletionBlock:^(BOOL finished) 		{	 			HostViewController *controller = [[HostViewController alloc] initWithNibName:@"HostViewController" bundle:nil]; 			controller.delegate = self;  			[self presentViewController:controller animated:NO completion:nil]; 		}]; 	} }        

Now you're making the MainViewController the delegate of the HostViewController. Lastly, add the implementation of the delegate method to MainViewController.m:

#pragma mark - HostViewControllerDelegate  - (void)hostViewControllerDidCancel:(HostViewController *)controller { 	[self dismissViewControllerAnimated:NO completion:nil]; }        

This simply closes the HostViewController screen without an animation. Because MainViewController's viewWillAppear will be called at this point, the flying cards animation will be performed again. Run the app and try it out.

Note: For debugging purposes, I like to make sure my view controllers (and any other objects) really do get deallocated when the screen gets dismissed, so I always add a dealloc method to my view controllers that logs a message to the Debug Output pane:

- (void)dealloc { 	#ifdef DEBUG 	NSLog(@"dealloc %@", self); 	

0 Response to "How To Make A Card Game App"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel