Archive for the ‘Dev Center’ category

How to install apps on devices without certificate

September 28th, 2009

As you know to distribute the applications in App Store or even to debug them on real device according to Apple you have to pay $99 to achieve the provisiong and distribution certificate.

But of course as you know, there are always other ways to achieve the similar result. But 1st. your device has to be jailbroken and 2nd. you can distribute your app only among jailbroken devices via Cydia, Icy or less friendly through SSH. It has advantages and disadvantage. Not all iPhones and iPod touches are jailbroken (I guess maybe 10% are), but you don’t have to obey the rules from Apple about submitted apps and you don’t have to wait at least one week for the review result.

Tasks you need to do only once (for developer):

  1. Jailbreak your iPhone
  2. Install LDID (Link Identity Editor), Mobile Termial and OpenSSH from Cydia
  3. Go to /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.sdk/
    I personally tested in on 3.0 that’s why I put that in bold, if you are going to built your app on a higher SDK, go to the appropriate directory.
  4. Open the SDKSettings.plist in your favorite editor (I recommend Property List Editor in Utilities) and in Root -> DefaultProperties set CODE_SIGNING_REQUIRED = NO. BTW check if AD_HOC_CODE_SIGNING_ALLOWED equals NO and CODE_SIGNING_ENTITLEMENTS is blank.
  5. Save the file.

sdksettings

Please remember: modification of above .plist file simply allows you to build the package without signing it, so from now, you are able both to built package for Cydia and AppStore submission. Of course for App Store submission you provide a Distribution profile, for Cydia “Don’t Code Sign” in your project info under Build, Code Signing, Code Signing Identity, Any iPhone OS Device.

build

Tasks you need to do for every package (developer):

  1. Build your project without signing it (Don’t Code Sign).
  2. SSH your bundle to /Applications on your device. You can use Fugu for Mac or  WinSCP for Windows.
  3. Now you have to fake-sign it to make it working. Open Mobile Terminal and type:
    su root
    you will be asked for password, by default: alpine. Next:
    ldid -S /Applications/My\ Project\ Name.app/My\ Project\ Name
    make sure every space in name comes with the backslash before
  4. If any error occurs, it means that you have provided wrong file path or didn’t built it with “Don’t Code Sign”.
  5. Now you can test your application, how it works on real device with full touch control, accelerometer and device’s performance, it’s really different from the Simulator.
  6. If everything works correctly download the file that you modified with ldid – it’s the file named exactly the same as your project without any extensions, and replace it with original from your bundle.
  7. Now you can distribute it in your own way.

Creating a nice glass buttons

May 13th, 2009

You can easily create buttons, link actions to them, but they aren’t cool at all.

This is the typical button:
buttonnormal

And when being pressed (highlighted):
buttonhighlighted

stopwatchbuttonIn my opinion rounded corners look nice, so do the highlighted version, but this white background doesn’t appeal to me. Open the Clock application and go to the Stopwatch. Don’t you think that these Start/Stop and Reset/Lap buttons are more acceptable? Yes, they are.

You might have noticed that using Interface Builder you can set the background color of UIButton. Exacly. Below you can see the button with green background:
buttonbackground

That’s it. UIButton has the green background color. Thank you very much and read my next tutorial.

Oh, sorry for the irony. So, in the case above, button looks like a TV from ’90s rather than a green button. There is a simple way to achieve results similar to the Stopwatch application. All you have to do is to get rid of UIButton and use UISegmentedControl.

UISegmentedControl is typicaly used in options / settings views, where you are able to pick one option of many.

Today, I’m going to show you how to customize this UISegmentedControl:
segmentedcontrol

so it will look like this nice button below:
segmentedcontrolbutton

Unfortunately you cannot do everything in Interface Builder only, so you will need to create an IBOutlet and connect it to created UISegmentedControl in Interface Builder.

IBOutlet UISegmentedControl *myShinyButton;

In Interface Builder go to UISegmentedControl’s attributes, and set Segments=1 – so your UISegmentedControl will consist of only one segment – button. Check momentary – as you see on above UISegmentedControl example (First | Second), First is selected an it stays in that state until you select Second. Once momentary is checked the segment returns to the normal state after a second. Change the style to Bar – only bar can be customized in the way I want to show you. And the last thing you need to do: uncheck the selected for the current segment. Double click the button to type your own text on it. Your button should look like this:

uisegmentedcontrolfirststate

Now you can customize the color, but you can only do it in Xcode. Use the property tintColor:

myShinyButton.tintColor = [UIColor darkGrayColor];

Here is how your button will look like after it:

mybuttondarkgray

Please note: avoid setting “strong colors” as a tintColor. Otherwise there will be no visible difference between normal and selected state when the color becomes more intensive. darkGrayColor will be perfect replacement of blackColor. You can also play with setting RGB colors where you have full control of intesitivity of red, green and blue.

Programmatically

	codeButton = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObject:@"My Button"]];
	codeButton.segmentedControlStyle = UISegmentedControlStyleBar;
	codeButton.momentary = YES;
	codeButton.tintColor = [UIColor lightGrayColor];
	codeButton.center = CGPointMake(160,400);
	[self.view addSubview:codeButton];

Please note:

It won’t be discussed with details here, but remember, you can always use an image as a button (UIButton type: custom). Simple code:

	[button setImage:[UIImage imageNamed:@"myButton.png"] forState:UIControlStateNormal];
	[button setImage:[UIImage imageNamed:@"myButton_highlighted.png"] forState:UIControlStateHighlighted];
	[button setImage:[UIImage imageNamed:@"myButton_selected.png"] forState:UIControlStateSelected];

Providing the right images will give you any effect you need.

Actions

To link any action to buttons created with UISegmentedControl use a value changed event instead of Touch Up Inside. In Interface Builder it should be quite easy, but to do it programmatically:

	[codeButton addTarget:self action:@selector(simpleAction) forControlEvents:UIControlEventValueChanged];

As usuall, sample project for you:

glassbuttonssimulator

xcodeproj

Download the project

Playing backgroud music

May 9th, 2009

Related tutorials:

I’ve already told you how to play short sounds in your game / application. Now it’s time to play some music in the background. Although using one type of short sounds , doesn’t stop the iPod playback, adding the background music will turn iPod off.

In fact using AVAudioPlayer you can also play background music, so, in other words AVAudioPlayer can play longer sounds, not only those limited to 30 seconds. Here is the code again (remember to add a framework: AVFoundation.framework):

	AVAudioPlayer *myAVsound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"backgroundMusic" ofType:@"caf"]  ] error:NULL];
	[myAVsound play]; 

The above code will simply play the background music [backgroundMusic.caf]. But it will play it only once. You could use NSTimer to play it several times – call [myAVsound play].

GBMusicTrack

I would like to present you very nice class – GBMusicTrack, written by Jake Peterson (aka AnotherJake). You can find more info herePlease note, that you can play only one background music at time using this class, the second and any next track can be played, once you stopped playing the previous.

GBMusicTrack.h:

//
//  GBMusicTrack.h
//  GameBase
//
//  Created by Jake Peterson (AnotherJake) on 7/6/08.
//  Copyright 2008 Jake Peterson. All rights reserved.
//

#import <UIKit/UIKit.h> // IN ORIGINAL FILE <Cocoa/Cocoa.h>
#import <AudioToolbox/AudioQueue.h>
#import <AudioToolbox/AudioFile.h>

#define NUM_QUEUE_BUFFERS	3

@interface GBMusicTrack : NSObject
{
	AudioFileID						audioFile;
	AudioStreamBasicDescription		dataFormat;
	AudioQueueRef					queue;
	UInt64							packetIndex;
	UInt32							numPacketsToRead;
	AudioStreamPacketDescription	*packetDescs;
	BOOL							repeat;
	BOOL							trackClosed;
	AudioQueueBufferRef				buffers[NUM_QUEUE_BUFFERS];
}

- (id)initWithPath:(NSString *)path;
- (void)setGain:(Float32)gain;
- (void)setRepeat:(BOOL)yn;
- (void)play;
- (void)pause;

// close is called automatically in GBMusicTrack's dealloc method, but it is recommended
// to call close first, so that the associated Audio Queue is released immediately, instead
// of having to wait for a possible autorelease, which may cause some conflict
- (void)close;

extern NSString	*GBMusicTrackFinishedPlayingNotification;

@end

GBMusicTrack.m

//
//  GBMusicTrack.m
//  GameBase
//
//  Created by Jake Peterson (AnotherJake) on 7/6/08.
//  Copyright 2008 Jake Peterson. All rights reserved.
//

#import "GBMusicTrack.h"

static UInt32 gBufferSizeBytes = 0x10000; // 64k

NSString *GBMusicTrackFinishedPlayingNotification = @"GBMusicTrackFinishedPlayingNotification";

@interface GBMusicTrack (InternalMethods)

static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer);
- (void)callbackForBuffer:(AudioQueueBufferRef)buffer;
- (UInt32)readPacketsIntoBuffer:(AudioQueueBufferRef)buffer;

@end

@implementation GBMusicTrack

#pragma mark -
#pragma mark GBMusicTrack

- (void)dealloc
{
	[self close];
	[super dealloc];
}

- (void)close
{
	// it is preferrable to call close first, before dealloc if there is a problem waiting for
	// an autorelease
	if (trackClosed)
		return;
	trackClosed = YES;
	AudioQueueStop(queue, YES);
	AudioQueueDispose(queue, YES);
	AudioFileClose(audioFile);
}

- (id)initWithPath:(NSString *)path
{
	UInt32		size, maxPacketSize;
	char		*cookie;
	int			i;

	if(!(self = [super init])) return nil;
	if (path == nil) return nil;

	// try to open up the file using the specified path
	if (noErr != AudioFileOpenURL((CFURLRef)[NSURL fileURLWithPath:path], 0x01, kAudioFileCAFType, &audioFile))
	{
		NSLog(@"GBMusicTrack Error - initWithPath: could not open audio file. Path given was: %@", path);
		return nil;
	}

	// get the data format of the file
	size = sizeof(dataFormat);
	AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat, &size, &dataFormat);

	// create a new playback queue using the specified data format and buffer callback
	AudioQueueNewOutput(&dataFormat, BufferCallback, self, nil, nil, 0, &queue);

	// calculate number of packets to read and allocate space for packet descriptions if needed
	if (dataFormat.mBytesPerPacket == 0 || dataFormat.mFramesPerPacket == 0)
	{
		// since we didn't get sizes to work with, then this must be VBR data (Variable BitRate), so
		// we'll have to ask Core Audio to give us a conservative estimate of the largest packet we are
		// likely to read with kAudioFilePropertyPacketSizeUpperBound
		size = sizeof(maxPacketSize);
		AudioFileGetProperty(audioFile, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize);
		if (maxPacketSize > gBufferSizeBytes)
		{
			// hmm... well, we don't want to go over our buffer size, so we'll have to limit it I guess
			maxPacketSize = gBufferSizeBytes;
			NSLog(@"GBMusicTrack Warning - initWithPath: had to limit packet size requested for file path: %@", path);
		}
		numPacketsToRead = gBufferSizeBytes / maxPacketSize;

		// will need a packet description for each packet since this is VBR data, so allocate space accordingly
		packetDescs = malloc(sizeof(AudioStreamPacketDescription) * numPacketsToRead);
	}
	else
	{
		// for CBR data (Constant BitRate), we can simply fill each buffer with as many packets as will fit
		numPacketsToRead = gBufferSizeBytes / dataFormat.mBytesPerPacket;

		// don't need packet descriptsions for CBR data
		packetDescs = nil;
	}

	// see if file uses a magic cookie (a magic cookie is meta data which some formats use)
	AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyMagicCookieData, &size, nil);
	if (size > 0)
	{
		// copy the cookie data from the file into the audio queue
		cookie = malloc(sizeof(char) * size);
		AudioFileGetProperty(audioFile, kAudioFilePropertyMagicCookieData, &size, cookie);
		AudioQueueSetProperty(queue, kAudioQueueProperty_MagicCookie, cookie, size);
		free(cookie);
	}

	// allocate and prime buffers with some data
	packetIndex = 0;
	for (i = 0; i < NUM_QUEUE_BUFFERS; i++)
	{
		AudioQueueAllocateBuffer(queue, gBufferSizeBytes, &buffers[i]);
		if ([self readPacketsIntoBuffer:buffers[i]] == 0)
		{
			// this might happen if the file was so short that it needed less buffers than we planned on using
			break;
		}
	}
	repeat = NO;
	trackClosed = NO;
	return self;
}

- (void)setGain:(Float32)gain
{
	AudioQueueSetParameter(queue, kAudioQueueParam_Volume, gain);
}

- (void)setRepeat:(BOOL)yn
{
	repeat = yn;
}

- (void)play
{
	AudioQueueStart(queue, nil);
}

- (void)pause
{
	AudioQueuePause(queue);
}

#pragma mark -
#pragma mark Callback

static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer)
{
	// redirect back to the class to handle it there instead, so we have direct access to the instance variables
	[(GBMusicTrack *)inUserData callbackForBuffer:buffer];
}

- (void)callbackForBuffer:(AudioQueueBufferRef)buffer
{
	if ([self readPacketsIntoBuffer:buffer] == 0)
	{
		// End Of File reached, so rewind and refill the buffer using the beginning of the file instead
		packetIndex = 0;
		[self readPacketsIntoBuffer:buffer];

		// if not repeating then we'll pause it so it's ready to play again immediately if needed
		if (!repeat)
		{
			AudioQueuePause(queue);

			// we're not in the main thread during this callback, so enqueue a message on the main thread to post notification
			// that we're done, or else the notification will have to be handled in this thread, making things more difficult
			[self performSelectorOnMainThread:@selector(postTrackFinishedPlayingNotification:) withObject:nil waitUntilDone:NO];
		}
	}
}

- (void)postTrackFinishedPlayingNotification:(id)object
{
	// if we're here then we're in the main thread as specified by the callback, so now we can post notification that
	// the track is done without the notification observer(s) having to worry about thread safety and autorelease pools
	[[NSNotificationCenter defaultCenter] postNotificationName:GBMusicTrackFinishedPlayingNotification object:self];
}

- (UInt32)readPacketsIntoBuffer:(AudioQueueBufferRef)buffer
{
	UInt32		numBytes, numPackets;

	// read packets into buffer from file
	numPackets = numPacketsToRead;
	AudioFileReadPackets(audioFile, NO, &numBytes, packetDescs, packetIndex, &numPackets, buffer->mAudioData);
	if (numPackets > 0)
	{
		// - End Of File has not been reached yet since we read some packets, so enqueue the buffer we just read into
		// the audio queue, to be played next
		// - (packetDescs ? numPackets : 0) means that if there are packet descriptions (which are used only for Variable
		// BitRate data (VBR)) we'll have to send one for each packet, otherwise zero
		buffer->mAudioDataByteSize = numBytes;
		AudioQueueEnqueueBuffer(queue, buffer, (packetDescs ? numPackets : 0), packetDescs);

		// move ahead to be ready for next time we need to read from the file
		packetIndex += numPackets;
	}
	return numPackets;
}

@end

If you don’t understand what every line of this code means, don’t worry. Here is the simple instruction how to use it:

GBMusicTrack *backgroundMusic = [[GBMusicTrack alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"backgroundMusic" ofType:@"caf"]];
[backgroundMusic setRepeat:YES]; // or NO if you want to play it only once
[backgroundMusic setGain:1.0]; // volume
[backgroundMusic play];

Remember to import not only GBMusicTrack.h to the class with above lines of code, but also to add AudiToolbox.framework. As you see using GBMusicTrack class is quite simple, setRepeat:YES/NO method allows you to decide if you want your music to loop constantly, setGain to adjust the volume (remember: try to avoid setting the volume louder than 1.0) and play to start playing.

Please note: compile for the version 2.2 or higher!

Please note: many people complains that anything from this tutorial doesn’t work on iPhone Simulator. Don’t worry, it works on real device.

Playing the next track using GBMusicTrack:

As I told before, this class allows you to play one track at the time. To play next, you have to call the close method and initialize it again:

	[backgroundMusic close];
	[backgroundMusic initWithPath:[[NSBundle mainBundle] pathForResource:@"nextTrack" ofType:@"caf"]];

You can download my sample project: – my music controller I’ve written using the GBMusicTrack. You can fade the music in / out, play and pause and set the volume.

musiccontrollerscreenshot

xcodeproj

Download the project

Creating programmatically a spinner (UIActivityIndicator)

May 8th, 2009

UIActivityIndicator commonly named Spinner is used usually to tell the user that some process is currently in progress.

There are three types of UIActivityIndicators:

activityindicators From the left: gray (UIActivityIndicatorViewStyleGray), white (UIActivityIndicatorViewStyleWhite) and large white (UIActivityIndicatorViewStyleWhiteLarge).

To allocate any of them use the following code:

	UIActivityIndicatorView *myIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]];
	myIndicator.center = CGPointMake(160, 240);
	myIndicator.hidesWhenStopped = NO;

As you see this time you shouldn’t provide initWithFrame as long as large white style has got different dimensions then white (small) and gray. Instead, set its center point.

To add UIActivityIndicator to your view simple use addSubview method:

	[self.view addSubview:myIndicator];

By default, UIActivityIndicator displays a static image, what in most cases isn’t what you want. You can easily start and stop animating it by using two methods:

	[myIndicator startAnimating];
	[myIndicator stopAnimating]; 

Please note: the hidesWhenStopped=YES property causes the UIActivityIndicator disappear once you stopped animating it and appear again once you started animating.

If you need to know programatically what is the current state of UIActivityIndicator, you can use isAnimating property:

if ([myIndicator isAnimating]) NSLog(@"animating");
else NSLog(@"not animating");

UIActivityIndicator in status bar

spinnerstatusbarTo display a UIActivityIndicator use the UIApplication’s property: networkActivityIndicatorVisible.

	[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

and to disable:

	[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

That’s almost all about UIActivityIndicator. More can be found in UIActivityIndicator Class Reference in documentation.

Creating programmatically a label (UILabel)

May 8th, 2009

UILabel allows you to display a single line text, most of us think so. In fact it can be used to display multiple lines as well.

To create an UILabel programmaticaly, code:

	UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 100, 200, 100)];
To display a text within UILabel use:
	myLabel.text = @"Lorem...";

And of course to add UILabel to your view:

	[self.view addSubview:myLabel];

By default UILabel has a white background, you can remove the background or change the color easily:

	myLabel.backgroundColor = [UIColor clearColor]; // [UIColor brownColor];

Setting the font:

	myLabel.font = [UIFont fontWithName:@"Zapfino" size: 14.0];

You can easily get a list of available fonts and the preview of them, using the free application Fonts! by AppEngines. UILabel can display text in different color then black and shadows as well:

	myLabel.shadowColor = [UIColor grayColor];
	myLabel.shadowOffset = CGSizeMake(1,1);
	myLabel.textColor = [UIColor blueColor];

Best effect you will obtain by setting a color using RGB while setting the alpha half transparent. shadowOffset gives you the ability to set the shadow’s angle.

By default text is horizontally aligned to the left, you can set it either to the right or to the center.

	myLabel.textAlignment = UITextAlignmentRight; // UITextAlignmentCenter, UITextAlignmentLeft

Text wrapping

As I told you, UILabel can display multiple lines of text, and to achieve it you need to set two properties lineBreakMode and numberOfLines:

	myLabel.lineBreakMode = UILineBreakModeWordWrap;
	myLabel.numberOfLines = 2; // 2 lines ; 0 - dynamical number of lines
	myLabel.text = @"Lorem ipsum dolor sit\namet...";

That’s all about UILabel. More can be found in UILabel Class Reference in documentation.

Creating programmatically a switch (UISwitch)

May 8th, 2009

UISwitch is one of the simplest structure of UIKit. To create it programmatically, code:

	UISwitch *mySwitch = [[UISwitch alloc] initWithFrame:CGRectMake(50, 100, 0, 0)];
Above line allocates an UISwitch and sets its frame. UISwitch has fixed dimensions (about 95 px long, 27px high), so you have to set the coordinates for top-left corner. Width and height are ignored, you can’t make switch smaller or bigger by changing the frame.

To add it to your view you should use this code:

	[self.view addSubview:mySwitch];

Be default switch is set to Off. You are able to change it’s state programmaticaly using setOn method:

	[mySwitch setOn:YES animated:YES];

To switch it back to Off use:

	[mySwitch setOn:NO animated:YES];

In both above cases UISwitch changes it state with animation (animated:YES), use animated:NO to change state without animation.

To check UISwitch current state use on property, example:

	if (mySwitch.on) NSLog(@"switch state: on");
	else NSLog(@"switch state: off");

To prevent changing the UISwitch state set the enabled property to NO:

mySwitch.enabled = NO; // or YES to reenable it

As I said before UISwitch is very simple structure – any action – touching it causes UISwitch to change it’s state, so to call any method by changing the UISwitch you have to add a target for ValueChanged event:

	[mySwitch addTarget:self action:@selector(myMethod) forControlEvents:UIControlEventValueChanged];
or
	[mySwitch addTarget:self action:@selector(myMethod:) forControlEvents:UIControlEventValueChanged];
If you need a id to mySwitch object.
That’s all about UISwitch. More can be found in UISwitch Class Reference in documentation.

Planning a bigger application

May 6th, 2009

Before you start developing a bigger application you should plan, how the whole project’s classes should talk to each other. You should take a sheet of paper and draw the schema, describing the most important classes

I don’t want you to draw the flowchart describing every single algorithm, every if statement or loop like this one below.

flowcharts

When I started programming, every next view (UIViewController) of any application, was allocated by previous view and there was no simple way to return from the last one to the first one. Either I had to start programming the app from the beginning to fix it or had to call dealloc method which should be called only automaticaly when the application is quited.

Since few months I’ve new idea, I would like to show you. Below is the schema – the frame of most my new apps:

schema

Legend:

  • A – App Delegate, the first objective-c file, autmatically allocated when the application starts.
  • B – Main Controller – this is the “application central unit” that is responsible for displaying some part of the menu, receiving messages from any classes it allocates and delivering them somewhere else.
  • C – Settings – class that provides an interface to adjust the volume of sounds / music, way of steering (arrows / accelerometer) etc.
  • D – heart of the sounds and the background music.
  • E – in this case, the funniest part of app, the hardest to code, responsible for loading a level, tracking the game progress, check for collisions and more.
  • F – provides a form for entering new high score
  • G – displays the best times / scores for any level.
  • H – other simple views that almost every application has.

How does it all work?

Let’s start with arrow #1. In fact, my App Delegate [A] has one single task - to allocate a next view, in this case the Main Controller [B] and I never use it again.

Once MainController [B] is allocated the Main Menu it provides is visible on the screen. Any function user presses is allocated and/or displayed via transition or not – it’s up to me.

#2, #3, #9
When users changes the volume in the Settings [C]
, the Main Controller [B] is informed about that change and it’s passed to the Sound and Music Engine [D] to perform necessary tasks. If it was other option, for sure Main Controller [B] now, how to deal with it.

#4, #5, #6
When users starts/load/continue the game, Game Controller [E]
is checking each parameter of the game. When the game is canceled it returns to Main Controller [B], when the game is over, it checks, if the score is enough to appear on the high scores list. If yes it tells the Main Controller [B] to display the form – New High-Scores [F]. Once form was compleated, #6 Main Controller [B] updates the list of high scores and displays the records High-Scores View [G].

#7
User can easily visit the High-Scores View [G] and quit to Main Controller [B] without any difficulties.

#8
Any instructions, information are simply more or less polished views, so one class is enough to handle them all. It’s easy to navigate from Main Controller [B] to Others [H] and vice verse.

#9
Whatever user does in the application, Main Controller [B]
tells the Sound Engine [D], if it should play a simple button pressed sound or change the background music.

I’ve just shown you my idea of programming bigger applications. Of course it doesn’t mean that whatever I do, is made with only eight classes. For example Game Controller has many other classes it includes to help him checking what is going on in the game and they talk only with Game Controller and it decides if it should send a message to Main Controller to finish the game or continue it.

Hope this helped.

Tables

May 5th, 2009

Tables – strictly UITableView, are very often used in utilities, content managers, lists and more. There are many examples of tables in applications that came with every iPhone:

  • Phone application: Favourites, Recents, Contacts
  • SMS application: list of contacts you recently text messaged (not sure about the chat bubbles – it can be UIScrollView)
  • Calendar application: list of Calendars, List view of selected calendar
  • Photos application: list of Photo Albums
  • YouTube application: every tab contains UITableView
  • Weather application: flip-side view where you can add cities
  • Notes application: list of notes
  • Settings application: whole application
  • App Store application: every tab
  • Clock application: World Clock, Alarm, Stopwatch

tableview1 tableview2 tableview3 tableview4 tableview5

As you see UITableView is not only a white list with black text on it. Table (strictly UITableViewCell) can be customized in many ways, it can not only have many sections, headers, footers, edit and reorder functionality but even a custom view.

Let’s start

Create a UIViewController and View XIB. Name them all whatever you like (in my project I use: MyView.h, MyView.m, MyView.xib). In App Delegate add standard lines:

#import "MyView.h"

@class MyView;

and:

	MyView *viewController = [[MyView alloc] initWithNibName:@"MyView" bundle:[NSBundle mainBundle]];
	[window addSubview:[viewController view]];

Open MyView.xib and set that it’s class is MyView, add UITableView and UIToolbar or UINavigationBar on top – might be important later. The view should look like this:

interfacetableprepare

Don’t close Interface Builder and edit MyView.h in Xcode. Add two protocols to UIViewController: UITableViewDelegate (responsible for a lot of actions with tables) and UITableViewDataSource (providing data to be displayed in the table). Add also an IBOutlet of UITableView. Although almost everything will be passed to table you created in Interface Builder via protocols, but this object might be helpful in some cases I will discuss later.

MyView.h

#import <UIKit/UIKit.h>

@interface MyView : UIViewController <UITableViewDelegate, UITableViewDataSource> {
	IBOutlet UITableView *myTable;
}

@end

Compile your project and return to Interface Builder and make connections. myTable with UITableView, view with View and click on UITableView again, and in Table View Connections, connect dataSource and delegate with File’s Owner. Save MyView.xib.

Before you can debug your application you need to implement two method that are required to use UITableView:

  1. numberOfRowsInSection – how many rows each section has
  2. cellForRowAtIndexPath – creates cells – UITableViewCell

So you need to implement above methods in your class:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
	return 20; // every section has 5 rows
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	NSLog(@"cellForRowAtIndexPath called");
	static NSString *CellIdentifier = @"Cell";
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
	if (cell == nil) { NSLog(@"new cell");
		cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
	}
	cell.text = [NSString stringWithFormat@"Cell #%i"indexPath.row];
	return cell;
}

simplestableview

If you Build & Go your project, you should get:

As you see numberOfRowsInSection method is quite obvious. In above example. I’m telling – preparing the table to display 8 rows in each section. As long as I didn’t tell the table how many sections there are in the table it assumes that I want to have 1 section. cellForRowAtIndexPath is “a little bit” more complex. Go to Debugger Console (Command+Shift+R) to open logs. Inside this method I added two functions to log whenever the method was called and whenever the cell is empty object (nil). To better understand this change the numberOfRowsInSection to return 20 for example, Build & Go your project and study logs while scrolling the UITableView. Conclusion: because of memory management and overall performance cellForRowAtIndexPath is only called to show the visible cells, and when you scroll back to see the cells that have been already loaded, cellForRowAtIndexPath is called again, but it remembers their settings and customization and reuse them instead of allocating again. If you don’t understand this, treat it like a template you need to use in most cellForRowAtIndexPath method. indexPath gives you two information, what is the section of current cell (indexPath.section) and row (indexPath.row).

Dynamic data source

dynamicdatasimulatorIn this tutorial I will only show you simple examples of using UITableView, but you could prepare NSMutableArray to be displayed in UITableView. To do so, first add an NSMutableArray in header of your class:

	NSMutableArray *dataForTable;

and in viewDidLoad method:

- (void)viewDidLoad {
	dataForTable = [[NSMutableArray alloc] initWithObjects:
					@"First row",
					@"Second row",
					@"Third row",
					nil];
	[dataForTable retain];
	[super viewDidLoad];
}

Now, numberOfRowsInSection shouldn’t return the static number 8, 20, or any other, but:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
	return [dataForTable count];
}

And cellForRowAtIndexPath shouldn’t tell you what is the row’s number, but the object (NSString) from NSMutableArray.

	cell.text = [dataForTable objectAtIndex:indexPath.row];

Please note: once you changed the objects in dataForTable you can call [myTable reloadData]; to redraw the table with new data.

Table Styles

tablestyleplaintablestylegrouped
There are two styles you can apply to UITableView. Plain (left, default) has “sticky headers”, Grouped (right) has rounded edges and stripped, gray background. Altough, so far there was no headers displayed by your code you can compare these two styles in Phone and Settings application. You can switch the style in Xcode, but as long as you are using Interface Builder, simply go to Table View Attributes and change the Style property.

Sections

By default there is one section in every UITableView. If you want more – seperate one group of rows from the other, you have to implement the numberOfSectionsInTableView method:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 2;
}

Above method will gave us one of these two results:

sectionsplain sectionsgrouped

You should notice two things. In grouped style the space between sections is clearly visible, but not in plain style as long as you don’t provide headers’ names. The second: both sections are identical, that’s because numbersOfRowsInSection is returning always the same value and the same is with cellForRowAtIndexPath. You can easily change it. You could do it dynamicaly, as shown before, but I will use switch instruction:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
	switch (section) {
		case 0:
			return 5;
			break;
		case 1:
			return 2;
			break;
	}
	return 0;
}

customsectionsAnd (instead of cell.text = [NSString ...);

	switch (indexPath.section) {
		case 0:
			cell.text = [NSString stringWithFormat:@"Happy Cell #%i",indexPath.row];;
			break;
		case 1:
			cell.text = [NSString stringWithFormat:@"Sad Cell #%i",indexPath.row];;
			break;
	}

This code should be easily understandable for you. I've not done anything else but used switch instruction to return / assign different values for given cases. Since now, I'm going to skip switch instructions in sample codes, you should implement them on your own easily.

Header and footer

headerfootergrouped headerfooterplain

As you see the grouped UITableView looks amazing with headers and footers, but for plain style, you should use only headers because it doesn't look good - header and footer look exactly the same. To achieve something like this you need to implement these two methods:

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
	return @"Section title";
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
	return @"Footer title";
}

Please note: if you return nil header / footer won't be displayed

Section index

tableindexWhen you scroll the contact list, the small letters A-Z on the right are very helpful, aren't thay? It's very easy to implement something like this.

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
	NSMutableArray *index = [[NSMutableArray alloc] initWithObjects:@"A",@"B",@"C",@"D",nil];
	return index;
}

In index array you have to provide that titles that will be displayed on the whole height of UITableView on the right. The titles don't have to be the same as the headers' titles (but their should, or be similar). If you provide more index titles than there are sections in UITableView, most of them will navigate to the bottom section.

Please note: sectionIndexTitlesForTableView should return a NSArray object, but you are returning NSMutableArray, don't worry, NSMutableArray is just a subclass of NSArray so this is acceptable and doesnn't cause any logical errors.

Please note: section index works only in plain style of UITableView.

Pressing the cell

cellpressedThe cell usually links to some actions. Here is the sample action to displays an alert with information what cell was selected.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Cell pressed" message:[NSString stringWithFormat:@"Cell #%i in section #%i",indexPath.row,indexPath.section] delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
	[alert show];
	[alert release];
}

And there is one "visual thing" you could add to maintain the nice aesthetics. Once cell (UITableViewCell) was pressed it stays selected - highlighted until you press any other cell or scroll the UITableView. Just add this line of code to above method to fix it:

	[tableView deselectRowAtIndexPath:indexPath animated:YES];

Accessory Buttons

accessorybuttonsAccessory Buttons are this nice tiny usually arrow-images on the right side of UITableViewCell with custom actions once tapped (only #2). There are 3 types of accessory buttons:

1. UITableViewCellAccessoryCheckmark
2. UITableViewCellAccessoryDetailDisclosureButton
3. UITableViewCellAccessoryDisclosureIndicator

- (UITableViewCellAccessoryType)tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath {
	return UITableViewCellAccessoryCheckmark;

}

If you don't want to display any accessory button for some cells simply return UITableViewCellAccessoryNone for them. If you used DetailDisclosureButton (nice blue button with arrow) you can perform some tasks, once it was tapped:

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Accessory Button pressed" message:[NSString stringWithFormat:@"Accessory Button for Cell #%i in section #%i",indexPath.row,indexPath.section] delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
	[alert show];
	[alert release];
}

Please note: above method works only with UITableViewCellAccessoryDetailDisclosureButton and it doesn't call any method connected with selecting a row.

Height and indent

sizeplain sizegrouped

If needed you can change the height of any cell or it's indend as shown screenshots above. By default height=45 and indent=0.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
	if (indexPath.row == 2) return 90;
	return 45;
}
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath {
	if (indexPath.row == 3) return 2;
	return 0;
}

Custom Cell

customcellibAs you see there are many possibilities of customizing the cell, but it's not enough. You can create a new .xib file and a UITableViewCell controller and design the cell like any view so far.

In Xcode add new class of type UITableViewCell and View XIB (name them CustomCell.*).

In CustomCell.h any any IBOutlets you need, labels, slider, switches... whatever.

Open CustomCell.xib and delete the View and add add UITableViewCell to CustomCell.xib. In Table View Cell Identity set class = CustomCell (not File's Owner this time). If you want you can adjust the size in Custom Cell Size...

customcellsize

or by dragging the bottom right triangle. Set it's features in Custom Cell Attributes, add some objects - treat it like a typical UIView.

Please note: this time, UITableViewCustomCell is a class of CustomCell, not File's Owner as usuall. So when you want to make connections go to Custom Cell Connections, forget about File's Owner this time.

OK, your custom cell is prepared right now, now it's time to show it in the UITableView. First you need to import CustomCell.h to your class that manages table (#import "CustomCell.h" and @class CustomCell).

Now you have to reorganize the table. Let's assume that your custom cell will be displayed only once in seperated section - in numberOfSectionsInTableView tell that there will be one more section and in numbersOfRowsInSection return 1 for a given section (add next case for existing switch).

Hope above wasn't difficult because this will be: now it's time to extend cellForRowAtIndexPath method. I'm like many times before using switch and I assume that custom cell will be displayed in 4th section:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	// custom cell only
	if (indexPath.section == 3) {
		static NSString *CellIdentifier = @"CustomCell";
		CustomCell *cell = (CustomCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
		if (cell == nil) {
			NSArray *outlets = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
			for (id currentObject in outlets) {
				if ([currentObject isKindOfClass:[UITableViewCell class]]){
					cell =  (CustomCell *) currentObject;
					break;
				}
			}
		}
		cell.myLabel.text = @"Some text";
		return cell;
	}

	// other cells:
	// ...

So, when it comes to 4th section (indexPath.section == 3), the CustomCell nib (.xib) file is loaded, and in for loop, all it's objects are assigned as properties to cell. Although you set the size of custom cell in Interface Builder, but you must also tell UITableView to expect this by extending heightForRowAtIndexPath method:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
	if (indexPath.row == 2) return 90;
	else if (indexPath.section == 3) return 95;
	return 45;
}

customcellsimulator

Please note:

you can read more about UITableView in documentation: check descriptions of UITableViewDelegate and UITableViewDataSource protocols. You will find there more options and methods allowing you to modify the UITableView - delete, add, edit, reorder rows and more.

Conclusion:

That was pretty much for the one tutorial. Hope you understand everything. Don't worry, tables are something that young developers try to avoid, but they are not so difficult at all. I provided for you a sample project. Remember to change occasionally in Interface Builder the style of UITableView (plain / grouped). Some methods are disabled by comments, you can enable them by yourself.

xcodeproj

Download the project

Instruments

May 5th, 2009

instrumentsscreenshot

dev_instrumentsmediumInstruments is the great tool to measure the performance of your application, problems, object allocations, memory leaks and more.

I personally don’t use it quite often. Why? I don’t have problems with memory leaks, because I know where I have to release some objects, where to keep them. How about performance? I measure the performance by debugging my application on the real device. Please remember, that iPod touch 2nd gen offers better performance because of it’s faster CPU. If your app runs smoothly on iPhone it will run even better on iPod touch but not vice versa!

OK, so far I told you my atitude to Instruments, but I’ve never said that I don’t use this tool at all. Of course I use. The goal of your application/game should’t be only the metter of how much fun or usability it provides, but also how long it takes to load, during gameplay not to show Loading screen every minute, uses as much memory as possible and cleans after app terminates.

Using the information from Instruments you have the great opportunity to improve your application. Use your application and track the graphs Instruments provides, optimize methods and delete any objects that has been allocated, but no longer needed.

In fact it’s difficult to describe, how you should use Instruments. The results are different for each application and differently interpreted by each developer. It’s up to you how Instruments can help you in development. If you can’t self-learn how to use this tool, I recommand you to read Instruments’ help.

Short sounds

May 5th, 2009

Related tutorials:

Sounds are very important to put some life into your application, usually the game.

What is the difference between short sounds and long sounds? Let’s say, you are making a game and there is a constantly looping (or changing the track once played) background music. This is certainly not a short sound and it will be discuss somewhere else. You can play a short sound once you press the button, hit the wall, an alert occurred, a weapon was used… This kind of sounds could also be played as a background music but there is one problem with playing few background musics on iPhone… The iPhone processor can deal only with one background music and many sounds, that’s why music played from your library on iPhone / iPod touch stops once any app starts to play background music.

Sounds cannot be longer the 30 seconds and you should convert them before adding to the project. How? Open Terminal application on your mac and use the afconvert:

/usr/bin/afconvert -f caff -d LEI16 input_file output_file

Example:

MBP-Krzysiek:~ Krzysiek$ /usr/bin/afconvert -f caff -d LEI16 /Users/Krzysiek/Desktop/samlpe\ music.mp3 /Users/Krzysiek/Desktop/samplemusic.caf
terminal
Please bear in mind that .caf aren’t like .mp3 – the lossless format, that’s why after conversion the sound file is usually bigger than original. That’s why you shouldn’t have original files in .mp3 as shown on example, but even you have .caf, convert it anyway. afconvert converts almost any sound format I know (but I don’t know many, I’m not DJ).
OK, your sound is ready to add to your project. Now in Xcode you need to write a class responsible for playing a sound. I will show you two ways: playing a sounds using:
  • SystemSound
  • AVAudioPlayer

SystemSound

SystemSound plays any sound without affecting the background music of your application or music from iPod. It doesn’t provide any volume control, user can control volume by changing the master volume of the device. Before you start to code, you need to add an AudioToolbox framework to your project (if you don’t know how: ctrl-press any existing .framework, Reveal in Finder and drag and drop the AudioToolbox.framework to the Frameworks group).

Add a new class (name it sounds) to your project, choose NSObject subclass, but in fact, it doesn’t metter, if you replace all code in header and implementation file with these provided by me:

sounds.h

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioServices.h>

@interface sounds : NSObject {
	SystemSoundID soundID;
}
-(id) initWithContentsOfFile:(NSString *)path;
-(void)play;
@end

sounds.m

#import "sounds.h"

@implementation sounds
-(id) initWithContentsOfFile:(NSString *)path {
	self = [super init];
	if (self != nil) {
		NSURL *filePath = [NSURL fileURLWithPath:path isDirectory:NO];
		AudioServicesCreateSystemSoundID((CFURLRef)filePath, &soundID);
	}
	return self;
}
-(void)play {
	AudioServicesPlaySystemSound(soundID);
}
-(void)dealloc {
	AudioServicesDisposeSystemSoundID(soundID);
	[super dealloc];
}
@end

Import sounds.h (#import “sounds.h”) to any class (for example game.h) that will call sounds class to play a short sound. If the sound is played many times (like in most cases) add in header the object:

	sounds *mySound;

or

	sounds *mySound1, *mySound2, *mySound3; // , *mySound4 ... *mySound(n)

or

	sounds *mySound[3];
Before any sound can be played you need to allocate it. While allocating you have to call the method initWithContentsOfFile and provide the path to the resource – sound file.
	mySound = [[sounds alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"myFunnySound" ofType:@"caf"]];
Now mySound object is ready to play myFunnySound.caf.
To play call the play method: [mySound play];. That’s all about SystemSounds.
Please note:
System Sounds can also toggle vibration, but it will be discussed in other tutorial.

AVAudioPlayer

AvAudioPlayer plays any sound also without affecting the background music of your application, but it terminates the music from iPod. It gives the user the ability to control the volume within the application (if you provide a slider or any other interface to adjust the volume), or by changing the master volume of the device. Before you can use AVAudioPlayer you need to add AVFoundation.framework to your project.

Please note:
Using AVAudioPlayer is much simpler than SystemSounds. I implemented a class to use SystemSounds, because I don’t like using such a long method names (AudioServicesPlaySystemSound) when they are called frequently. To play sound with AVAudioPlayer I need only 2-3 lines of code so writing a class to handle it is pointless and useless for me.

To prepare the sound you need to repeat almost the same steps I told in SystemSounds. Let’s add in the header file of your class an instruction to import AVAudioPlayer.h framework:

#import <AVFoundation/AVAudioPlayer.h>

And the AVAudioPlayer object with our sound:

	AVAudioPlayer *myAVsound;

To start, and assing the sound file you also need to provide a path to that resource:

	myAVsound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"myFunnySound" ofType:@"caf"]  ] error:NULL];

The method to play this sound couldn’t be simpler:

	[myAVsound play];

… and to adjust the volume:

	myAVsound.volume = 0.6;

The volume can be adjusted also during the sound is being played.

Please note:
The given volume should be a number from range <0.0,1.0>
. When the volume=0, user won’t hear the sound at all (O RLY?). You could also set the volume=2.0 or 10.0 but be careful. Instead of the louder sound you can get an unbearable to here noise (especially when the sound is saved in low quality), it can be harmful to user’s ear, if he uses headset, or can damage the device’s speaker.

BTW:
AVAudioPlayer allows you to loop the sound (myAVsound.numberOfLoops = 2;).

Please note:

I’ve heard many problems with playing the sounds with iPhone Simulator, in other words, the project cannot be compiled to run on iPhone Simulator. I personally have this problem, but as long as I participate in iPhone Developer program. I guess (but I’m not sure) that is because of computer you use. I use MacBook Pro Mid/Late 2007 (MA895) 2.2 GHz. As long as I know, this problem doesn’t occur on MacBook Pros shipped with unibody enclosure.

That’s almost everything you need to know about short sounds. You can download the sample project below.It allows you to play a sound (included in the project) via SystemSound and via AVAudio Player with slider adjusting the volume.

screenshotsounds

xcodeproj

Download the project