21

I want to implement the same behavior with the native camera of iOS5:

  • press the volume + button to take a photo

What's the ideal way to archive it? Are there any ways to capture the volume key pressed event?

After googling & searching around for hours, I found 1 solution: using NSNotificationCenter:

...
    [[NSNotificationCenter defaultCenter]
         addObserver:self
         selector:@selector(volumeChanged:)
         name:@"AVSystemController_SystemVolumeDidChangeNotification"
         object:nil];
...
- (void)volumeChanged:(NSNotification *)notification{
    [self takePhoto];   
}

However, it has 2 issues:

  • There is an semi-transparent overlay of "current system volume" show up every time when pressing the volume key, this is not what I wanted.
  • For the native camera, when you press the volume key as shutter, the system volume won't change, however, by using the above method, the system volume will change.
BryanH
  • 5,826
  • 3
  • 34
  • 47
huxia
  • 492
  • 1
  • 3
  • 16
  • i Know the camera + app got rejected from the app store when releasing a camera that could take a photo using the + volume button. – bogen Dec 06 '11 at 08:46
  • 2
    I think that's about half year ago, checkout their recent introduction: "What's New in Version 2.4: VolumeSnap is back, beyotches!!" http://itunes.apple.com/us/app/id329670577?mt=8 – huxia Dec 06 '11 at 08:56
  • 1
    It is now built into the default iOS camera as of iOS 5, so it is clearly now allowed behavior. – Duncan Babbage Dec 06 '11 at 09:12

5 Answers5

13

Building on huxia's code, this works on ios5+, no need to run the code every time it becomes active, just run it once in the beginning.

// these 4 lines of code tell the system that "this app needs to play sound/music"
AVAudioPlayer* p = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"photoshutter.wav"]] error:NULL];
[p prepareToPlay];
[p stop];

//make MPVolumeView Offscreen
CGRect frame = CGRectMake(-1000, -1000, 100, 100);
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:frame];
[volumeView sizeToFit];
[self.view addSubview:volumeView];
Kwok Pan Fung
  • 416
  • 5
  • 6
  • Correct me if I'm wrong but i believe that for this to work a reference to "p" has to be kept as an instance variable. Otherwise returning from the background will reset the volume view. – Pochi Aug 14 '13 at 03:54
  • @kwok-pan-fung anyway to prevent this from stopping music currently playing? – Ryan Romanchuk Jan 09 '14 at 05:44
  • @Kwok Pan Fung , thank you for your wonderful solution. I wonder that if i put this code in my application then is there any possibility that apple may reject my application ? – Birju Dec 16 '15 at 06:40
13

I've found another way to hide the "system volume overlay" and "bypass the system volume change when the volume key pressed" by myself.

The bad part: this is an super UGLY hack.

However, the good part is: this ugly hack uses NO private APIs.

Another note is: it only works for ios5+ (anyway, for my issue, since the AVSystemController_SystemVolumeDidChangeNotification only works for ios5, so this UGLY hack just fits my issue.)

The way it work: "act as a music/movie player app and let the volume key to adjust the application-volume".

Code:

// these 4 lines of code tell the system that "this app needs to play sound/music"
AVAudioPlayer* p = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"photo-shutter.wav"]] error:NULL];
[p prepareToPlay];
[p stop];
[p release];

// these 5 lines of code tell the system that "this window has an volume view inside it, so there is no need to show a system overlay"
[[self.view viewWithTag:54870149] removeFromSuperview];
MPVolumeView* vv = [[MPVolumeView alloc] initWithFrame:CGRectMake(-100, -100, 100, 100)];
[self.view addSubview:vv];
vv.tag = 54870149;
[vv release];

(5 hours spending on discovering this super ugly method... shit... 草尼马啊!)

Another thing: if you take the above hack, you need to run the code EVERY-TIME when your app become active. So, you might need to put some code into your app delegate.

- (void)applicationDidBecomeActive:(UIApplication *)application 
huxia
  • 492
  • 1
  • 3
  • 16
  • 1
    `[[NSBundle mainBundle] pathForResource:ofType:]` would probably simplify that one line a little; just to be explicit: there's presumably no special meaning to the tag? You could just as successfully keep an explicit reference to any previous `MPVolumeView`? – Tommy Dec 06 '11 at 22:41
  • @Tommy the reason I added the tag is just to let the code could be ran more than once(because some times the self.view will be re-created because of memory warning). And Yes, there is no special meaning to the tag. – huxia Dec 07 '11 at 08:34
  • @huxia anyway to prevent this from stopping music that's currently playing in the background? – Ryan Romanchuk Jan 09 '14 at 05:45
  • Captures the button press but doesn't stop the UI overlay or the system volume change. – Ethan_AI Dec 02 '14 at 22:26
3

[[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(volumeChanged:)
     name:@"AVSystemController_SystemVolumeDidChangeNotification"
     object:nil];

- (void)volumeChanged:(NSNotification *)notification{ 

    CGRect frame = CGRectMake(-1000, -1000, 100, 100);
    MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:frame];
    [volumeView sizeToFit];
    [self.view addSubview:volumeView];
    [volumeView release];

    [self takePhoto];   
}
MByD
  • 135,866
  • 28
  • 264
  • 277
2

I call this method from viewDidAppear

-(void) startTrackingVolume
{
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
    [[AVAudioSession sharedInstance] setActive:YES error:nil];

    if (!self.volumeView) {
        // put it somewhere outside the bounds of parent view
        self.volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(-100, -100, 10, 0)];
        [self.volumeView sizeToFit];
    }

    if (!self.volumeView.superview) {
        [self.view addSubview:self.volumeView];
    }
}

In viewWillDisappear in call

[[AVAudioSession sharedInstance] setActive:NO error:nil];
AndroC
  • 4,758
  • 2
  • 46
  • 69
2

There's currently no official way to capture the volume key pressed event. Apple's stated line is that the volume button works with the UIImagePickerController if you've allowed it to show camera controls.

Other approaches, such as listening for the notification, seem to be unsupported hacks that Apple's team are — anecdotally — sometimes turning a blind eye to. To prevent the volume HUD from appearing you can use the undocumented UIApplication methods:

- (void)setSystemVolumeHUDEnabled:(BOOL)enabled;
- (void)setSystemVolumeHUDEnabled:(BOOL)enabled forAudioCategory:(NSString *)category;

The only statement of their use I've seen is:

UIApplication *app = [UIApplication sharedApplication];
[app setSystemVolumeHUDEnabled:NO forAudioCategory:@"Ringtone"];
[app setSystemVolumeHUDEnabled:NO];

I'm unsure if or why you seemingly need to disable the HUD for a specific category and then in general, but without proper documentation that's difficult to figure out.

So: use UIImagePickerController and its camera buttons if you want to be within the rules. If you've found an app that seems to work outside of the rules then it's probably using the methods above.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • Thank you for your answer first. I've been looking and trying around for hours and found another way to disabled the system volume HUD. And it uses no private API. In my case, we cannot use UIImagePickerController api because the camera app we are building is so different with the native one. – huxia Dec 06 '11 at 16:25