Delayed Class Selector Call - ios

I'm attempting to have a class method called with args on a delay, asynchronously, to hide a UILabel. Essentially, the label should appear, and then disappear in three seconds. I'm using the below to accomplish this.
Main method setting up the displayed view
+(void)queueError:(UILabel*)messageView errorText:(NSString*)errorText{
[messageView setText:errorText];
messageView.hidden = NO;
messageView.tag = arc4random_uniform(UINT32_MAX);
[UIView animateWithDuration:0.3 delay:0.0 options:0 animations:^(){
messageView.alpha = 1.0;
}completion:^(BOOL finished){
NSArray* args = [NSArray arrayWithObjects:messageView, [NSNumber numberWithInt:messageView.tag ], nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[UBSNavigationUtils class] performSelector:#selector(dequeueErrorTime:) withObject:args afterDelay:3];
});
}];
}
Method to be called after three second delay
+(void)dequeueErrorTime:(NSArray*)args{
UILabel* messageView = args[0];
NSInteger tag = [((NSNumber*)args[1]) integerValue];
if(messageView.tag == tag){
[[UBSNavigationUtils class] fadeOutError:messageView];
}
}
However, my method is never being called.

You put selector in the dispatch_queue runloop (technically as a timer), that isn't running. Therefore your method never called. I think, if you try invoke [[NSRunLoop currentRunLoop]run]; , method will get called.
From apple discussion about performSelector:afterDelay:
When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.

Instead of using dispatch_async, consider dispatch_after.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UBSNavigationUtils dequeueErrorTime:args];
});
Edit: I just wanted to help you clean up your code. :(
+(void)queueError:(UILabel*)messageView errorText:(NSString*)errorText{
[messageView setText:errorText];
messageView.hidden = NO;
NSInteger expectedTag = arc4random_uniform(UINT32_MAX);
messageView.tag = expectedTag;
[UIView animateWithDuration:0.3 delay:0.0 options:0 animations:^(){
messageView.alpha = 1.0;
} completion:^(BOOL finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UBSNavigationUtils dequeueErrorMessage:messageView tag:expectedTag];
});
}];
}
+ (void)dequeueErrorMessage:(UILabel *)messageView tag:(NSInteger)tag {
if(messageView.tag == tag) {
[[UBSNavigationUtils class] fadeOutError:messageView];
}
}

Related

iOS 13 Error: Main Thread Checker: UI API called on a background thread:-[UITableView reloadData] In-App Purchases [duplicate]

I am using RNFrostedSidebar for leftmenu. While dismissing the sidebar, below block of code is executing
- (void)handleTap:(UIPanGestureRecognizer *)recognizer {
[self dismissAnimated:YES completion:nil];
}
- (void)dismissAnimated:(BOOL)animated completion:(void (^)(BOOL finished))completion {
void (^completionBlock)(BOOL) = ^(BOOL finished){
[self rn_removeFromParentViewControllerCallingAppearanceMethods:YES];
rn_frostedMenu = nil;
if (completion) {
completion(finished);
}
};
if (animated) {
CGFloat parentWidth = self.view.bounds.size.width;
CGRect contentFrame = self.contentView.frame;
contentFrame.origin.x = self.showFromRight ? parentWidth : -_width;
CGRect blurFrame = self.blurView.frame;
blurFrame.origin.x = self.showFromRight ? parentWidth : 0;
blurFrame.size.width = 0;
self.blurView.frame = blurFrame;
self.blurView.alpha=0.1;
[UIView animateWithDuration:self.animationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
self.viewForTable.frame = contentFrame;
}
completion:completionBlock];
} else {
completionBlock(YES);
}
}
After dismissAnimated method executed i want to run a code to update the HomeViewController, That code is
[self callNavBar];
-(void) callNavBar {
[[NSNotificationCenter defaultCenter] postNotificationName:#"adjustNavBar"
object:self];
}
i tried this solution, like adding the methods in queue and executing but i got exception as
"Main Thread Checker: UI API called on a background thread: -[UIView bounds]"
Then i tried this too got the same error as above.
Your problem is because you didn't update ui inside UI thread. To resolve it, call dismissAnimated:completion: method inside main queue.
- (void)handleTap:(UIPanGestureRecognizer *)recognizer {
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissAnimated:YES completion:^(BOOL finished) {
[self callNavBar];
}];
});
}
You can put the function as the completion handler here, instead of nil.
That way it will run exactly when you want it.
It would look somewhat like this.
- (void)handleTap:(UIPanGestureRecognizer *)recognizer {
[self dismissAnimated:YES completion:[self callNavBar]];
}

How to write test case for UIViewAnimation in iOS

I want to write test case for animation block.
-(void)removeFooter {
[UIView animateWithDuration:0.3 animations:^{
self.contentOffset = CGPointMake(0, 0);
} completion:^(BOOL finished) {
if (!self.loadMoreDelegate) {
self.tableFooterView = nil;
}
}];
}
in test case need to check the tablefooterview, but self.tableFooterView = nil; execute after some time in completion block while test case run before it. Please tell me how to write test case for that.
You can call the "test case" method from the competition block
Keep in mind that it will be usually executed in that thread.
But if you want to execute it in the main one you can simply call the method in this way:
-(void)removeFooter {
[UIView animateWithDuration:0.3 animations:^{
self.contentOffset = CGPointMake(0, 0);
} completion:^(BOOL finished) {
if (!self.loadMoreDelegate) {
self.tableFooterView = nil;
}
[self performSelectorOnMainThread:#selector(myMethod:) withObject:nil waitUntilDone:YES];
}];
}
I got the solution.
Execute the task in anthor thread and and sleep the main thread till task completion.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
// task which take time to execute
self.tableView.refreshDelegate = self;
[self.tableView failedToRefreshWithMessage:#"test message"];
}];
sleep(2.5); // greater than task completion time
XCTAssertFalse(self.tableView.isPullToRefresh,#"refresh control not stop.");

Creating a method to perform animations and wait for completion using a semaphore in objective c

I am trying to create a method which makes use of UIView's "+animateWithDuration:animations:completion" method to perform animations, and wait for completion. I am well aware that I could just place the code that would normally come after it in a completion block, but I would like to avoid this because there is a substantial amount of code after it including more animations, which would leave me with nested blocks.
I tried to implement this method as below using a semaphore, but I don't think this is the best way to do it, especially because it doesn't actually work. Can anyone tell me what is wrong with my code, and/or what the best way of achieving the same goal is?
+(void)animateAndWaitForCompletionWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[UIView animateWithDuration:duration
animations:animations
completion:^(BOOL finished) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
I'm not sure what's wrong with my code but when I call the method as shown below, the completion block never runs and I end up in limbo.
[Foo animateAndWaitForCompletionWithDuration:0.5 animations:^{
//do stuff
}];
-------------------------------------------EDIT-------------------------------------------------
If anyone is facing a similar issue, you might be interested to see the code I used. It uses recursion to make use of each completion block, without having to nest each function call.
+(void)animateBlocks:(NSArray*)animations withDurations:(NSArray*)durations {
[Foo animateBlocks:animations withDurations:durations atIndex:0];
}
+(void)animateBlocks:(NSArray*)animations withDurations:(NSArray*)durations atIndex:(NSUInteger)i {
if (i < [animations count] && i < [durations count]) {
[UIView animateWithDuration:[(NSNumber*)durations[i] floatValue]
animations:(void(^)(void))animations[i]
completion:^(BOOL finished){
[Foo animateBlocks:animations withDurations:durations atIndex:i+1];
}];
}
}
which can be used like so
[Foo animateBlocks:#[^{/*first animation*/},
^{/*second animation*/}]
withDurations:#[[NSNumber numberWithFloat:2.0],
[NSNumber numberWithFloat:2.0]]];
In iOS 7 and later one would generally employ keyframe animation to achieve this effect.
For example, a two second animation sequence that is composed of four separate animations that take up 25% of the entire animation each would look like:
[UIView animateKeyframesWithDuration:2.0 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat animations:^{
[UIView addKeyframeWithRelativeStartTime:0.00 relativeDuration:0.25 animations:^{
viewToAnimate.frame = ...;
}];
[UIView addKeyframeWithRelativeStartTime:0.25 relativeDuration:0.25 animations:^{
viewToAnimate.frame = ...;
}];
[UIView addKeyframeWithRelativeStartTime:0.50 relativeDuration:0.25 animations:^{
viewToAnimate.frame = ...;
}];
[UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.25 animations:^{
viewToAnimate.frame = ...;
}];
} completion:nil];
In earlier iOS versions, you could queue up a series of animations a couple of ways, but I'd encourage you to avoid using a semaphore on the main thread.
One approach would be to wrap the animation in a concurrent NSOperation subclass, which doesn't complete until the animation does. You can then add your animations to your own custom serial queue:
NSOperationQueue *animationQueue = [[NSOperationQueue alloc] init];
animationQueue.maxConcurrentOperationCount = 1;
[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
viewToAnimate.center = point1;
}]];
[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
viewToAnimate.center = point2;
}]];
[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
viewToAnimate.center = point3;
}]];
[animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{
viewToAnimate.center = point4;
}]];
The AnimationOperation subclass might look like:
// AnimationOperation.h
#import <Foundation/Foundation.h>
#interface AnimationOperation : NSOperation
- (instancetype)initWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations;
#end
and
// AnimationOperation.m
#import "AnimationOperation.h"
#interface AnimationOperation ()
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#property (nonatomic, copy) void (^animations)(void);
#property (nonatomic) UIViewAnimationOptions options;
#property (nonatomic) NSTimeInterval duration;
#property (nonatomic) NSTimeInterval delay;
#end
#implementation AnimationOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (instancetype)initWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations {
self = [super init];
if (self) {
_animations = animations;
_options = options;
_delay = delay;
_duration = duration;
}
return self;
}
- (void)start {
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)main {
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:self.duration delay:self.delay options:self.options animations:self.animations completion:^(BOOL finished) {
[self completeOperation];
}];
});
}
#pragma mark - NSOperation methods
- (void)completeOperation {
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
- (BOOL)isAsynchronous {
return YES;
}
- (BOOL)isExecuting {
#synchronized(self) { return _executing; }
}
- (BOOL)isFinished {
#synchronized(self) { return _finished; }
}
- (void)setExecuting:(BOOL)executing {
if (_executing != executing) {
[self willChangeValueForKey:#"isExecuting"];
#synchronized(self) { _executing = executing; }
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void)setFinished:(BOOL)finished {
if (_finished != finished) {
[self willChangeValueForKey:#"isFinished"];
#synchronized(self) { _finished = finished; }
[self didChangeValueForKey:#"isFinished"];
}
}
#end
In my demonstration, above, I used a serial queue. But you could also use a concurrent queue, but use NSOperation dependencies to manage the relationship between the various animation operations. Lots of options here.
If you want to cancel the animations, you could then do something like the following:
CALayer *layer = [viewToAnimate.layer presentationLayer];
CGRect frame = layer.frame; // identify where it is now
[animationQueue cancelAllOperations]; // cancel the operations
[viewToAnimate.layer removeAllAnimations]; // cancel the animations
viewToAnimate.frame = frame; // set the final position to where it currently is
You could also incorporate this into a custom cancel method in the operation, too, if you want.
You appear to be using a semaphore to block the main thread until animations compete. Unfortunately the main thread is the thread performing those animations so this will never occur.
You must not block the main thread of your application. By doing so you prevent any view drawing or responding to user input. You will also soon trigger an OS watchdog which detects your app as unresponsive and terminates it.
Animations an their completion blocks are expressed as asynchronous operations for good reason. Try to embrace that in your design.
Semaphore can solve all the block synchronization problems.
Two points
Need to create a serial queue.
dispatch_semaphore_wait and dispatch_semaphore_signal can not be in the same queue.
Here is a example
- (dispatch_queue_t)queue {
if (!_queue) {
_queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
}
return _queue;
}
- (dispatch_semaphore_t)semaphore {
if (!_semaphore) {
_semaphore = dispatch_semaphore_create(0);
}
return _semaphore;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self funcA];
[self funcA];
[self funcA];
[self funcA];
[self funcA];
[self funcB];
}
- (void)funcA {
dispatch_async(self.queue, ^{
//Switch to main queue to perform animation
dispatch_async(dispatch_get_main_queue(), ^{
self.view.alpha = 1;
[UIView animateWithDuration:2 animations:^{
self.view.alpha = 0;
} completion:^(BOOL finished) {
NSLog(#"funcA completed");
//Unblock custom queue
dispatch_semaphore_signal(self.semaphore);
}];
});
//Block custom queue
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
});
}
- (void)funcB {
dispatch_async(self.queue, ^{
NSLog(#"funcB completed");
});
}
As Jonah said, there is no way to do that on the main thread. If you do not want to have nested blocks, there is nothing bad with it, but I understand your wish, simply put a method inside the block and the inner block then in the method. If you need closures in the inner block, you can pass them as an argument.
Doing so will enlarge your love to nested blocks. ;-)
As many people pointed out here, is true that the animation runs on the main thread and the semaphore usage stops the main thread. But this still can be done with a semaphore using this approach:
// create semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
// do animations
} completion:^(BOOL finished) {
// send signal
dispatch_semaphore_signal(semaphore);
}];
// create background execution to avoid blocking the main thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// wait for the semaphore
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// now create a main thread execution
dispatch_async(dispatch_get_main_queue(), ^{
// continue main thread logic
});
});

Stopping GCD when replacing scene?

I know its impossible to stop a GCD action that started, but i wonder what happens when it is trying to execute after i have already replace a view, means that i have a few GCD functions that loading images to scrollView, and sometimes the user wants to replace the scene while they are working, how should i stop it than ? or shouldn't i ?
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^
{
NSData *imgdata = [NSData dataWithContentsOfURL:url];
UIImage *image=[UIImage imageWithData:imgdata scale:1];
dispatch_async(dispatch_get_main_queue(), ^
{
[UIView transitionWithView:backView
duration:0.55f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^
{
backView.image=image;
}
completion:nil];
});
});
}
You could switch to NSOperation to get the more precise cancel mechanism. But it cannot guarantee that it will cancel if the operation is already in progress. As an option you can check if your backView still has superview.
If you removed a view from superview no animation work occurred but every step of transition lifecycle happens:
- (void)testUIViewOffScreen
{
UIWindow *aWindow = [[UIApplication sharedApplication] windows][0];
UIView *aView = [[UIView alloc] initWithFrame:CGRectMake(10.0, 10.0, 10.0, 10.0)];
aView.backgroundColor = [UIColor redColor];
[aWindow addSubview:aView];
[aWindow layoutIfNeeded];
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView transitionWithView:aView duration:1.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
NSLog(#"Transition will start now");
aView.frame = CGRectMake(100.0, 100.0, 100.0, 100.0);
} completion:^(BOOL finished) {
NSLog(#"finished: %#", finished ? #"YES" : #"NO");
}];
});
});
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
[aView removeFromSuperview];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate distantFuture]];
}
The output:
23.941 TestCase[70489:60b] Transition will start now
23.942 TestCase[70489:60b] finished: NO

how to do the animate the uiimageview and stop the remaining process

I am doing one application.In that i am doing the animation for uiimageview to show the different image like below
UIImageView * fearthersanimate = nil;
[fearthersanimate setCenter:sender.center];
fearthersanimate = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 120,200)];
[fearthersanimate setCenter:CGPointMake(sender.center.x, sender.center.y)];
fearthersanimate .animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"water_can_1.png"],
[UIImage imageNamed:#"water_can_2.png"],[UIImage imageNamed:#"water_can_3.png"],
[UIImage imageNamed:#"water_can_4.png"],[UIImage imageNamed:#"water_can_5.png"],
[UIImage imageNamed:#"water_can_6.png"],[UIImage imageNamed:#"water_can_7.png"],
[UIImage imageNamed:#"water_can_8.png"],
nil];
fearthersanimate.animationDuration = 1.0f;
fearthersanimate.animationRepeatCount = 1;
[self.view addSubview: fearthersanimate];
[fearthersanimate startAnimating];
But remaining operation is starting before end of this animation.But i need to do this animation first and until i need to stop the remaining process.
You can call your rest of operation in other method and call it after the same delay as you set animation time for images.
Below is the line of code by which you can call other method after some time interval
[self performSelector:#selector(restOfOperations) withObject:nil afterDelay:1.0];
//restOfOperations method defination/implementation
-(void)restOfOperations
{
//your code you want to perform after image animation
}
You can use the UIView animation block. For example:
[UIView animateWithDuration:0.5 animations:^{
} completion:^(BOOL finished) {
}];
Put the remaining process in the completion block.
Those animations are performed asynchronously, so you just need to fight async with async.
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(feathersanimate.animationDuration * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Remaining logic
});

Resources