Cancelling / stopping an executing method (selector) - Objective-C, iOS - ios

Hifellows, please consider following scenario:
I have 3 methods, where on each create and add some objects like imageviews,activityindicators,etc.
So imagine:
- (void)anim1 {
// do some verifications like internet reachability. So create imageview1, imageview2, imageview3, activityindicator then add to subView.
[UIView animateWithDuration:1 delay:0.3 options:0 animations:^{
// Animate the alpha value of your imageView from 1.0 to 0.0 here
//...
} completion:^(BOOL finished) {
if(![(AppDelegate*)[[UIApplication sharedApplication] delegate] hasConnectivity])
{
NSLog(#"No Internet...");
return;
}
else {
// If i got internet connection, i create another imageview (checked image) and add to subview. So play with alpha animations then call the second method.
[UIView animateWithDuration:0.8 delay:0.8 options:0 animations:^{
someobject.alpha = 0.0f;
[someobject setAlpha:0.5f];
[someobject setAlpha:0.5f];
} completion:^(BOOL finished) {
[self performSelector:#selector(anim2) withObject:nil afterDelay:0.1];
NSLog(#"Got Internet...");
}];
return;
}
}];
}
So... anim2:
- (void)anim2 {
// do some URL connections (POST FORM) with blocks using AFNetworking framework. So, depending on results of URL connection, i work with imageview2, add an activityindicator (while URL processing) then add to subView. To resume, let consider this:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.somepage.com"]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Response OK..");
// Here i create another imageview (checked image) and add to subview. So play with alpha animations then call the third method.
[UIView animateWithDuration:0.8 delay:0.8 options:0 animations:^{
someobject.alpha = 0.0f;
[someobject setAlpha:0.5f];
[someobject setAlpha:0.5f];
} completion:^(BOOL finished) {
[self performSelector:#selector(anim3) withObject:nil afterDelay:0.1];
}];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Response error. Aborting.");
}
}];
[operation start];
}
Finally, anim3 method:
- (void)anim3 {
// Work with imageview3, another activityindicator, then after animation delay, i create a new imageview (checked image). Then enable a uibutton.
...
}
So, when i start anim1, it do the verifications, then follow 'sequence', calling anim2... And so on... Anim1 = OK -> Anim2 = OK -> Anim3 -> Button enabled -> Clicked -> Set all imageviews to alpha = 0.70f.
All works as well, but i have create an another method to 'clean' these views, then start again.
Basically, it cancels all selectors, then removesfromSuperView all imageviews that have been created. And, call anim1 again.
Please consider:
-(void)cleanAnimViews
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(anim1) object:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(anim2) object:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(anim3) object:nil];
NSArray* subviews = [[NSArray alloc] initWithArray: scrollview.subviews];
for (UIView* view in subviews) {
if ([view isKindOfClass:[UIActivityIndicatorView class]]) {
[view removeFromSuperview];
}
}
for (UIView* view in subviews) {
if ([view isKindOfClass:[UIImageView class]] && view.tag == 200) {
[view removeFromSuperview];
}
if ([view isKindOfClass:[UIImageView class]] && view.tag == 201) {
[view removeFromSuperview];
}
if ([view isKindOfClass:[UIImageView class]] && view.tag == 202) {
[view removeFromSuperview];
}
if ([view isKindOfClass:[UIImageView class]] && view.tag == 203) {
[view removeFromSuperview];
}
if ([view isKindOfClass:[UIActivityIndicatorView class]] && view.tag == 204) {
[view removeFromSuperview];
}
if ([view isKindOfClass:[UIImageView class]] && view.tag == 250) {
[view removeFromSuperview];
}
}
[self performSelector:#selector(anim1) withObject:nil afterDelay:0.8];
}
I call to 'cleanAnimViews' on viewWillAppear, and using a RefreshControl object.
My problem:
When all tasks (think anim1, anim2 and anim3) already done, THEN i call 'cleanAnimViews' using pull to refresh, it beautifully removes all imageviews then start again from first method (anim1).
But, if i call 'cleanAnimViews' using pull to refresh during animX proccessing (between anim1 to anim2, or anim2 to anim3), i got mess because the 'cleanAnimViews' removes all imageviews, but the 'anim1 -> anim2 -> anim3' continues being processing at same time... So, i got two (or three) activityindicator (eg: from anim1 and anim3), mess with objects controls, because it executing an older 'proccess' on anim2 (eg) and new one to anim1 in sametime...
Let me explain better:
viewDidAppear ->
started anim1:
... This created the 3 images... Create ActivityIndicator... Verification has done... Create a "checked" imageview... So it perform selector anim2.
started anim2...
... This works with objects controls, Create ActivityIndicator, Verification has done... Create a "checked" imageview...
----> NOW i call "cleanAnimViews" from pull to refresh. But the anim2 already performed selector anim3...
So, it removes all imageview and at end of method start anim1 again (from cleanAnimViews), and as anim3 method already has started, it create imageviews, activityindicator and checked image again.. AND anim1 is doing ALL the process again on same time..
There is a way to 'cancel/stop' these animX method when i call 'cleanAnimViews'?
I ask this because when the view disappear and appear again all works beautifully. It stops all animX, and when appear again call anim1 again...
I have tried creating a BOOL flag on 'cleanAnimViews', then play with it on animX methods.. but dont works..
Sorry for long post, typos, and if i didn't so much clear... But i got no more idea.
Thanks in advance.

Yes there is a way to cancel all animation on your someObject so you have to call [someObject.layer removeAllAnimations];.
PS Don't forget to add QuartzCore so that you can access the layer.

Related

keyboard not show at first time(iOS)

My code like this:
- (void)setupSubViews {
[self addSubview:self.textView];
[self addSubview:self.sendButton];
[self.textView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(5);
make.bottom.equalTo(-5);
make.leading.equalTo(9);
}];
[self.sendButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self);
make.trailing.equalTo(-9);
make.leading.equalTo(self.textView.mas_trailing).offset(7);
make.width.equalTo(60);
make.height.equalTo(40);
}];
}
The show function to make the textView becomeFirstResponder
- (void)show {
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
[keyWindow addSubview:self];
[self mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.equalTo(0);
make.bottom.equalTo(InputHeight);
}];
[self.textView becomeFirstResponder];
}
This view add to the keyWindow at the bottom of mainScreen, it's translate will changing follow the keyboard when the keyboard's frame changed.
- (void)keyboardWillChangeFrame:(NSNotification *)noti {
if (!self.textView.isFirstResponder) {
return;
}
NSValue *endFrame = noti.userInfo[#"UIKeyboardFrameEndUserInfoKey"];
CGFloat endY = [endFrame CGRectValue].origin.y;
BOOL isBackToBottom = endY == UI_SCREEN_HEIGHT;
CGFloat needOffset = endY - (UI_SCREEN_HEIGHT + InputHeight);
if (isBackToBottom) {
[UIView animateWithDuration:0.25 animations:^{
self.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
} else {
[UIView animateWithDuration:0.25 animations:^{
self.transform = CGAffineTransformMakeTranslation(0, needOffset);
}];
}
}
Use Case:
viewController has a button, button click action like this:
- (void)beginChat {
[self.inputView show];
}
self.inputView is the above customView,
- (LiveChatInputView *)inputView {
if (!_inputView) {
_inputView = [LiveChatInputView new];
_inputView.sendBlock = ^(NSString *string) {
....
};
}
return _inputView;
}
Now my question is that when we call show function at the first time after application launched, the keyboard will not show, but call show function in second time, every thing is fine.
How to deal with this question, THX.
Calling this method is not a guarantee that the object will become the first responder.
As per Apple,
A responder object only becomes the first responder if the current
responder can resign first-responder status (canResignFirstResponder)
and the new responder can become first responder.
Try calling becomeFirstResponder like below,
[self.textView performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0];
I made a mistake, call [self removeFromSuperview] in the function of keyboardWillChangeFrame:.
I thought UIKeyboardWillChangeFrameNotification could only post it once at the keyboard show or hide, so i put the implementation of back action in keyboardWillChangeFrame:, and controlled by endFrame.origin.y == UI_SCREEN_HEIGHT
In practice, the fact is that:
when the keyboard show, NSNotificationCenter will post UIKeyboardWillChangeFrameNotification three times:
first
second
third
Tips:
only first time show the keyboard after application launched, the first UIKeyboardWillChangeFrameNotification of above three, the endFrame.origin.y is equal to UI_SCREEN_HEIGHT
My solution:
Observer the UIKeyboardWillHideNotification, and do back action in this callback.
Thanks for all answers of this question.

Stop UIView animation or detect if view is animating

There's an array with uiviews. Their frames are set due to current interfaceOrientation
Then we add these views to self.view programmatically using
-(void)placeView
{
if ([self.arrayWithViews count] > 0)
{
[UIView animateWithDuration:1.f animations:^{
UIView *currentView = [self.arrayWithViews lastObject];
[currentView setAlpha:0.f];
[self.view addSubview:currentView];
[currentView setAlpha:1.f];
[self.arrayWithViews removeLastObject];
} completion:^(BOOL finished) {
[self placeView];
}];
}
}
Now, if the orientation change happens the views keep adding (with coordinates corresponding to previous orientation).
The question is - how to stop the animation process or how to prevent interface orientation change while the animation takes place.
P.S. [self.view.layer removeAllAnimations] doesn't work (I didn't figure out why)
Could you please advise what to do?
removeAllAnimations will still call completion block, so you repeat to call placeView, so you need add a flag:
-(void)placeView
{
if(!self.runAni){ //Add a flag to stop repeat to call [self placeView]
return;
}
if ([self.arrayWithViews count] > 0)
{
[UIView animateWithDuration:1.f animations:^{
UIView *currentView = [self.arrayWithViews lastObject];
[currentView setAlpha:0.f];
[self.view addSubview:currentView];
[currentView setAlpha:1.f];
[self.arrayWithViews removeLastObject];
} completion:^(BOOL finished) {
[self placeView];
}];
}
}
to stop animation, you should do :
self.runAni = NO;
[self.view.layer removeAllAnimations];
begin to do:
self.runAni = YES;
[self placeView];

Show UIImageView on screen Tap

I am trying to Display a an Image when the user Taps on the UIImageView. But before the user Taps the UIImageView the Image should not be shown, and after a few seconds the Image should disappear again. Does anyone know how to do this? I read through couple of Threads but they do not work with the latest Xcode as it appears. Thanks for your help and time.
Udate
Well, my code now looks like this:
-(void)imageTapped:(UITapGestureRecognizer*)recognizer
{
recognizer.view.alpha=0.0;
((UIImageView*)recognizer.view).image = [UIImage imageNamed:#"twingo_main.png"];
[UIView animateWithDuration:1.0 delay:2.0 options:0 animations:^{
recognizer.view.alpha=1.0;
} completion:^(BOOL finished) {
recognizer.view.alpha=0.0;
((UIImageView*)recognizer.view).image = nil;
recognizer.view.alpha=1.0;
}];
}
- (void)viewDidLoad
{
UIImageView *hiddenImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 30, 20, 20)];
hiddenImage.userInteractionEnabled=YES;
[hiddenImage addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageTapped:)]];
[self.view addSubview:hiddenImage];
Well, now my question is, how do I need to set up the UIImageView in the View Controller?
In your UIViewController's viewDidLoad method:
...
UIImageView *hiddenImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 30, 20, 20)];
hiddenImage.userInteractionEnabled=YES;
[hiddenImage addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageTapped:)]];
[self.view addSubview:hiddenImage];
...
UITapGestureRecognizer Handler:
-(void)imageTapped:(UITapGestureRecognizer*)recognizer
{
recognizer.view.alpha=0.0;
((UIImageView*)recognizer.view).image = [UIImage imageNamed:#"imageName"];
[UIView animateWithDuration:1.0 delay:2.0 options:0 animations:^{
recognizer.view.alpha=1.0;
} completion:^(BOOL finished) {
recognizer.view.alpha=0.0;
((UIImageView*)recognizer.view).image = nil;
recognizer.view.alpha=1.0;
}];
}
Set the image in the UIImageView to nil initially. Add a tap gesture recognizer to the UIImageView that, when fired, sets the image and starts a timer. When the timer completes, set your image back to nil.
Here is an another way to handle above with NSTimer..
-(void)imageTapped:(UITapGestureRecognizer*)recognizer
{
CustomPopUpView *lCustomPopUpView = [[CustomPopUpView alloc]init];
//Added your imageview to Custom UIView class
[self.window addSubview:lCustomPopUpView];
[self.window bringSubviewToFront:lCustomPopUpView];
mPopupTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(closePopUp) userInfo:Nil repeats:FALSE];
}
- (void)closePopUp{
if (mPopupTimer != nil) {
[mPopupTimer invalidate];
mPopupTimer = nil;
}
for (UIView *lView in self.window.subviews) {
if ([lView isKindOfClass:[CustomPopUpView class]]) {
[lView removeFromSuperview];
}
}
}
Its better to have view behind the UIImageView to handle the TapGesture.
But here is the fix for you code:
Add this to viewDidLoad method to setup you image view to handle tap gesture
//By default the UserInteraction is disabled in UIImageView
[self.testImageView setUserInteractionEnabled:YES];
UITapGestureRecognizer *tapgesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(ShowImage:)];
tapgesture.numberOfTapsRequired=1;
tapgesture.numberOfTouchesRequired=1;
[self.testImageView addGestureRecognizer:tapgesture];
here is the method handle the gesture event
-(void)ShowImage:(UIGestureRecognizer*)recognizer{
recognizer.view.alpha=0.0;
((UIImageView*)recognizer.view).image = [UIImage imageNamed:#"clone.jpg"];
[UIView animateWithDuration:1.0 delay:0 options:0 animations:^{
recognizer.view.alpha=1.0;
} completion:^(BOOL finished) {
//[self performSelector:#selector(hideImage:) withObject:recognizer.view afterDelay:10];
[UIView animateWithDuration:1.0 delay:3.0 options:0 animations:^{
recognizer.view.alpha=0.0;
} completion:^(BOOL finished) {
((UIImageView*)recognizer.view).image=nil;
((UIImageView*)recognizer.view).alpha=1.0;
}];
}];
}
If you set the alpha of view to 0, then your view will not receive any touch event further. So its best practice to set it again to 1.0 after removed the image from UIImageView.
You may want to consider using a UIButton. Detecting touches with these is easy - as is changing their image.
You could also subclass UIControl (see http://www.raywenderlich.com/36288/how-to-make-a-custom-control).

Cannot modify the properties of any IBOutlets within file

Currently I'm in an AVCaptureMetadataOutputObjectsDelegate, with a storyboard created method of presenting the data to the user. However, when I call a method from another view controller to pass the data between the two, I can retrieve the data just fine and NSLog it accordingly but cannot assign or update any of the properties of the view controller at all. I.e. updating label text, moving UIView etc... Here's what the code is looking like,
- (void) retrieveObject: (ScannedItem *) object
{
if(object == NULL)
nil;
else
self.scannedObject = object;
NSLog(#"RETRIEVED IN V2: %#", self.scannedObject.itemName);
[self performSelector:#selector(animateResults:) withObject:self afterDelay:0];
}
- (void) refreshBarcode: sender
{
NSLog(#"%#", detectionString);
if(detectionString != nil)
{
[self performSelector:#selector(retrieveOnlineData:) withObject:self afterDelay:0];
}
}
- (void) animateResults: sender
{
self.productName.text = #"Hey";
if(resultsViewExpanded == YES)
{
NSLog(#"hiding");
[UIView animateWithDuration:0.5 animations:^{
self.resultsView.frame = CGRectMake(0, 531, self.resultsView.frame.size.width, self.resultsView.frame.size.height);
}];
resultsViewExpanded = NO;
}
else
{
NSLog(#"showing");
self.productName.text = [NSString stringWithFormat:#"%#", self.scannedObject.itemName];
[UIView beginAnimations:#"ShowButtons" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.5];
self.resultsView.frame = CGRectMake(0, 364, self.resultsView.frame.size.width, self.resultsView.frame.size.height);
[UIView commitAnimations];
resultsViewExpanded = YES;
}
}
Ignore the horrific formatting, I have no clue what I'm doing with it. Thanks in advance for any support. :)
I think, you are updating, modifying the properties before view Load of second controller. Before View Load, if you update anything, it will not reflect, because update of properties will call after Controller navigated.

UIView unchanged when modified from dispatch_async

I have a subview with live view from the back camera. I am animating it when toggling the camera to front. Because I could't sync the animation and the toggle, I covered it with another view with plain black background. Because toggle is slower, I want to remove the black subview after toggle was performed. But when I do it, it won't disappear. Debugger shows all NSLogs I put in, and the debugger even stops when I put breakpoints anywhere, so the code is reachable. But the UIView is simply not modified and still holds the black subview.
Interesting fact: when I turn display off with the top button and then back on again, the black view is gone!
CODE:
- (IBAction)cameraChanged:(id)sender {
NSLog(#"Camera changed");
UIView *animView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.videoPreviewView.frame.size.width, self.videoPreviewView.frame.size.height)];
animView.tag = 42;
[animView setBackgroundColor:[UIColor blackColor]];
[UIView transitionWithView:self.videoPreviewView duration:1.0
options:self.animate ? UIViewAnimationOptionTransitionFlipFromLeft:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[self.videoPreviewView addSubview:animView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul), ^{
[self.captureManager toggleCamera];
NSLog(#"Done toggling");
[self done];
});
}
completion:^(BOOL finished){
if (finished) {
//
}
}];
self.animate = !self.animate;
}
- (void)done {
for (UIView *subview in [self.videoPreviewView subviews]) {
NSLog(#"In for");
if (subview.tag == 42) {
NSLog(#"removing");
[subview removeFromSuperview];
}
}
}
Anybody knows why is this happening and how to solve it?
UIView Update should be run on Main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul), ^{
//BlahBlah
dispatch_sync(dispatch_get_main_queue(), ^{
//update your UIView
});
});

Resources