Camera+ just got VolumeSnap back in their app. If you don’t know the story, Camera+ used the volume button on the iPhone to snap a picture. Apple rejected their app and kept them out of the store for three months when they found out. Then when iOS 5 came out Apple implemented volume snap in their own camera app. So now that Camera+ has it again and Apple seems to have relaxed their policy, I tried to figure out how Camera+ did it.

I came up with this class RBVolumeButtons. You use it like this:

1

2

3

4

5

6

7

8

9

RBVolumeButtons *buttonStealer = [[[RBVolumeButtons alloc] init] autorelease];

buttonStealer.upBlock = ^{

   counter++;

   [counterLabel setText:[NSString stringWithFormat:@"%i",counter]];

};

buttonStealer.downBlock = ^{

   counter--;

   [counterLabel setText:[NSString stringWithFormat:@"%i",counter]];

};

It’s really that simple. Here’s how I did it.

First thing I had to do was figure out how to listen to the button presses. As far as I know, there isn’t a way to do it directly, so I listen to a volume change notification.

To do this, you start by initializing an audio session and adding a volume property listener

1

2

3

AudioSessionInitialize(NULL, NULL, NULL, NULL);

AudioSessionSetActive(YES);

AudioSessionAddPropertyListener(kAudioSessionProperty_CurrentHardwareOutputVolume, volumeListenerCallback, self);

The callback looks like this

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

void volumeListenerCallback (

                             void                      *inClientData,

                             AudioSessionPropertyID    inID,

                             UInt32                    inDataSize,

                             const void                *inData

                             ){

   const float *volumePointer = inData;

   float volume = *volumePointer;

   if( volume > [(RBVolumeButtons*)inClientData launchVolume] )

   {

      [(RBVolumeButtons*)inClientData volumeUp];

   }

   else if( volume < [(RBVolumeButtons*)inClientData launchVolume] )

   {

      [(RBVolumeButtons*)inClientData volumeDown];

   }

}

So now I can get the volume values. Now I need to prevent the volume change notifications from showing up on screen. To do this, you can use an MPVolumeView. This is a special slider that will let you change the system volume for use in music playing applications. When it is in your app, the normal system volume change notifications don’t show on screen.

1

2

3

4

CGRect frame = CGRectMake(0, -100, 10, 0);

MPVolumeView *volumeView = [[[MPVolumeView alloc] initWithFrame:frame] autorelease];

[volumeView sizeToFit];

[[[[UIApplication sharedApplication] windows] objectAtIndex:0] addSubview:volumeView];

I get the window from the UIApplication and add the MPVolumeView off the screen.

So now the volume notifications aren’t showing and we’re getting some callbacks about when the volume changes.

When the user presses the volume buttons in your app, you don’t want the system volume to change. It would suck to have them taking pictures and turn up their ringtone volume all the way or something.

So how do I pull that off? This is pretty hacky but it works. First I store the volume that the device was at when the app launched. Then, each time the button is pressed, I set it back to that. In order two prevent getting two callbacks, I stop listening for notifications, set the volume, and start listening again. Like I said, it’s a bit of a hack.

1

2

3

4

5

AudioSessionRemovePropertyListenerWithUserData(kAudioSessionProperty_CurrentHardwareOutputVolume, volumeListenerCallback, self);

   [[MPMusicPlayerController applicationMusicPlayer] setVolume:launchVolume];

   [self performSelector:@selector(initializeVolumeButtonStealer) withObject:self afterDelay:0.1];

Now you just need to do some tricks with observing the application’s state (backgrounding, becoming inactive, etc.) to make sure that you’re always observing at the right times.

It’s a bit rough around the edges, but it should work. I’m sure there some issues with AudioSessions and if your app records or plays audio, this might mess some stuff up. You’ll also need to link the MediaPlayer and AudioToolbox frameworks with your project. There is a bug where the volume change notification shows when you close the app if the volume was at 0% or 100% when you launched the app. I don’t know if these are the same approaches that Camera+ used to do the same thing, but their implementation doesn’t have the bugs that mine does. If anyone knows a better way, let me know.

I have no idea if Apple will allow this in the App Store. It only uses public APIs (as far as I can tell) so it’s possible they will.

You can check out the full project on GitHub.

This entry was posted in Uncategorized. Bookmark the permalink.