Setting object to nil iOS7 vs iOS8 - objective-c

WI have an iPad kiosk app that displays videos on an external monitor connected to the iPad via HDMI. I have a viewController that manages the view on the external monitor. When I am done playing back a video I nil out the MPMoviePlayerController instance. In iOS7 this works fine, but in iOS8 I get a hard crash after setting the moviePlayer to nil.
- (void)removeMoviePlayer {
[self.moviePlayerController.view removeFromSuperview];
[self removeMovieNotificationHandlers];
self.moviePlayerController = nil;}
With Zombies enabled I get a message in the debugger:
[MPAVController release]: message sent to deallocated instance
Again, this crash does not happen when the app runs under iOS7. What has changed that is causing this crash?

After a couple days of trial and error I discovered that when trying to nil out the MPMoviePlayerController instance when the MPMoviePlayerPlaybackState was MPMoviePlaybackStatePaused, the app would crash. When a video reaches the end, the MPMoviePlayerController sends a MPMoviePlaybackDidFinish notification that reports the playback state as MPMoviePlaybackStatePaused. The fix was to test for playback state and if paused call [MPMoviePlayerController stop]. That changes the MPMoviePlaybackState to MPMoviePlaybackStateStopped, and you can then nil out the instance without a crash.
This crash did not happen before iOS 8. Code below:
-(void)moviePlayBackDidFinish:(NSNotification *)notification {
[self stopVideo:notification];
}
- (void)stopVideo:(NSNotification *)notification {
if (self.moviePlayerController) {
if (self.moviePlayerController.playbackState == MPMoviePlaybackStatePlaying || self.moviePlayerController.playbackState == MPMoviePlaybackStatePaused) {
[self.moviePlayerController stop];
}
[self cleanUpVideo];
}
}
- (void)cleanUpVideo {
[self killProgressTimer];
[UIView animateWithDuration:1.0f animations:^{
self.closedCaptionLabel.alpha = 0.0f;
self.moviePlayerController.view.alpha = 0.0f;
self.backgroundImageView.alpha = 1.0f;
} completion:^(BOOL finished) {
[self removeMoviePlayer];
[self resetClosedCaptions];
[self.delegate videoDidStop];
}];
}
- (void)removeMoviePlayer {
[self.moviePlayerController.view removeFromSuperview];
[self removeMovieNotificationHandlers];
self.moviePlayerController = nil;
}

Related

orientation issue on rotation in ios 8 when dismissViewControllerAnimated

I am using mobiscan library for scan barcode. After complete barcode scanning I dismiss the current screen and force rotate to portrait mode. At some moment a blank screen appear for some seconds and then display properly. This issue is generate only in ios 8. It's working fine in ios 7. This issue is generate only when scan barcode in landscape mode.
Here are the code and snap of the issue which can generate in my app.
if(![UICommonUtils isiPad]) {
// Force the iPhone or iPod to display the TableViewController in portrait mode.
if (![self.presentedViewController isBeingDismissed])
{
UIApplication* application = [UIApplication sharedApplication];
if (application.statusBarOrientation != UIInterfaceOrientationPortrait)
{
UIViewController *c = [[[UIViewController alloc] init] autorelease];
[c.view setBackgroundColor:[UIColor clearColor]];
[self presentViewController:c animated:NO completion:^{
// Made changes for Issue#2 (When Compiler level flag -DUSE_AUTOLAYOUT=0) as per iOS8 Competibility.
if([UICommonUtils checkIfiOS8])
{
[self performSelector:#selector(dismissSheet) withObject:nil afterDelay:0];
}
else
{
[self dismissViewControllerAnimated:NO completion:nil];
}
}];
}
}
}
-(void)dismissSheet
{
[self dismissViewControllerAnimated:NO completion:nil];
}
Any solution How can I fix it?

ios8 iPad uiwebview crashes while displaying popover when user taps drop down list HTML select tag

On ios8 and iPad if a uiwebview is displaying a HTML page containing a drop down list
eg this page http://www.w3schools.com/tags/tryit.asp?filename=tryhtml_select
then
repeatedly tap on the HTML drop down list that contain lists of cars . first item is Volvo.
tap every 1/2 second or so that uipopover opens and closes
app will crash:
Terminating app due to uncaught exception 'NSGenericException',
reason: 'UIPopoverPresentationController
() should have a non-nil
sourceView or barButtonItem set before the presentation occurs.'
Is there anyway to work around this in uiwebview in ios8?
It doesn't happen using wkwebview, but I would like to fix it in uiwebview.
Update: This seems to help but unsure of side effects. I have overridden the following in the view controller that contains the uiwebview.
-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
if (completion)
{
completion();
}
[super dismissViewControllerAnimated:NO completion:nil];
}
The solution mentioned in the question did not help me, however it did point me in the right direction.
After some investigation I would say it's some sort of race condition between presenting and removing the popover. As a workaround you can postpone the presentation in the delegate of the UIWebView:
-(void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_USEC), dispatch_get_main_queue(),
^{
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
});
}
The previous solutions did not help me.
There is a bug already logged to Apple (see openradar) for this.
The issue seems to be that the web view tries to present a view controller in a popover without setting the sourceView of the popover. Although it's definitely an Apple issue I have used the following workaround to avoid my app to crash:
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
{
// Override this method in the view controller that owns the web view - the web view will try to present on this view controller ;)
if (viewControllerToPresent.popoverPresentationController && !viewControllerToPresent.popoverPresentationController.sourceView) {
return;
}
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
}
I worked around it in the following way after noticing that sourceView is set in the cases where it crashes:
-(void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
UIPopoverPresentationController* pres = viewControllerToPresent.popoverPresentationController;
if(pres.sourceView) {
//log the fact you are ignoring the call
}
else {
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
}
}
I had different exception in the same scenario, and none of workarounds from here helped me.
This was my exception:
Terminating app due to uncaught exception 'NSRangeException', reason: '-[UITableView _contentOffsetForScrollingToRowAtIndexPath:atScrollPosition:]: row (4) beyond bounds (0) for section (0).'
This is code I used to workaround it:
-(void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
if ([viewControllerToPresent respondsToSelector:NSSelectorFromString(#"_cachedItems")]) {
if([viewControllerToPresent valueForKey:#"_cachedItems"] == nil) {
if (completion != nil) {
completion();
}
return;
}
}
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
}
It's very nasty workaround that prevents from showing dropdown in cases when it's about to crash, and this solution can stop working in any time as it uses internal properties. However it was the only solution that worked for me so maybe it will be helpful for someone.
I have decreased the probability of occurrence of crash in this way..
Used javascript code and native ios
Web Side code changes
Register a 'click' event listener to your html component(drop down).
In the call back method send notification to native code. ex : "window.location='fromJavaScript://PopoverIssue'; "
It will call uiwebviews shouldStartLoadWithRequest
Native Side code changes
Implement UIPopoverPresentationControllerDelegate protocol on viewcontroller which has uiwebview and over ride popoverPresentationControllerShouldDismissPopover popoverPresentationControllerDidDismissPopover
put below code in shouldStartLoadWithRequest method of uiwebview for the above click notification
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
self.popoverPresentationController = self.presentedViewController.popoverPresentationController;
self.existedPopoverDelegate = [self.popoverPresentationController delegate];
self.popoverPresentationController.delegate = self;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
int64_t delay = 2.0;
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
if([[UIApplication sharedApplication] isIgnoringInteractionEvents])
{
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}
});
});
implement the overridden protocol methods as follows
(BOOL)popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController
{
[self.existedPopoverDelegate popoverPresentationControllerShouldDismissPopover:popoverPresentationController];
return YES;
}
(void)popoverPresentationControllerDidDismissPopover:(UIPopoverPresentationController *)popoverPresentationController
{
[self.existedPopoverDelegate popoverPresentationControllerDidDismissPopover:popoverPresentationController];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
int64_t delay = 2.0;
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
if([[UIApplication sharedApplication] isIgnoringInteractionEvents])
{
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}
});
});
}
Hope it will help to decrease the crash occurrence .

Game center unavailable (player is not signed in )

I am integrating game center on cocos2d project.
GKLeaderboardViewController* leaderboardController = [[GKLeaderboardViewController alloc] init];
if (leaderboardController != nil){
NSLog(#"view make");
leaderboardController.leaderboardDelegate = self;
leaderboardController.timeScope = GKLeaderboardTimeScopeAllTime;
leaderboardController.category = #"myGameBoard";
[[CCDirector sharedDirector] presentViewController: leaderboardController animated: YES completion:nil];
show popup
Game Center unavailable
Player is not signed in
and no leaderboard appeared.
My Conditions are
1)testuser looks successfully loginnd
this message appeared on screen.
Welcome back testuser
*** sandbox ***
2)then this message appeared
Game Center unavailable
Player is not signed in
3)App upload is ready and pass the validate on organizer
4)app version on itunes connect and local is the same 1.00
5)Bundle Idenfier on itunes connect and local is the same
6)leaderboardController.category name 'myGameBoard' is correctly set.
is there any other point I need to investigate??
I solved this problem for TEST MODE in this way:
Go to Game Center App
Tab Friends
Click Setting
at the end of the screen: SANDBOX and LOGGING MUST BE ON MODE
I hope that it works for everyone
I solved this issue by ensuring the local user was authenticated and logged in:
- (void) authenticateLocalUser
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
if (![localPlayer isAuthenticated])
{
[localPlayer setAuthenticateHandler:(^(UIViewController* viewcontroller, NSError *error) {
if (!error && viewcontroller)
{
AppDelegate *appDelegate = (AppDelegate*) [[UIApplication sharedApplication] delegate];
UIViewController *currentViewController = [[appDelegate window] rootViewController];
[currentViewController presentViewController:viewcontroller animated:YES completion:nil];
}
})];
}
}

iOS 6 Orientation rotate automatically back to portrait on pressing done on MoviePlayer

I am using MoviePlayer controller to play a video in my iOS app. I am using orientation notification like this
if(deviceOrientation ==UIDeviceOrientationLandscapeLeft)
{
NSLog(#"Replay is in Landscape");
self.fullScreenFlag = YES;
[self.moviePlayer setFullscreen:YES animated:NO];
}
This makes my video screen to play in full screen when user turns the phone to landscape orientation. But when I press done button on moviePlayer control I go into following method
- (void)movieWillExitFullscreen:(NSNotification*)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if(deviceOrientation ==UIDeviceOrientationLandscapeLeft) {
NSLog(#"Pressed Done in Landscape");
//Problem: Here I want to force my VideoViewController to rotate back to portrait Mode
}
}
Not sure how can I make the VC to go back to portrait as soon as user pressed done button or video stops playing. I am aware to the moviePlayerNotificationMethods but what should I call in those method for orientation is not clear.
I solved this issue by having a separate view controller for the video playback.
So, you would have two view controllers
SomeViewController
MoviePlayerViewController
In your SomeViewController, when you want to play the movie:
MoviePlayerViewController *vc = [[MoviePlayerViewController alloc] initWithNibName:#"MoviePlayerViewController" bundle:nil];
[vc setPathToMovie:path];
[self.navigationController pushViewController:vc animated:YES];
[vc release];
And then in your MoviePlayerViewController
- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
[[self navigationController] popViewControllerAnimated:YES];
}
You can then lock down your SomeViewController to portrait, and if the user is in landscape when watching the video, they will return to portrait when popping back to SomeViewController.
I never found a solution using the deviceOrientation method and a modal MPMoviePlayerController. There may be one though!
I solved this by doing this in "moviePlayBackDidFinish"
UIViewController* forcePortrait = [[UIViewController alloc] init];
[self presentViewController:forcePortrait animated:NO completion:^{
[forcePortrait dismissViewControllerAnimated:NO completion:nil];
}];
It's not beautiful but it works like a charm :-)
Depending upon whether you using MPMoviePlayerController within a ViewController or as a separate ViewController the answer is as follows :-
Firstly :- This link will explain you how to restrict some views to portrait and allow others to rotate?
In that link you will see that, in the NavigationViewController you have made, following changes:-
-(BOOL)shouldAutorotate
{
return [[self.viewControllers lastObject] shouldAutorotate];
}
-(NSUInteger)supportedInterfaceOrientations
{
return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}
What it does is, it give the child to make their own decision if they want to auto-rotate or not.
Next the ViewController containing your MoviePlayer should do this :-
-(BOOL)shouldAutorotate
{
return YES;
}
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
Once you have done this, it gives the power of AutoRotation to your ViewController.
Now here's the tricky part, see I assume that you might have restricted your ViewController to Portrait, and since movie player allows you go fullscreen and in fullscreen when you rotate your screen it will turn to landscape, and now if you press done button it won't turn to portrait rather it will exit the fullscreen in landscape itself. In this case what you should do is, in your:-
- (BOOL)shouldAutorotate
{
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (orientation == UIInterfaceOrientationLandscapeRight || orientation == UIInterfaceOrientationLandscapeLeft) {
if ([[[self.navigationViewController.viewControllers] lastObject] class] == [MoviePlayerViewController class] ) {
return YES;
}
return NO;
}
return NO;
}
So, what it does is, you should auto-rotate only when the orientation is landscape and not when its portrait.
So far so good, next comes the MoviePlayer, considering that you have already played the Video and your only interest is when we click "Done" button it should auto-rotate to portrait.
Register for a notification to your MoviePlayer
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerWillExitFullScreen:) name:MPMoviePlayerWillExitFullscreenNotification object:_moviePlayer];
Then in the selector:
- (void) moviePlayerWillExitFullScreen:(NSNotification*)notification{
NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationPortrait];
[[UIDevice currentDevice] setValue:value forKey:#"orientation"];
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerWillExitFullscreenNotification object:_moviePlayer];
}
Tada! the magic is done! try out let me know ;-)

Can't beginReceivingRemoteControlEvents in iOS

In my app i want let user to control audio playback in background. I set backGround modes in .plist, and in plays in bg just like i wanted.But i can't get any response from touching the control buttons.
I set the AudioSession like this
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance]setActive:YES error:nil];
Then, in viewContoller where my player is placed i beginReceivingRemoteControlEvents like this
if ([[UIApplication sharedApplication] respondsToSelector:#selector(beginReceivingRemoteControlEvents)]){
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
[self becomeFirstResponder];
NSLog(#"Responds!");
}
And it prints Responds!
But the problem is that this method is never called
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
NSLog(#"Where is my event?");
if(event.type == UIEventTypeRemoteControl)
{
switch (event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
NSLog(#"Pause");
[self playWords:playButton];
break;
case UIEventSubtypeRemoteControlNextTrack:
NSLog(#"Next");
[self next];
break;
case UIEventSubtypeRemoteControlPreviousTrack:
NSLog(#"Prev");
[self prev];
break;
}
}
I even tried to write a category on UIApplication to let it become the first responder, but it doesn't help
#implementation UIApplication (RemoteEvents)
-(BOOL)canBecomeFirstResponder
{
return YES;
}
#end
Why can this happen?
SOLUTION
Here's what solved my problem Entering background on iOS4 to play audio
I have done the same work in my project, it's working fine. Please follow this, perhaps it will help you. Change the event name etc. In my code _audio is the object of AVAudioPlayer.
- (void)viewDidLoad {
NSError *setCategoryErr = nil;
NSError *activationErr = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: &setCategoryErr];
[[AVAudioSession sharedInstance] setActive: YES error: &activationErr];
}
- (void)viewWillAppear {
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
switch (event.subtype) {
case UIEventSubtypeRemoteControlPlay:
[_audio play];
break;
case UIEventSubtypeRemoteControlPause:
[_audio pause];
break;
default:
break;
}
}
There is a newer mechanism for listening to remote control events. For example, to execute a block when the headphone play/pause button is pressed:
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
NSLog(#"toggle button pressed");
return MPRemoteCommandHandlerStatusSuccess;
}];
or, if you prefer to use a method instead of a block:
[commandCenter.togglePlayPauseCommand addTarget:self action:#selector(toggleButtonAction)];
To stop:
[commandCenter.togglePlayPauseCommand removeTarget:self];
or:
[commandCenter.togglePlayPauseCommand removeTarget:self action:#selector(toggleButtonAction)];
You'll need to add this to the includes area of your file:
#import MediaPlayer;
For it to work in the background, you must have the background audio mode added to your app's capabilities.
Review this sample code from Apple for an example of usage. Likely your primary view controller is not actually becoming the first responder. An alternative usage is to place this code in your Application Delegate (which will always be the first responder) and respond to these events before they have had a chance to propagate, and potentially be consumed by other responders.
You have
respondsToSelector:#selector(beginReceivingRemoteControlEvents)]){
but in the method signature you have
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
The place where you are registering the signature is wrong. Just replace it by
respondsToSelector:#selector(beginReceivingRemoteControlEvents:)]){
You are missing the : which is making the app send the even to a non existing method I am assuming. I am surprised that your app is not crashing.