How to access MPVolumeView in my app delegate applicationWillTerminate? - ios

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

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.

Related

Highlighting a button from another class in objective-C, iOS

I have an app that includes a bluetooth LE class for handling a BT connection. When a characteristic value is changed I want code in the BT class to change a button state on the main viewcontroller. I'm new to obj-c and usually use stackoverflow to help with my understanding. I have the following defined in my viewcontroller:
#interface myViewController : UIViewController<CLLocationManagerDelegate, UITableViewDataSource, UITableViewDelegate, UINavigationBarDelegate, UIActionSheetDelegate>
#property (weak, nonatomic) IBOutlet UIButton *eraseButton;
within the view controller, I can happily change the button state:
[self.eraseButton setHidden:YES];
and
-(void) deselectEraseButton
{
[self.eraseButton setSelected:NO];
}
within the BT class I have tried many things (changing a property of viewcontroller which changes the button state via a timer, calling a method that directly changes the button state etc) and although the code is executed (I have breakpoints set on the code in myViewController), the button state isn't changed. For example:
myAppDelegate *app = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
app.vCtrl=[[myViewController alloc] init];
[app.vCtrl deselectEraseButton];
What am I doing wrong and what's the best way of doing this? I'm sure it shouldn't be this hard!
UPDATE:
with the help of Zhi-Wei Cai (thanks), I'm made some changes but alas, it's still not working:
I added the following to myAppDelegate.h:
#property (strong, nonatomic) myViewController* vCtrl;
I added the following to myAppDelegate.m:
-(void)eraseButton:(BOOL)state{
[vCtrl accessEraseButton:state];
}
From the BT class, I do the following:
[self performSelectorOnMainThread:#selector(test) withObject:nil waitUntilDone:YES];
with the following method in that class:
-(void) test
{
myAppDelegate *app = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
[app eraseButton:0];
}
This called myViewController and on thread1 did the following:
-(void) accessEraseButton: (BOOL) state
{
if (state==0)
{
[self.eraseButton setSelected:NO];
[self.view setNeedsDisplay];
}
else
{
[self.eraseButton setSelected:YES];
}
}
I then added [self.view setNeedsDisplay]; for good luck. The button is still not changing state but the code is running (I can hit a breakpoint and it breaks on the method and the method is running on thread1)?!? Any ideas?
* UPDATE 2 *
Ok, I've worked out what I was doing wrong. I was doing all of the above without properly referencing the viewcontroller. So although the code was executing, the instance of the viewcontroller wasn't correct and all the button handles were nil. I needed to add the following to myViewController.m, in viewDidLoad:
myAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
appDelegate.myViewController = self;
Now it works. Happy days...
Because when you do app.vCtrl = [[myViewController alloc] init];, you actually created a new instance of the UIViewController, not the original one that's on your screen.
I didn't test them, but you can achieve what you wanted by:
Via Interface Builder: Setup an IBOutlet of the UIViewController in your myAppDelegate, connect it to the ViewController within Interface Builder, then create a method in myAppDelegate to change it or access it directly using myAppDelegate as it's a property. You can now call it within your BT class e.g. [app deselectEraseButton] or [app.myViewController deselectEraseButton] or even do something with the button if the button is a property of the AppDelegate/VC.
Via Delegate: Create a #protocol to setup a delegate for the BT class, so the it can do callback within other classes such as myAppDelegate.
Via NSNotificationCenter: Use - (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender and - (void)postNotificationName:(NSString *)notificationName object:(id)notificationSender userInfo:(NSDictionary *)userInfo to do it. This will work app-wide.
I believe the above three already has tons of code example on SO, just search them and you will get what you need.
I've updated by original question with 2 updates that lead to a final understanding and solution. Hope it helps others who may be similarly confused!

EXC_BAD_ACCESS crash when stopping MPMoviePlayerController

I am having some EXC_BAD_ACCESS problems whilst trying to stop a video that is being played through MPMoviePlayerController. Here is some code:
Video Class:
#interface MyVideo()
#property (nonatomic, strong) MPMoviePlayerController * videoController
#end
#implementation MyVideo
#synthesize videoController;
- (MyVideo*) initIntoView: (UIView*) view withContent (NSDictionary*) contentDict {
self=[super init];
NSString * rawUrl=[[NSString alloc] initWithFormat:#"http://.../%#.mp4", [contentDict objectForKey:#"filename"]];
NSURL * videoUrl=[[NSURL alloc] initWithString:rawUrl];
videoController = [[MPMoviePlayerController alloc] initWithContentURL:videoUrl];
videoController.movieSourceType=MPMovieSourceTypeFile;
videoController.view.frame = viewRef.bounds;
[videoController.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
videoController.controlStyle=MPMovieControlStyleNone;
[view addSubview:videoController.view];
return self;
}
/* other code */
- (void) stop {
NSLog(#"video stop");
[videoController stop];
}
#end
This MyVideo class is a property within my AppDelegate class, like so:
#property (nonatomic, strong) MyVideo video;
A line in my AppDelegate class fires off the public method stop of this MyVideo class, like so:
[video stop];
This usually works fine. But occasionally I get an EXC_BAD_ACCESS error on the line with [videoController stop]. The line before it, the one with NSLog on it, outputs to the console as expected.
This crash happens while the video that has been loaded into the videoController is still playing. But it does not happen every time.
Can anyone suggest why this crash is happening? I suspect its because videoController is no longer in memory, despite it being strong and still in use.
Am I right in thinking there is absolutely no way of testing the videoController to see if it is still in memory?
Am I right in thinking there is absolutely no way of forcing videoController to stay in memory while it is being used to play a video?
So instead of trying to stop the video and shut down the MyVideo class properly when I dont want to play the video anymore, I am now thinking of just setting the MyVideo class to nil, and let ARC deal with stopping the video and clearing it from memory. Is this the right way of doing this? Would there be any disadvantages to this?
Are there any other solutions to this problem that I am missing?
With EXC_BAD_ACCESS my first port of call is to enable Zombie Objects in my Debug Scheme.
This should give you an idea of what object is causing the EXC_BAD_ACCESS. Just to double check it is your videoController.
When does your stop function get called on MyVideo
Is the crash on specific devices, iPad iPod, does it occur on specific os's iOS 6,7
Is it the same video file?
It cant randomly break there must be some pattern that is causing the EXC_BAD_ACCESS
I may be wrong but I have a feeling that its a threading issue. I suspect the thread calling the [myVideo stop] function is not aware of the videoController(probably initialised on the main thread). Try calling the [videoController stop] within the main thread by using the following:
dispatch_async(dispatch_get_main_queue(), ^{
[videoController stop];
});
Do let me know if this works!

Controlling the screenshot in the iOS 7 multitasking switcher

I've been trying to find some information regarding the new multitasking switcher in iOS 7 and especially the screenshot that the OS takes when the app is going into hibernation.
Is there any way to completely turn off this feature or screenshot? Or can I hide the app altogether from the switcher? The app needs to run in the background, but we do not want to show any screenshot from the app.
The screenshot is potentially a security-risk, think along the lines for banking-apps where your card number or account summary will be available to anyone that double-click on the home button on the device.
Anyone with any insight into this? Thanks.
In Preparing Your UI to Run in the Background, Apple says:
Prepare Your UI for the App Snapshot
At some point after your app enters the background and your delegate method returns, UIKit takes a snapshot of your app’s current user interface. The system displays the resulting image in the app switcher. It also displays the image temporarily when bringing your app back to the foreground.
Your app’s UI must not contain any sensitive user information, such as passwords or credit card numbers. If your interface contains such information, remove it from your views when entering the background. Also, dismiss alerts, temporary interfaces, and system view controllers that obscure your app’s content. The snapshot represents your app’s interface and should be recognizable to users. When your app returns to the foreground, you can restore data and views as appropriate.
See Technical Q&A QA1838: Preventing Sensitive Information From Appearing In The Task Switcher
In addition to obscuring/replacing sensitive information, you might also want to tell iOS 7 to not take the screen snapshot via ignoreSnapshotOnNextApplicationLaunch, whose documentation says:
If you feel that the snapshot cannot correctly reflect your app’s user interface when your app is relaunched, you can call ignoreSnapshotOnNextApplicationLaunch to prevent that snapshot image from being taken.
Having said that, it appears that the screen snapshot is still taken and I have therefore filed a bug report. But you should test further and see if using this setting helps.
If this was an enterprise app, you might also want to look into the appropriate setting of allowScreenShot outlined in the Restrictions Payload section of the Configuration Profile Reference.
Here is an implementation that achieves what I needed. You can present your own UIImageView, or your can use a delegate-protocol pattern to obscure the confidential information:
// SecureDelegate.h
#import <Foundation/Foundation.h>
#protocol SecureDelegate <NSObject>
- (void)hide:(id)object;
- (void)show:(id)object;
#end
I then gave my app delegate a property for that:
#property (weak, nonatomic) id<SecureDelegate> secureDelegate;
My view controller sets it:
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
delegate.secureDelegate = self;
}
The view controller obviously implements that protocol:
- (void)hide:(id)object
{
self.passwordLabel.alpha = 0.0;
}
- (void)show:(id)object
{
self.passwordLabel.alpha = 1.0;
}
And, finally, my app delegate avails itself of this protocol and property:
- (void)applicationWillResignActive:(UIApplication *)application
{
[application ignoreSnapshotOnNextApplicationLaunch]; // this doesn't appear to work, whether called here or `didFinishLaunchingWithOptions`, but seems prudent to include it
[self.secureDelegate hide:#"applicationWillResignActive:"]; // you don't need to pass the "object", but it was useful during my testing...
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[self.secureDelegate show:#"applicationDidBecomeActive:"];
}
Note, I'm using applicationWillResignActive rather than the advised applicationDidEnterBackground, because, as others have pointed out, the latter is not called when double tapping on the home button while the app is running.
I wish I could use notifications to handle all of this, rather than the delegate-protocol pattern, but in my limited testing, the notifications aren't handled in a timely-enough manner, but the above pattern works fine.
This is the solution I worked with for my application:
As Tommy said: You can use the applicationWillResignActive. What I did was making a UIImageView with my SplashImage and add it as subview to my main window-
(void)applicationWillResignActive:(UIApplication *)application
{
imageView = [[UIImageView alloc]initWithFrame:[self.window frame]];
[imageView setImage:[UIImage imageNamed:#"Portrait(768x1024).png"]];
[self.window addSubview:imageView];
}
I used this method instead of applicationDidEnterBackground because applicationDidEnterBackground won't be triggered if you doubletap the home button, and applicationWillResignActive will be. I heard people say though it can be triggered in other cases aswell, so I'm still testing around to see if it gives problem, but none so far! ;)
Here to remove the imageview:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if(imageView != nil) {
[imageView removeFromSuperview];
imageView = nil;
}
}
Hope this helps!
Sidenote: I tested this on both the simulator and a real device: It Won't Show on the simulator, but it does on a real device!
This quick and easy method will yield a black snapshot above your app's icon in the iOS7 or later app switcher.
First, take your app's key window (typically setup in AppDelegate.m in application:didFinishLaunchingWithOptions), and hide it when your app is about to move into the background:
- (void)applicationWillResignActive:(UIApplication *)application
{
if(isIOS7Or8)
{
self.window.hidden = YES;
}
}
Then, un-hide your app's key window when your app becomes active again:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if(isIOS7Or8)
{
self.window.hidden = NO;
}
}
At this point, check out the app switcher and verify that you see a black snapshot above your app's icon. I've noticed that if you launch the app switcher immediately after moving your app into the background, there can be a delay of ~5 seconds where you'll see a snapshot of your app (the one you want to hide!), after which it transitions to an all-black snapshot. I'm not sure what's up with the delay; if anyone has any suggestions, please chime in.
If you want a color other than black in the switcher, you could do something like this by adding a subview with any background color you'd like:
- (void)applicationWillResignActive:(UIApplication *)application
{
if(isIOS7Or8)
{
UIView *colorView = [[[UIView alloc] initWithFrame:self.window.frame] autorelease];
colorView.tag = 9999;
colorView.backgroundColor = [UIColor purpleColor];
[self.window addSubview:colorView];
[self.window bringSubviewToFront:colorView];
}
}
Then, remove this color subview when your app becomes active again:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if(isIOS7Or8)
{
UIView *colorView = [self.window viewWithTag:9999];
[colorView removeFromSuperview];
}
}
I used the following solution:
when application is going to resign I get appWindow snapshot as a View and add blur to it. Then I add this view to app window
how to do this:
in appDelegate just before implementation add line:
static const int kNVSBlurViewTag = 198490;//or wherever number you like
add this methods:
- (void)nvs_blurPresentedView
{
if ([self.window viewWithTag:kNVSBlurViewTag]){
return;
}
[self.window addSubview:[self p_blurView]];
}
- (void)nvs_unblurPresentedView
{
[[self.window viewWithTag:kNVSBlurViewTag] removeFromSuperview];
}
#pragma mark - Private
- (UIView *)p_blurView
{
UIView *snapshot = [self.window snapshotViewAfterScreenUpdates:NO];
UIView *blurView = nil;
if ([UIVisualEffectView class]){
UIVisualEffectView *aView = [[UIVisualEffectView alloc]initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
blurView = aView;
blurView.frame = snapshot.bounds;
[snapshot addSubview:aView];
}
else {
UIToolbar *toolBar = [[UIToolbar alloc] initWithFrame:snapshot.bounds];
toolBar.barStyle = UIBarStyleBlackTranslucent;
[snapshot addSubview:toolBar];
}
snapshot.tag = kNVSBlurViewTag;
return snapshot;
}
make your appDelegate implementation be the as follows:
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
//...
//your code
//...
[self nvs_blurPresentedView];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
//...
//your code
//...
[self nvs_unblurPresentedView];
}
I created Example projects in Swift and Objective C.
Both projects makes the following actions in:
-application:didResignActive - snapshot is created, blurred and added to app window
-application:willBecomeActive blur view is being removed from window.
How to use:
Objecitve C
Add AppDelegate+NVSBlurAppScreen .h and .m files to your project
in your -applicationWillResignActive: method add the following line:
[self nvs_blurPresentedView];
in your -applicationDidEnterBackground: method add the following line:
[self nvs_unblurPresentedView];
Swift
add AppDelegateExtention.swift file to your project
in your applicationWillResignActive function add the following line:
blurPresentedView()
in your applicationDidBecomeActive function add the following line:
unblurPresentedView()
if only use [self.window addSubview:imageView]; in applicationWillResignActive function, This imageView won't cover UIAlertView, UIActionSheet or MFMailComposeViewController...
Best solution is
- (void)applicationWillResignActive:(UIApplication *)application
{
UIWindow *mainWindow = [[[UIApplication sharedApplication] windows] lastObject];
[mainWindow addSubview:imageView];
}
Providing my own solution as an "answers", though this solution is very unreliable. Sometimes i get a black screen as the screenshot, sometimes the XIB and sometimes a screenshot from the app itself. Depending on device and/or if i run this in the simulator.
Please note i cannot provide any code for this solution since it's a lot of app-specific details in there. But this should explain the basic gist of my solution.
In AppDelegate.m under applicationWillResignActive i check if we're
running iOS7, if we do i load a new view which is empty with the
app-logo in the middle. Once applicationDidBecomeActive is called i
re-launch my old views, which will be reset - but that works for the
type of application i'm developing.
You can use activator to configure double clicking of home button to launch multitasking and disable default double clicking of home button and launching of multitasking window. This method can be used to change the screenshots to the application's default image. This is applicable to apps with default passcode protection feature.
Xamarin.iOS
Adapted from https://stackoverflow.com/a/20040270/7561
Instead of just showing a color I wanted to show my launch screen.
public override void DidEnterBackground(UIApplication application)
{
//to add the background image in place of 'active' image
var backgroundImage = new UIImageView();
backgroundImage.Tag = 1234;
backgroundImage.Image = UIImage.FromBundle("Background");
backgroundImage.Frame = this.window.Frame;
this.window.AddSubview(backgroundImage);
this.window.BringSubviewToFront(backgroundImage);
}
public override void WillEnterForeground(UIApplication application)
{
//remove 'background' image
var backgroundView = this.window.ViewWithTag(1234);
if(null != backgroundView)
backgroundView.RemoveFromSuperview();
}

iOS 6 Map problems with MKUserTrackingModeFollowWithHeading

I'm trying to solve two problems with MKUserTrackingModeFollowWithHeading in iOS 6:
MKUserTrackingModeFollowWithHeading works briefly, but it's jittery and returns to MKUserTrackingModeFollow almost immediately, especially at high zoom levels.
The app occasionally crashes when repeatedly changing the MKUserTrackingMode: I get EXC_BAD_ACCESS on the main thread, without further information. This is hard to reproduce, but it has happened repeatedly.
Any thoughts on what might be causing this? It feels like a bug, but Apple's own "Maps" app doesn't exhibit this behavior.
In order to isolate the problems, I've created a Single View Application with an MKMapView and a UIToolbar (set up in a .xib), to which I'm adding a MKUserTrackingBarButtonItem. The UIViewController acts as a <MKMapViewDelegate>. Here's the complete implementation code:
#import "ViewController.h"
#implementation ViewController
#synthesize mapView, toolbar;
- (void)viewDidLoad
{
[super viewDidLoad];
// Add MKUserTrackingBarButtonItem to toolbar
MKUserTrackingBarButtonItem *trackButton = [[MKUserTrackingBarButtonItem alloc] initWithMapView:self.mapView];
[toolbar setItems:[NSArray arrayWithObjects:trackButton, nil] animated:YES];
}
- (void)mapView:(MKMapView *)mapView didChangeUserTrackingMode:(MKUserTrackingMode)mode animated:(BOOL)animated
{
// Log MKUserTrackingMode change
NSString *modeType = (mode == 0) ? #"None" : ((mode == 1) ? #"Follow" : #"FollowWithHeading");
NSLog(#"MKUserTrackingMode changed to: %#", modeType);
}
#end
This is a bug in MapKit. It can be observed also in Apple Maps using MapKit such as the Find My Friends app. Note that the Apple Maps app is not using MapKit (at least not the same version) thus it's not affected by this bug.
I also do see sporadic EXC_BAD_ACCESS crashes in MapKit. In fact, MapKit related crashes account for the vast majority of my app's crashes. :(
I also noticed that MKUserTrackingModeFollowWithHeading works briefly and it changes to MKUserTrackingModeFollow almost immediately, especially at high zoom levels.
I tried
- (void)mapView:(MKMapView *)mapView didChangeUserTrackingMode:(MKUserTrackingMode)mode animated:(BOOL)animated {
if (mapView.userTrackingMode != MKUserTrackingModeFollowWithHeading) {
[mapView setUserTrackingMode:MKUserTrackingModeFollowWithHeading];
}
}
but this creates a forever loop since right after I change to MKUserTrackingModeFollowWithHeading, something changes back to MKUserTrackingModeFollow. It's really annoying because I don't know what keeps changing the tracking mode to MKUserTrackingModeFollow.
Sorry that my answer was not useful, but I posted here to confirm the problem.

UIImagePickerController: [PLImageScrollView release]: message sent to deallocated instance

I am writing an iOS6 app using Cocos2D with ARC turned on (Cocos is linked as a static library, not under ARC). I am able to present the camera using the following code:
cameraController = [[UIImagePickerController alloc] init];
// set other properties of camera
cameraController.delegate = psImageLayer;
psImageLayer.imagePicker = cameraController;
[[CCDirector sharedDirector] presentViewController:cameraController animated:YES completion:nil];
and I dismiss the camera in psImageLayer with this code:
- (void) imagePickerController: (UIImagePickerController *) picker
didFinishPickingMediaWithInfo: (NSDictionary *) info {
// do something with image
[[CCDirector sharedDirector] dismissViewControllerAnimated:YES completion:nil];
}
When I dismiss the camera, the app crashes with the following error: *** -[PLImageScrollView release]: message sent to deallocated instance 0x2494f4f0 I am pretty sure that PLImageScrollView is an iOS class, because I did not write it.
My issue appears to be very similar to the issue posted here, but his solution involves modifying the class that owns the delegate. In this case, UIImagePickerController is the class, which cannot be modified.
The relevant parts of the PhotoShareImageLayer header file are posted below:
// PhotoShareImageLayer.h (this is what psImageLayer is)
#interface PhotoShareImageLayer : CCLayer <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
#property(nonatomic, retain) UIImagePickerController *imagePicker;
#property(nonatomic, retain) UIImage *currentImage;
#end
Any ideas on how to stop this error from happening? Thanks.
EDIT: List of things I have already tried.
Subclassing UIImagePickerController and adding - (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self setDelegate:nil];
}
EDIT 2: The crash does not happen in imagePickerController:didCancel. Only when a picture is taken, OR when "Retake" is pressed in the camera. There is no UIImagePickerDelegate method for "Retake" (only "Cancel" and "Use").
EDIT 3: After continuing and writing more of the app, it appears this issue is not unique to the camera. The same (or very similar) errors occur when dismissing modal views for Twitter, Facebook, Contacts, and more.
I believe this is a problem with Apple's internal implementation of PLImageScrollView. I'm swizzling the UIScrollView`s setDelegate method and this causes a crash when a UIImagePicker is used (although, it only seems to be if you select a photo, not if you cancel). The problem I end up seeing is that the scrollViewDidScroll: method is sent to the real delegate (through my interceptor), but it's already been released.
This suggests to me that the PLImageScrollView's delegate is being dealloced without being nilled out. I half-solved the problem by creating my own strong reference to the real delegate. This could cause a memory leak in other implementations, but that's better than a crash at least.

Resources