Blog Projects
Student in IT & Web develop{design}er
Beatbox App on iPad

Beatbox App on iPad

How to use iOS SDK to make a fun app

I’m currently learning how to develop apps on iOS (iPhone, iPad, iPod) and I share with you how I’ve done to make one of my first app. As I am starting the development on iOS, this tutorial can be useful for developers beginning on that platform.


The App

This app is made with Xcode 4 and run on iOS 4.3, but before starting we need to define the specifications of the app. I have made it simple ;)

The app is a beatbox, so we will need some buttons to play a unique sound on each one when the user touches it. But if we want play to some sounds, we actually need to have these sounds, so we will let the user record his own, with a record mode. Finally the user will have the possibility to save a name on each button, so he can remember easily the button’s sound.
Here is a quick video showing what the app is able to do. (You will see that I’m not a musician :D).



Setup of our new app

Project Files

First, we create a new window based project. This beat box can be done with a View based project, but I think is better to start from nothing, to understand how everything works.

Window in xcode 4 to choose a new project type  Window in xcode 4 to set information about the new project

Secondly, we will need two UIViewController. The first one controlling a switch making the app entering in a record mode, and some instances of our second UIViewController. This second UIViewController will control two UIView (One with a button, and another with a text field allowing the user to change the button’s name) and perform the playing/recording actions.

Schema showing the two different controller of the project

So we create our two UIViewController subclasses (CMD+N) called PadViewController and PadButtonViewController. Don’t forget to click on "With XIB for user interface".

Create a new subclass of UIViewController

Interface

Now we can make our interface with connections between the Controller and the View.
Open PadViewController.h and add these attributs which will let us connect the controls to the controller in Interface Builder.

@interface PadViewController : UIViewController {
    NSMutableArray *pads; //will be a collection of PadButtonViewController
}

@property(nonatomic, retain) IBOutlet UISwitch *rec;
@property(nonatomic, retain) NSMutableArray *pads;

@end

And in PadViewController.m inside the implementation:

@synthesize pads;
@synthesize rec;

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations - here Landscape Left and Right
	return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft
            || interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}

Now we can open PadViewController.xib and make our interface. We will need to place two labels and one switch. To connect the switch to our controller, just ctrl-click on File's Owner (the orange cube) and drag to the switch contol. After that, make sure that the switch state is on off. You should have something like this :

Screen of the main interface  File's Owner Connections of PadViewController.xib

As we are done with the PadViewController interface, we can start the PadButtonViewController interface, so in PadButtonViewController.h we put this :

@interface PadButtonViewController : UIViewController {
    int identifier;
}

@property(nonatomic, retain) IBOutlet UIButton *infoButton;
@property(nonatomic, retain) IBOutlet UITextField *textField;
@property(nonatomic, retain) IBOutlet UIView *moreView;
@property(nonatomic, retain) IBOutlet UIButton *pad;
@property(nonatomic) int identifier;

@end

And in PadButtonViewController.m we put inside of the implementation :

@synthesize pad, infoButton;
@synthesize moreView;
@synthesize textField;
@synthesize identifier;

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

Then in PadButtonViewController.xib we can make our interface; First delete the current view, and drag and drop two views, then change their sizes :

View in the list of interface elements Configuration of the view's size

In the first view we can put a button with a custom type, which will take the same size of the view. This button need to have a dark background.
In the second view, which will be our "moreView", we put a label and a text field.
In the two view we place a button in a bottom right with type "Info Dark" or "Info Light". The Info Button on the first view need to be hidden. It can be done in the Attributes Inspector > View > check the Hidden.
Now we can make connections between File’s Owner and interface elements. (The infoButton IBoutlet have to be connected only with the info button on the first view)

Screen of the button interface  File's Owner Connections of PadButtonViewController.xib

To mix all these interfaces, first of all we call the PadViewController when the app is launched in the My_BeatBoxAppDelegate.
So in My_BeatBoxAppDelegate.h :

#import "PadViewController.h"

@interface My_BeatBoxAppDelegate : NSObject <uiapplicationdelegate> {
    PadViewController *padView;
}

And in My_BeatBoxAppDelegate.m :

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    padView = [[PadViewController alloc] init];
    [self.window addSubview:padView.view];
    [self.window makeKeyAndVisible];
    return YES;
}

Now we have to create 6 PadButtonViewController in PadViewController and put them in the middle of the view.
So we import the PadButtonViewController.h in the PadViewController.h, and when the view is loaded we create these buttons :

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    pads = [[NSMutableArray alloc] init];
    for (int i = 1; i <= 6; i++) {
        PadButtonViewController *pad = [[PadButtonViewController alloc] initWithNibName:@"PadButtonViewController" bundle:nil];
        [pads addObject:pad];
        pad.identifier = i;
        [self.view addSubview:pad.view];
    }
}

As you can see I’m setting an identifier on each PadButtonViewController. It will be useful to know which sound to load when the user touch one of these buttons.
To place the view at the good place we can set the frame on each one in their respective viewDidLoad (PadButtonViewController.m) :

- (void)viewDidLoad
{
    [super viewDidLoad];
    if (identifier != 0) {
        
        int marge = 30;
        
        int width = 225;
        int height = 200;
        
        int line = 0;
        int pos = (identifier-1);
        if (identifier > 3){
            line = 1;
            pos = pos - 3;
        }
        self.view.frame = CGRectMake( 144.5 + (pos * (width+marge)), 169 + (line*(height + marge)), width, height);
    }
}

Oh, and for the good practice, we have to nil every Outlets in the viewDidUnload :

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    pad = nil;
    infoButton = nil;
    moreView = nil;
    textField = nil;
}

We do the same in for PadViewController :

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    rec = nil;
}

We are done with the interface, now we need some interactions.


Interactions

Our app will have two states :

  • Playing Mode
  • Recording Mode

So the switch on the PadViewController, will help us know in which mode we are. But in PadButtonViewController we need to add an attribut (Boolean) to know in which mode the button is, and another one to know if the user is on the more Info view. So in PadButtonViewController.h, inside the @interface brackets we add :

BOOL recordingMode;
BOOL infoMode;

and after the bracket :

@property(nonatomic) BOOL recordingMode;
@property(nonatomic) BOOL infoMode;

- (IBAction)changePadName;
- (IBAction)moreInfo;
- (IBAction)touchDown;
- (IBAction)touchUp;

You can see that we add some IBAction methods, and we need to define them in the .m file, inside the implementation :

@synthesize recordingMode, infoMode;

//Called when the user type something in the text field on the moreInfo view
- (IBAction)changePadName
{

}

//Called when the user touch the info buttons in the bottom right of the views
- (IBAction)moreInfo
{

}

//Called when the user touch the button 'pad'
- (IBAction)touchDown
{

}

//Called when the user touch up the button or drag his finger outside of the button
- (IBAction)touchUp
{

}

Now we can go in the PadButtonViewController.xib file and connect these IBAction to the good interface elements. You should have that in the File's Owner Connection Inspector:

File's Owner Received Actions of PadButtonViewController.xib

The PadViewController need just one IBAction. It will be called when the value is changed on the switch. So in PadViewController.h :

- (IBAction)changeState:(id)sender;

and in the .m file :

- (IBAction)changeState:(id)sender
{

}

Now we can connect it to the switch in the .xib file.


The change of state

As I said before, this app has two states. The user can use the switch to define the state he want to use. So when it's turned on, the PadViewController has to say to all the PadButtonViewController that they have to change their state to a recording mode; And in playing mode when it's turned off.
The user also need to know in which state the app is. So we will change the buttons background color to red.

So inside the "changeState" method in the PadViewController.m file put this code :

UISwitch *theSwitch = (UISwitch *)sender;
for (PadButtonViewController *pad in pads) {
        [pad setRecordingMode:theSwitch.on];
}

So we set the switch's state to our PadButtonViewControllers by setting the "recordingMode" attribute. But in order the change the button's color, we have to override the setter method of this attribute in the PadButtonViewController.m :

-(void)setRecordingMode:(BOOL)value
{
    recordingMode = value;
    [UIView animateWithDuration:0.7
                                        delay:0 
                                      options:UIViewAnimationOptionBeginFromCurrentState 
                                 animations:^{
                                     if (recordingMode) {
                                         //Recording color
                                         pad.backgroundColor = [UIColor redColor];
                                     }else{
                                         //Playing Color
                                         pad.backgroundColor = [UIColor colorWithWhite:0.25 alpha:1];
                                     }
                                 } 
                               completion:nil];
    
    infoButton.hidden = !value; //if in recording mode we show the info button so the user can go on the moreView
}

You can see here that we are making an animation. This animation is actually making a smoothly change of color. After that we are showing our info button only if the PadButtonViewController is in recording mode.

Then we have to make the transition between the button in recording mode and the more view. We will use the flip animation. So in the the "moreInfo" method add this code :

[UIView beginAnimations: @"flip" context: nil];
[UIView setAnimationDuration: 0.5f];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:self.view cache: YES];
    
infoMode = !infoMode;
if (infoMode)
{
    [self.view addSubview:moreView];
}
else
{
    [moreView removeFromSuperview];
}
[UIView commitAnimations];

We now have an issue! When the user flip on the "more view" of one or more PadButtonViewController, and switch to the playing mode, the more view still here. To solve this just put this line of code in the for loop of the changeState method (PadViewController.m) :

if(!theSwitch.on && pad.infoMode) [pad moreInfo];

Now we need to define the default states of our PadButtonViewControllers. The right place to do that is in the initWithNibName:bundle: method :

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        identifier = 0;
        infoButton.hidden = YES;
        recordingMode = NO;
        infoMode = NO;
    }
    return self;
}

What's next ?

Now that we can navigate through our application, the next step will be to add some interaction with the main button to perform some playing and recording. We will use the AVFoundation framework to do this, and I'll show you how in a future article.

Posted on July 26, 2011


Add a Comment

Add a Comment
Full Name
Email
Message