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
Related
I want to do a save image function, after the success of the pop-up tips. When I press the picture pop-up alertController, save the success did not see the prompt box displayed on the keywindow, what is going on ?
//show the tip of suceess
-(void)show{
UIWindow * window =[UIApplication sharedApplication].keyWindow;
[window addSubview:self];
[self mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(window.mas_top);
make.leading.equalTo(window.mas_leading);
make.trailing.equalTo(window.mas_trailing);
make.bottom.equalTo(window.mas_bottom);
}];
[self layoutIfNeeded];
self.alpha = 0;
[UIView animateWithDuration:.2 animations:^{
self.alpha = 1;
}];
_alertView.transform = CGAffineTransformMakeScale(0.1, 0.1);
[UIView animateWithDuration:0.3 delay:0.0 usingSpringWithDamping:0.7 initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseOut animations:^{
_alertView.alpha=1.0;
_alertView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
}];
}
Try this
// Delay execution of my block for 10 seconds.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
UIView * windowView =[UIApplication sharedApplication].keyWindow.rootViewController.view;
[windowView addSubview:self];
});
I need to animate a UIImageView inside my application, and cyclically change the UIImage inside it, making it look like an animated photo slideshow.
Right now I'm using an NSTimer to fire every N seconds the UIImage change and the animation itself:
- (void)viewDidLoad {
// NSArray initialization
NSTimer *timer = [NSTimer timerWithTimeInterval:16
target:self
selector:#selector(onTimer)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[timer fire];
[super viewDidLoad];
}
This is the onTimer selector code:
- (void) onTimer {
// cycle through max 9 images
if(imageIndex > 8)
imageIndex = 0;
// set the image
[_imageContainer setImage:[images objectAtIndex:imageIndex]];
// reset width and height of the UIImage frame
CGRect frame = [_imageContainer frame];
frame.size.width -= 170.0f;
frame.size.height -= 100.0f;
[_imageContainer setFrame:frame];
// fade in
[UIView animateKeyframesWithDuration:2.0f delay:0.0f options:0 animations:^{
[_imageContainer setAlpha:1];
} completion:^(BOOL finished) {
// image movement
[UIView animateKeyframesWithDuration:12.0f delay:0.0f options:0 animations:^{
CGRect frame = [_imageContainer frame];
frame.size.width += 170.0f;
frame.size.height += 100.0f;
[_imageContainer setFrame:frame];
} completion:^(BOOL finished) {
// fade out
[UIView animateKeyframesWithDuration:2.0f delay:0.0f options:0 animations:^{
[_imageContainer setAlpha:0];
} completion:nil];
}];
}];
imageIndex++;
}
This seem a very raw but "working" way to achieve what I want but I recognize it might not be the ideal way.
Is there any better method to achieve what I'm looking for?
Updated Answer For Fade Animation
- (void)viewDidLoad
{
[super viewDidLoad];
// self.animationImages is your Image Array
self.animationImages = #[[UIImage imageNamed:#"Image1"], [UIImage imageNamed:#"Image2"]];
// make the first call
[self animateImages];
}
- (void)animateImages
{
static int count = 0;
UIImage *image = [self.animationImages objectAtIndex:(count % [animationImages count])];
[UIView transitionWithView:self.animationImageView
duration:1.0f // animation duration
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.animationImageView.image = image; // change to other image
} completion:^(BOOL finished) {
[self animateImages]; // once finished, repeat again
count++; // this is to keep the reference of which image should be loaded next
}];
}
- (IBAction)press:(id)sender {
UIImage *bomb = [UIImage imageNamed:#"bom"];
UIImageView *bom =[[UIImageView alloc] initWithImage:bomb];
[bom setFrame: CGRectMake(83,115,35,35)];
[self.view addSubview:bom];
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationCurveEaseIn animations:^{
[bom setFrame:CGRectMake(149,47,35,35)];
}
completion:^(BOOL finished)
{
[bom setFrame:CGRectMake(134,40,60,55)];
[bom setImage:[UIImage imageNamed:#"splash"]];
hits++;
_hit.text=[NSString stringWithFormat:#"%i",hits];
}];
}
Here is my code! The object bom here handles the imageview for splash.png image and i want that image to be appeared only for 0.5 sec when each time the button is pressed
You could set up an NSTimer that calls a method which hides the image. You would have to keep a reference to the bomb.
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(hideImage) userInfo:nil repeats:NO];
-(void)hideImage {
self.bomb.hidden = YES;
}
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];
}
}
I have a UIViewController and a Category for adding methods to the UIViewController. There is a method in the category:
#implementation UIViewController (AlertAnimationsAndModalViews)
-(void)someAddedMethod
{
UIView *someView;
//do some animation with the view that lasts 3 seconds
//remove the view and return
}
And in any view controller i can call this method
[self someAddedMethod];
However, i only want to allow this method to run one at a time. For example, if i make two calls one after the other
[self someAddedMethod];//call1
[self someAddedMethod];//call2
i want the second call to wait until the first call has completed. I understand that UIView animationWithduration... is run in a seperate thread, and seeing as i cant create iVars in the category i cant really use #synchronized(someObject)..
Any advice?
Thanks in advance!
EDIT
The method looks like this:
-(void)showTopBannerWithHeight:(CGFloat)height andWidth:(CGFloat)width andMessage:(NSString *)message andDuration:(CGFloat)duration
{
UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, -height, width, height)];
[self.view.superview addSubview:messageLabel];
[UIView animateWithDuration:0.5
delay:0
options: UIViewAnimationOptionBeginFromCurrentState
animations:^{
messageLabel.frame = CGRectMake(0, 0, SCREEN_WIDTH, height);
}
completion:^(BOOL finished){
[UIView animateWithDuration: 0.5
delay:duration
options: UIViewAnimationOptionBeginFromCurrentState
animations:^{
messageLabel.frame = CGRectMake(0, -height, SCREEN_WIDTH, height);
}
completion:^(BOOL finished){
[messageLabel removeFromSuperview];
}];
}];
}
So i show a "banner" from the top of the screen, wait for a duration (CGFloat) then slide the view out of the screen and remove. As this is in a category i can't add instance variables..
so what i want to achieve is that if more than one call to this method is made, i want the first call to execute without waiting, but each call after that to wait until the previous call has finished.
If its just about the animations you may check if ([someView.layer animationForKey:#"sameKeyAsOnCreation"] == nil). Than you will only add an animation, if it is not currently runnning.
You could also use associated objects to store the state on your own (animation running / not running).
Assuming you want to start next animation after previous one has finished.
This way you can use some shared NSMutableArray* _animationQueue storage:
-(void)someAddedMethod
{
NSTimeInterval duration = 3.0;
void (^animationBlock)() = ^{
//do some animations here
self.view.frame = CGRectOffset(self.view.frame, 40, 0);
};
__block void (^completionBlock)(BOOL) = ^(BOOL finished){
[_animationQueue removeObjectAtIndex:0];
if([_animationQueue count]>0) {
[UIView animateWithDuration:duration animations:_animationQueue[0] completion:completionBlock];
}
};
[_animationQueue addObject:animationBlock];
if([_animationQueue count]==1) {
[UIView animateWithDuration:duration animations:animationBlock completion:completionBlock];
}
}
Note, you don't need any #synchronized features since everything goes on main thread.
UPDATE: the code below does exactly you need:
-(void)showTopBannerWithHeight:(CGFloat)height andWidth:(CGFloat)width andMessage:(NSString *)message andDuration:(CGFloat)duration
{
static NSMutableArray* animationQueue = nil;
if(!animationQueue) {
animationQueue = [[NSMutableArray alloc] init];
}
void (^showMessageBlock)() = ^{
UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width, height)];
messageLabel.text = message;
[self.view.superview addSubview:messageLabel];
[UIView animateWithDuration: 0.5
delay:duration
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
messageLabel.frame = CGRectOffset(messageLabel.frame, 0, -height);
}
completion:^(BOOL finished){
[messageLabel removeFromSuperview];
[animationQueue removeObjectAtIndex:0];
if([animationQueue count]>0) {
void (^nextAction)() = [animationQueue objectAtIndex:0];
nextAction();
}
}];
};
[animationQueue addObject:showMessageBlock];
if([animationQueue count]==1) {
showMessageBlock();
}
}
Try to use this one.And used self in #synchronized directive.
- (void)criticalMethod {
#synchronized(self) {
// Critical code.
}
}
Note: The #synchronized() directive takes as its only argument any Objective-C object, including self. This object is known as a mutual exclusion semaphore or mutex. It allows a thread to lock a section of code to prevent its use by other threads.