-1

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......

KiloOne
  • 312
  • 1
  • 6
  • 20

1 Answers1

0

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.

Rychu
  • 1,028
  • 1
  • 9
  • 20
  • No I am not trying to do that and I thought I tried the UISlider within subviews in ATTEMPT 1. Also, the willTerminate 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. – KiloOne Jan 05 '19 at 17:35
  • I added an ATTEMPT 3 with similar results to ATTEMPT 2. Any comments? – KiloOne Jan 05 '19 at 17:59
  • Ok, I tried it properly - and actually I wasn't able to get it to work on terminate. I'd recommend doing it on applicationDidEnterBackground, also it needs some delay between MPVolumeView, and using it (so, cache it in a local variable). – Rychu Jan 05 '19 at 18:33
  • Also - what you are trying to do is basically private api usage - it seems there's no legal api for changing the volume, and the one you are using can change/be disabled with any future OS update. And seems. like it already changed a bit (need of delay). – Rychu Jan 05 '19 at 18:34
  • My app is a music player and if I use applicationDidEnterBackground, that means the volume will go back to pre-app levels whenever you use another app while my app is playing music in the background at the level you want for music. – KiloOne Jan 05 '19 at 18:38
  • I am having a hard time believing that Apple does not want my app to be a good camper that leaves their campsite the way they found it, there must be a way... – KiloOne Jan 05 '19 at 18:39
  • https://developer.apple.com/documentation/avfoundation/avaudiosession/1616533-outputvolume?language=objc see the "The system wide output volume can be set directly only by the user; to provide volume control in your app, use the MPVolumeView class." - to do that legally you can't directly access that control, but add it somewhere and allow user to change the volume. – Rychu Jan 05 '19 at 18:41
  • Thanks, but I hope you don't mind if I leave this unanswered until there is a definitive, not possible answer to the question of whether there is another way to accomplish the SAME thing. – KiloOne Jan 05 '19 at 18:46
  • SAME thing as what the topic asks for... (to be more clear) – KiloOne Jan 06 '19 at 00:32