cancelPreviousPerformRequestsWithTarget not cancelling an outstanding performSelector:withDelay - ios

I am using a UIWebView and don't want the navigation bar to appear unless the user taps anywhere on the screen that isn't a link.
So I have this code to display the navigation bar after a delay:
- (void)handleTapGesture:(UITapGestureRecognizer *)sender
{
....
[self performSelector:#selector(showNavigationBar) withObject:self afterDelay:0.2];
}
I'm not calling showNavigationBar immediately when the tap handler is invoked because the user might have tapped on a link in which case the tap hander is called before UIWebView shouldStartLoadWithRequest, so if I hid the navigation bar in shouldStartLoadWithRequest it would flash momentarily onto the screen.
So instead I set it to display after a delay which gives time for the following code to execute within shouldStartLoadWithRequest (and if the user didn't tap on a link shouldStartLoadWithRequest isn't called and the navigation bar is displayed, as it should be in that case).
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showNavigationBar) object:nil];
...
However this isn't working, I've increased the delay time to several seconds and can confirm cancelPreviousPerformRequestWithTarget is getting called before the navigation bar has been displayed, but when the specified time elapses the bar displays. cancelPreviousPerformRequestWithTarget is having no effect.
Does anybody know why its not working?

Your perform doesn't match your cancel. In the perform you're passing self as the object:
[self performSelector:#selector(showNavigationBar) withObject:self afterDelay:0.2];
In the cancel you're passing nil as the object:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showNavigationBar) object:nil];
They don't match, so the delayed perform should not be canceled.

In the documentation of that + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument method there is this sentence :
This method removes perform requests only in the current run loop, not all run loops.
If I'm interpreting it correctly it would mean that you need to cancel your action in the same run loop that you launched it. Which is clearly not what you want to do.
A way to go around this would be to have a flag that showNavigationBar would have to check to see if it should proceed or abort.

[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showNavigationBar) object:self];
That worked for me ;)

Not sure why but works like a charm for me.
dispatch_async(dispatch_get_main_queue(), ^{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
});

Related

When is ios auto screen capture taken?

I am trying to change the screen shot that is automatically captured by iOS when the app enters the background.
However I am not entirely sure exactly when this screenshot is taken.
For example:
If you pull down the notification bar while in the app the following method is called:
- (void) applicationWillResignActive:(UIApplication *) application {
}
Also if you double tap the home button while in the app the same method is called. In addition if an alert view is show 'applicationWillResignActive' is called.
But in both of these cases
- (void) applicationDidEnterBackground:(UIApplication *) application {
}
is not called.
So my question is, is there a screenshot captured after the call to applicationWillResignActive even if the application does not enter the background? Or does iOS only capture a screenshot after applicationDidEnterBackground?
Look at official doc - "Preventing Sensitive Information From Appearing In The Task Switcher".
It says applicationDidEnterBackground: should be used for the said purpose.
you can look over here, in summary, change the view before return from applicationDidEnterBackground:
Yes.
- (void) applicationWillResignActive:(UIApplication *) application {
}
is called when you pull down the notification bar or even when you double click the home button. You have to do something here to prevent your sensitive information to be captured by the OS. One workaround might be:
Set a blurry screen overlay before the app goes in the background
Once the app becomes active remove this overlay
Something like this:
-(void)applicationWillResignActive:(UIApplication *)application
{
imageView = [[UIImageView alloc]initWithFrame:[self.window frame]];
[imageView setImage:[UIImage imageNamed:#"blurryImage.png"]];
[self.window addSubview:imageView];
}
And then remove this overlay before the application enters foreground:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if(imageView != nil) {
[imageView removeFromSuperview];
imageView = nil;
}
}
is called when you pull down the notification bar or even when you double click the home button. You have to do something here to prevent your sensitive information to be captured by the OS. One workaround might be:
Set a blurry screen overlay before the app goes in the background
Once the app becomes active remove this overlay

Cancel perform selector method in recursion

I am repeating a function again and again using this code
- (void)refresh {
[self performSelector:#selector(refresh) withObject:nil afterDelay:5.0];
}
On viewWillDisappear I wrote the code to cancel this but still the function called. How can we cancel this perform selector repeating itself ?
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(refresh) object:nil];
}
please First of all , check this you might be running on different run loops in creation and cancellation.I faced this problem in past.
or
I think no need to write selector again. Just right this and try.
With this
// cancel the above call (and any others on self)
[NSObject cancelPreviousPerformRequestsWithTarget:self];
Refer apples document.
NSObject_Class
Discussion All perform requests are canceled that have the same target
as aTarget, argument as anArgument, and selector as aSelector. This
method removes perform requests only in the current run loop, not all
run loops.

How can I cancel the code in touchesEnded when touchesBegan begins again

I dont know if this is possible... I have this code in touchesEnded:
[self performSelector:#selector(GameOver) withObject:nil afterDelay:3];
However, if you touch the screen again before the 3 seconds is up, I want to cancel the calling of GameOver. Any idea how I can do this.
In touchesBegan, I tried something like this:
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:#selector(touchesEnded:withEvent:)
object:nil];
This did not work.
Your selector value is wrong in your cancelPreviousPerformRequestsWithTarget call, that's all. If the original selector in performSelector was GameOver then obviously the perform request you are canceling must be specified as GameOver.
This is perfectly clear from the documentation: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/index.html#//apple_ref/occ/clm/NSObject/cancelPreviousPerformRequestsWithTarget:selector:object:

Dealing with two screens and one activity indicator in iOS

I have 3 screens on my app.First is login. Second is search and third is process the task.
On login i retrieve data from a web service. It returns data in XML format. So the data is considerably large. So i am doing that task on a background thread like this to stop Mainthread freezing up on me:
-(BOOL)loginEmp
{
.....some computation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
[self getAllCustomerValues];
});
}
-(void)getAllCustomerValues
{
....more computation.Bring the data,parse it and save it to CoreData DB.
//notification - EDIT
NSNotification *notification =[NSNotification notificationWithName:#"reloadRequest"
object:self];
[[NSNotificationCenter defaultCenter] postNotification : notification];
}
//EDIT
//SearchScreenVC.m
- (void)viewDidLoad
{
....some computation
[self.customerActIndicator startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(stopActivityIndicator)
name:#"reloadRequest"
object:nil];
}
- (void)stopActivityIndicator
{
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
So on condition that login was successful, i move to screen 2. But the background thread is still in process( i know because i have logs logging values) . I want an activity indicator showing up here (2nd screen)telling user to wait before he starts searching. So how do i do it?How can i make my activity indicator listen/wait for background thread. Please let me know if you need more info.Thanks
EDIT: so I edited accordingly but the notification never gets called. I put a notification at the end of getAllCustomerValues and in viewDidLoad of SearchScreen i used it. That notification on 2nd screen to stop animating never gets called. What is the mistake i am doing.?Thanks
EDIT 2: So it finally hits the method. I dont know what made it to hit that method. I put a break point. I wrote to stop animating but it wouldn't. I wrote hidesWhenStoppped and hidden both to YES. But it still keeps animating.How do i get it to stop?
Ok, if it is not the main thread, put the following in and that should fix it.
- (void)stopActivityIndicator
{
if(![NSThread isMainThread]){
[self performSelectorOnMainThread:#selector(stopActivityIndicator) withObject:nil waitUntilDone:NO];
return;
}
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
Could you put your background operation into a separate class and then set a delegate on it so you can alert the delegate once the operation has completed?
I havent tried this, its just an idea :)
You could use a delegate pointing to your view controller & a method in your view controller like:
- (void) updateProgress:(NSNumber*)percentageComplete {
}
And then in the background thread:
float percentComplete = 0.5; // for example
NSNumber *percentComplete = [NSNumber numberWithFloat:percentComplete];
[delegate performSelectorOnMainThread:#selector(updateProgress:) withObject:percentageComplete waitUntilDone:NO];

Youtube dismissal event (iOS)

Good day,
Through the use of an UIWebview I have now a working method to show a youtube video within my app (using the tag, finding the play button within the webview and firing the touch event on that).
Works like a charm. The video pops up and plays. However I would like to recieve an event when the video ends or the user clicks the done button.
On the internet I have found that there is an event: MPAVControllerItemPlaybackDidEndNotification where you can listen to. However this one does not get called.
After some further research I found that for Youtube Videos embedded through UIWebView another notification was called ( UIMoviePlayerControllerDidExitFullscreenNotification ). Unfortunately that one does not work either anymore. ( found it here )
Does anyone have any idea how I can do some action after the video is done playing or has been dismissed?
Thanks
Use the UIMoviePlayerControllerWillExitFullscreenNotification for getting notified once the user tapped on the DONE button. The UIMoviePlayerControllerDidExitFullscreenNotification seems indeed to be omitted on iOS6.
Mind that ...Did... vs. ...Will... difference!
For more on that subject, once again check my updated answer within that posting you referenced in your question.
Let's look at this scenario :
In your view, you have a button. When it's clicked, you want to play the video directly.
In order, to do so, you open the webview as a modal view of your view :
[self presentModalViewController:videoWebView animated:NO];
For your webview, you should use Youtube API to integrate and autoplay the video. See the proposed working example here : https://stackoverflow.com/a/15538968
You'll see that the video is launched in a modal view of your webview view. One way to detect when the video is dismissed (when the "done" button has been clicked) is to use the viewDidAppear on your webview view class. In this method you will then dismiss the webview view too but...when this view is launched at first, you don't want to dismiss it. You can add a boolean property to avoid that.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (_videoLaunched) {
[self dismissModalViewControllerAnimated:YES];
}
}
In the viewDidLoad method, set this property to NO and in the webViewDidFinishLoad method (delegate method of webview) set it to YES.
I think it answers one part of your question. Concerning the detection of the end of the video you have to modify you YT_Player.html file to listen to state changes.
ytPlayer = new YT.Player('media_area', {height: '100%', width: '100%', videoId: 'SbPFDcspRBA',
events: {'onReady': onPlayerReady, 'onStateChange': onPlayerStateChange}
function onPlayerStateChange(e) {
var result = JSON.parse(event.data);
if (result.info == 0) { // Video end
window.location = "videomessage://end";
}
}
});
You will then catch the event in your webview view and dismiss it like this :
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = request.URL;
if ([[url scheme] isEqualToString:#"videomessage"]) {
[self dismissModalViewControllerAnimated:YES];
return YES;
}
return YES;
}
What you need here is something like this:
- (void)playerWillExitFullscreen:(NSNotification *)notification
{
//do something...
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerWillExitFullscreen:)
name:#"MPMoviePlayerWillExitFullscreenNotification" object:nil];

Resources