How to access MPVolumeView in my app delegate applicationWillTerminate?

718 Views Asked by At

EDIT: It is appearing that there may not be ANY possible way to, on app termination only, set the device volume back to the level it was when an app started. To me this is a possible oversight on the part of Apple. Why wouldn't Apple want my app to be a good camper that leaves their campsite the way they found it, there must be a way, please help... I tried to get an answer to this broader question with another topic but it was closed as a duplicate, please go there and vote to re-open it :)

When my app terminates, I want to set the system volume back to the same volume it was when my app started (Edit: and not change the volume when the app enters the background).

I am attempting to do that with MPVolumeView as the mechanism to set the devices volume. From what I have researched, that seems to be the only way to set the devices volume programmatically. If there is another way, please suggest it.

So, when I launch my app I save the system volume as an external variable 'appStartVol' in my AppDelegate.m.

I let the user change the volume during app usage with MPVolumeView.

Then I try to set the system volume back to the appStartVol in the AppDelegate.m files' applicationWillTerminate. EDIT: applicationWillTerminate is called when a user dismisses apps from their recents list. I do this all the time and leave regularly used apps in recents so I don't have to scroll through 9 pages of icons to find them. So, there is a reason to do what I am asking, in that function.

I use this approach for screen brightness but can not seem to do it for volume.

I am having trouble because I do not seem to be able to get access to my storyboard MPVolumeView in AppDelegate.m applicationWillTerminate and I can not seem to make a local MPVolumeView work in AppDelegate.m applicationWillTerminate.

If I use a UIApplicationWillTerminateNotification notification in my view controller, I still run into the same problems in the notification event since the storyboard MPVolumeView also seems not accessible from that event.

EDIT: This is the reason that using code in applicationDidEnterBackground does not meet my needs: I want my users to be able to use my music player at the volume they have manually chosen in my app even when they decide to bring another app into focus. I believe that this is what the users would naturally assume would happen. For instance, why would the volume change if I want to use the calculator? I also want to believe that the natural assumption for a user would be that the volume should pop back to pre-app volume if the app is dismissed. Using applicationDidEnterBackground would make the app go to pre-app volume both when the app goes into background AND when it terminates, this is not acceptable.

ATTEMPT 1: Here is my code in my AppDelegate.m applicationWillTerminate:

- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSLog(@"=== App is terminating ===");
UIScreen.mainScreen.brightness = brightnessORG;
NSLog(@"=== Screen brightness back to pre-app level of %f ===", brightnessORG);

UISlider *volumeViewSlider;
for (UIView *view in [_mpVolumeViewParentView subviews]){
    if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
        volumeViewSlider = (UISlider*)view;
        volumeViewSlider.value = appStartVol;
        break;
    }
}
}

ATTEMPT 1: Here is my AppDelegate.h:

#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>

extern CGFloat brightnessORG;
extern float appStartVol;

@interface AppDelegate : UIResponder <UIApplicationDelegate>
{
    MPVolumeView *_mpVolumeViewParentView;
}

@property (strong, nonatomic) UIWindow *window;

@property (nonatomic, retain) IBOutlet MPVolumeView *mpVolumeViewParentView;

@end

Re. ATTEMPT 1: Although this code runs without error, it does not set the volume back to appStartVol since there are no views in the app delegates [_mpVolumeViewParentView subviews]. I am obviously not accessing the mpVolumeViewParentView that is in my storyboard.

ATTEMPT 2: Lets see if I can just add a local MPVolumeView in AppDelegate.m applicationWillTerminate:

- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSLog(@"=== App is terminating ===");
UIScreen.mainScreen.brightness = brightnessORG;
NSLog(@"=== Screen brightness back to pre-app level of %f ===", brightnessORG);

MPVolumeView *volumeView = [ [MPVolumeView alloc] init];
UISlider *volumeViewSlider;
volumeViewSlider = (UISlider*)volumeView;
volumeViewSlider.value = appStartVol;
}

Re. ATTEMPT 2: Runs with error = 'Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MPVolumeView setValue:]: unrecognized selector sent to instance'

But I have tried :), as you can see I am an objective-c newbie...

Any help would be appreciated :)

ATTEMPT 3: Try subviews in local MPVolumeView in applicationWillTerminate:

MPVolumeView *_mpVolumeViewParentView = [ [MPVolumeView alloc] init];
MPVolumeView *volumeView = [ [MPVolumeView alloc] init];
[_mpVolumeViewParentView addSubview:volumeView];
UISlider *volumeViewSlider;
for (UIView *view in [_mpVolumeViewParentView subviews]){
    if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
        volumeViewSlider = (UISlider*)volumeView;
        volumeViewSlider.value = appStartVol;
        break;
    }
}

Re. ATTEMPT 3: Runs with error at for loop initiation: 'Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MPVolumeView setValue:]: unrecognized selector sent to instance'

ATTEMPT 4: After adding AVfoundation framework to the project, I added this to my AppDelegate.m:

    #import <AVFoundation/AVFoundation.h>

And I put these two lines to AppDelegate.m applicationWillTerminate:

    AVAudioPlayer* wavplayer = [[AVAudioPlayer alloc] init];
    wavplayer.volume = appStartVol;

Re. ATTEMPT 4: Runs with error at 'wavplayer.volume= appStartVol;': 'Thread 1: EXC_BAD_ACCESS (code=1, address=0x48)' , darn......

1

There are 1 best solutions below

9
Rychu On

First, you are trying something that probably can't work - you are casting MPVolumeView to UISlider and then try to use UISlider's setValue: method. But your object is still MPVolumeView and support that method. So this can't work not because you are using it in wrong place, but in never works.

Also - appWillTerminate is not sufficient, as it's called only in one specific case. If app goes to background first, and then killed - the willTerminate is never called.

I assume you were trying to de something like described here iOS 9: How to change volume programmatically without showing system sound bar popup? - but you should just do the same - so find the UISlider within subviews, instead of casting the whole thing to it.

Edit:

@implementation AppDelegate
{
    MPVolumeView* mv;
    UISlider* slider;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    slider.value = 1;
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    mv = [MPVolumeView new];
    for (UIView *view in [mv subviews]){
        if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
            slider = ((UISlider*)view);
            break;
        }
    }
}

@end

that's the sample code I used. Using in terminate unfortunately didn't work, so that's the most that I see that can be done (I also think it's the more correct way than using terminate, as it's not always called). Remember this can stop working at any time, or even be rejected when submitted to the AppStore.