A button object

Gone uses the technology for playing a long sound, i.e., background music. This app uses the technology for playing a short sound i.e., a sound effect.

In iOS 6, a button had an outline. In iOS 7 there’s no outline, so I gave it a yellow color.

Source code in Button.zip

  1. main.m
  2. Class ButtonAppDelegate creates, plays, and destroys the sound effect.
  3. Class View contains the button.

Create the project

Add the AudioToolbox framework to the project. Select the project Button at the top of the Xcode Project Navigator. In the center pane of Code, scroll down to Linked Frameworks and Libraries. Press the plus sign, select AudioToolbox.framework, and press Add.

MP3 is on Apple’s list of supported audio formats. Go here and save the file China-sound--Lo-Fi-Preview-.mp3 onto your Mac desktop, renaming it chinese.mp3. Use the Macintosh command-I to make sure it’s really an mp3 file. Warning: when saving this file in Safari, you must specify Page Source.
File → Save As…
Export as: chinese.mp3
Format: Page Source
Save
If you don’t specify Page Source, you’ll end up with a web archive file that will cause the AudioServicesCreateSystemSoudID function to return error code –1500 (a.k.a. the notoriously unhelpful kAudioServicesSystemSoundUnspecifiedError).

Then drag chinese.mp3 into the Supporting Files folder in the Xcode Project Navigator.
Choose options for adding these files
Destination ☑ copy items into destiation group’s folder (if needed)
Finish

The button and its target

The initWithFrame: method of the View creates the button and centers it in the View. Our button has the most modest style, rounded rect. The button’s title, however, is in flaming red, set in initWithFrame: and retrieved in the touchUpInside: method of the application delegate. Each “control state” of the button can have a different title. Our title was the one for the normal state.

When the user presses and releases the button, it can send any message to any object. The object is called the target of the release. A control such as a button can have many target objects, and the target objects do not have to adopt any protocol. (Later we will see that a control can have only one “delegate” object, and that the delegate must adopt a protocol.) Let’s have the button send the touchUpInside: message to the View object.

To arrange this, the View object passes three arguments to the button’s addTarget:action:forControlEvents: method. The second argument is the expression @selector(touchUpInside:), called a selector, which specifies which method of the target should be called. The first argument speciefies which object the method belongs to. And the UIControlEventTouchUpEventInside is one of several possible finger events. (Touch down or touch up? Touch up inside or touch up outside?)

The selected method, touchUpInside: must return void.

touchUpInside:

The method touchUpInside: could be the target of several different buttons. To tell it which button was pressed, the button is passed to it as an argument. Since our app has only one button, we already know which one was pressed.

To prove that the button sent the touchUpInside: message to the application delegate, the touchUpInside: method calls NSLog and can make the cellphone vibrate. But NSLog is too tame and an iPod Touch cannot vibrate. Let’s play a sound file instead.

The call to AudioServicesPlaySystemSound starts the sound file playing, and returns without waiting for the sound to finish. We must not call AudioServicesDisposeSystemSoundID immediately afterwards, so I put the call to AudioServicesDisposeSystemSoundID in the applicationWillTerminate: method of the application delegate. Now that sid is now mentioned in two methods, it had to be an instance variable rather than a local variable inside one of the methods. I wanted to name it id, but id is a keyword in the language Objective-C.

The output of the NSLogs

2014-06-08 21:33:25.217 Button[2998:60b] bundle.bundlePath == "/Users/myname/Library/Application Support/iPhone Simulator/7.1-64/Applications/D680F8B6-902D-4126-B19F-83390882387A/Button.app"

2014-06-08 21:33:25.220 Button[2998:60b] filename == "/Users/myname/Library/Application Support/iPhone Simulator/7.1-64/Applications/D680F8B6-902D-4126-B19F-83390882387A/Button.app/chinese.mp3"

2014-06-08 21:33:25.220 Button[2998:60b] url == "file:///Users/myname/Library/Application%20Support/iPhone%20Simulator/7.1-64/Applications/D680F8B6-902D-4126-B19F-83390882387A/Button.app/chinese.mp3"

2014-06-08 21:33:30.626 Button[2998:60b] The "Chinese sound effect" button was pressed.

Things to try

  1. Call the buttonFontSize method of class UIFont. Make your buttons look like everyone else’s.

  2. Instead of hardcoding the button’s size of 200 × 40, figure out how big the button would have to be to hold the title. Use the sizeWithFont: method of class NSString that we saw in Hello, World!.

  3. Let the aspect ratio of the button be a golden rectangle.

  4. The last argument of the method addTarget:action:forControlEvents: could be several control events combined with “bitwise or”.
    UIControlEventTouchDown | UIControlEventTouchDragEnter
    

  5. If we press the button while the sound is playing, the sound will be interruped and will start playing again from the beginning. Disable the button while the sound is playing, rendering it insensitive to touches. Re-enable the button when the sound has finished playing.

    First, let the button be a property of the View. This will let the application delegate mention the button.

    @property (strong, nonatomic) UIButton *button;	//after } in View.h
    
    @synthesize button;		//after @implementation in View.m
    

    Add the following statements to the touchUpInside: method of the application delegate immediately before the call to AudioServicesPlaySystemSound. We convert the argument sender from a very generic id to a very specific “pointer to a UIButton”. This conversion makes it possible for us to mention the enabled property of the button.

    	UIButton *button = sender;
    	button.enabled = NO;
    
    Define the following function (which is not a method) in the ButtonAppDelegate.m file immediately above the @implementation ButtonAppDelegate. A cast is a conversion from one data type to another. The __bridge cast (with two underscores) is necessary when converting from or to the data type “pointer to void”.
    //Called when the sound has finished playing.
    
    static void complete(SystemSoundID sid, void *p)
    {
    	NSLog(@"complete");
    	UIButton *button = (__bridge UIButton *)p;
    	button.enabled = YES;
    }
    
    To have the above function called when the sound has finished playing, insert the following code into the application:didFinishLaunchingWithOptions: method of the application delegate after creating the view. The fourth argument of AudioServicesAddSystemSoundCompletion is the function to call, which cannot be a method. The fifth argument is the argument to be passed to that function.
    		error = AudioServicesAddSystemSoundCompletion(
    			sid, NULL, NULL, complete,
    			(__bridge void *)view.button);
    
    		if (error != kAudioServicesNoError) {
    			NSLog(@"AudioServicesAddSystemSoundCompletion error == %d",
    				error);
    			return YES;
    		}
    
    To show that the button is disabled, make its text red and background yellow when enabled, faint pink and faint yellow when disabled. Hint: setTitleColor:forState: UIControlStateDisabled.

  6. Add an “erase” button to the Etch a Sketch. The button will be added as a subview to the View. When pressed, the View will execute the CGPathRelease and CGPathCreate functions.