I have a simple timer that on completion shows the message "Rest Time Complete" I want that message to stay on the screen for one second, then dismiss a view. I've tried a few things that I'll detail below, but I can never get the message to display, pause, then dismiss the view. The timer pauses on the last number in the timer, then the message always appears as the view is being dismissed. Here is my code:
- (void)restTimerComplete
{
self.restTimeLabel.text = #"Rest Period Complete!";
[NSThread sleepForTimeInterval:1];
[self hideRestTimerAnimated:YES];
}
I've also tried using the simple sleep(1), but that has the same end result.
The simplest approach is to use dispatch_after:
- (void)restTimerComplete {
self.restTimeLabel.text = #"Rest Period Complete!";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self hideRestTimerAnimated:YES];
});
}
Adding the following code to a new project cause a flash of a black screen. It seems to be caused by the fade out animation of the splash screen. Unfortunately, the long process must be on main thread. It is possible to avoid it by delaying it but it is unreliable and lengthens the loading process.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// A spinner is shown
// This most probably will not cause a black screen
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// [self loadSomething];
// });
// This will not cause a black screen but not suitable for my use
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// [self loadSomething];
// });
dispatch_async(dispatch_get_main_queue(), ^{
[self loadSomething];
});
}
- (void)loadSomething
{
NSLog(#"start long process");
NSDate *start = [NSDate date];
while (-[start timeIntervalSinceNow] < 5) {
}
}
Consider why it must be on the main thread and come up with a way to move this processing or minimise it.
You should create a root view controller which displays the app default image and have that view controller displayed (probably with some animation or progress indication) until your processing has completed.
If you're already on your main thread, then there is no need of getting the main thread asynchronously.
dispatch_async(dispatch_get_main_queue(), ^{
});
The above code should be removed.
In case you want to do something or load something, then you should use
performSelectorInBackground
Hope this helps...
I have a View to show and hide to give users some hint.
The show and hide method look some like this:
-(void)show{
[UIView animateWithDuration:3.0f
animations:^{
//do something to show self to give hint;
self.frame = CGRectMake(0,0,0,0);
} completion:nil];
}
-(void)hide{
[UIView animateWithDuration:3.0f
animations:^{
//do something to hide self to give hint;
self.frame = CGRectMake(centerX,centerY,100,100);
} completion:nil];
}
when showing a new view, I must call hide method, and then show method. But the duration delay, 3.0f, will cause some error. I was using methods like this:
dispatch_async(dispatch_get_main_queue(), ^{
[view hide];
});
dispatch_async(dispatch_get_main_queue(), ^{
[view show];
});
I was calling show method right after hide method. Animations cannot execute as the sequence they are submitted to the queue. What I want is the show method executed exactly after the hide method completed. How can I control the order of these two methods.
I think I cannot use the completion handler cause I cannot assure where these two methods are called, or whether the view is shown when I called another show method or hide method.
If I am not clear, any suggestions? I will reedit my questions.
PS:
It's not just a flash. When next show method is called, I can not assure the last view is shown or hide and how long the last view is being shown, that is, if the view is being shown and the hide method has been called and already completed, then the show method is called, the result is right. If the view is being shown, another hint view need to be presented, I will call hide first, then show, since the main_queue is serial but the animation block is executed syncly, so the result is wrong. I am looking for is there some kind of lock in GCD that can help me execute a block after last queued block is completed rather than changing within show and hide method. cause there are many other calls to show and hide method with many different kinds of parameters, I need to fix many places in my code.
If you want to execute one task at a time in the order in which they are added to the queue, Use serial queue.
So you can use a serial queue to execute show and hide task at a time in the added order. Yeah, the main queue is ok for that.
However UIView -animateWithDuration:animations: method is kind of asynchronous call, the method returns immediately. So you need to wait until the completion block was called.
If you want to wait until some tasks were finished, Use dispatch group. But you should avoid to wait like that on the main queue. It blocks the main queue. Bad app.
Thus, you might need to use a serial queue and dispatch group as the following.
properties and initialize
#property (nonatomic, strong) dispatch_queue_t serialQueue;
#property (nonatomic, strong) dispatch_group_t group;
-(void)initQueue {
// create a serial queue
self.serialQueue = dispatch_queue_create("com.example.serialQueue", 0);
// create a dispatch group
self.group = dispatch_group_create();
}
a method that uses the serial queue and the dispatch group
-(void)animateSyncWithDuration:(NSTimeInterval)duration animations:(block_t)animations {
dispatch_async(self.serialQueue, ^{
/*
* This block is invoked on the serial queue
* This block would never be executed concurrently
*/
/*
* Enter the dispatch group
*/
dispatch_group_enter(self.group);
dispatch_async(dispatch_get_main_queue(), ^{
/*
* This block is invoked on the main queue
* It is safe to use UIKit
*/
[UIView animateWithDuration:duration animations:animations completion:^{
/*
* This completion block is invoked on the main queue
* Now leave the dispatch group
*/
dispatch_group_leave(self.group);
}];
});
/*
* Wait until leaving the dispatch group from the UIView animation completion block
* It means it blocks the serial queue
*/
dispatch_group_wait(self.group, DISPATCH_TIME_FOREVER);
});
}
show and hide
-(void)show{
[self animateSyncWithDuration:3.0f animations:^{
//do something to show self to give hint;
self.frame = CGRectMake(0,0,0,0);
}];
}
-(void)hide{
[self animateSyncWithDuration:3.0f animations:^{
//do something to hide self to give hint;
self.frame = CGRectMake(centerX,centerY,100,100);
}];
}
If what you wanted is one action (hide then show itself), you should make just one animation to do this instead of join two animations.
There are two possible solutions.
(1) use animation repeat and auto-reverse (need to reset back to original size in completion callback)
-(void) flash {
CGRect bounds = self.bounds;
[UIView animateWithDuration:1.0f
delay:0.0f
options:UIViewAnimationOptionAutoreverse |
UIViewAnimationOptionRepeat
animations:^{
[UIView setAnimationRepeatCount:1];
self.bounds = CGRectZero;
}
completion:^(BOOL finished) {
self.bounds = bounds;
}];
}
(2) use key frame animation
-(void) flash2 {
[UIView animateKeyframesWithDuration:1.0f
delay:0.0f
options:UIViewKeyframeAnimationOptionCalculationModeLinear
animations:^{
CGRect bounds = self.bounds;
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5
animations:^{ self.bounds = CGRectZero; }];
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5
animations:^{ self.bounds = bounds; }];
}
completion:nil];
}
i am using following way to put delay in function calling.
- (void) doAnimation : (double) delay {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Call your First function here");
});
double delayInSeconds = delay;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"Call your second function here");
});
}
I may not be fully understanding the use case, but what I think you should be doing here is checking whether the hide operation actually needs to occur. Also, since the hide has an animation duration of 3 seconds in your code, you should create your method with a completion block, so you can do something similar to what I've written below in pseudocode:
- (void)hideIfNeededWithCompletionBlock:((^)())completionBlock {
if (self.isShowing) {
[self hideWithCompletionBlock:^(BOOL didHide) {
if (completionBlock) {
completionBlock();
}
}];
} else {
if (completionBlock) {
//We didn't need to hide anything, so we're done
completionBlock();
}
}
}
Then you can call it like so:
[self hideIfNeededWithCompletionBlock:^(){
[self show];
}];
You can do something similar with the show method if you need that flexibility.
Also, depending on your needs, you can make your method take a BOOL for whether to animate the show/hide and if you pass NO, use a duration of 0.0.
I think its working against the UIView animations API to start wrapping it in dispatch_async blocks when you can handle it all with the API provided.
I am using tableview and I want to call a method after some time duration.This method return a array and reload the tableview.I want within this time duration UI doesn't stuck.
Best to use Grand Central Dispatch (GCD). If you call dispatch_after(), you can run a block of code whenever you want.
// Run this code after 1 second.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self someMethod];
});
I have an iOS Xcode question I'm hoping someone can help me out with. I have a simple action button that invokes a series of methods to run, however these methods all write to a stream and retrieve the input that comes in return, so I'm having a hard time reading the stream and extracting the information.
I think this is because its all happening too fast. I would like it where i press the button and method one runs, waits half a second (for example), then method two, then method three, etc... can someone show me a simple code to do so please?
Thanks in advance, example below:
Chuck
- (IBAction)updateStatsButton:(id)sender {
[self method1];
[self method2];
[self method3];
self.label1.text = result from method 1;
self.label2.text = result from method 2;
self.label3.text = result from method 3;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self method1];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self method2];
});
[self performSelector:#selector(method1) withObject:nil afterDelay:2];