I have a thread which performs some tasks.
At the end I want to run a selector to hide an image with animation.
[self performSelectorOnMainThread:#selector(finishUpdate) withObject:nil waitUntilDone:YES];
I am setting the duration to 10 seconds:
- (void) finishUpdate {
UIImageView *myImage = (UIImageView *)[self.view viewWithTag:788];
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:10.0f];
myImage.frame = CGRectMake(0, 200, 320, 80);
myImage.alpha = 0.0f;
[UIView commitAnimations];
}
But it is disappearing instantly and the thread continues immediately.
Do you know how to make my thread to wait or how to display this simple animation?
It is normal behavior for the method to return immediately. [UIView commitAnimations] does not wait for the animation to finish (or any other method you use).
Kristopher Johnson is right that you shouldn't use UIGraphicsGetCurrentContext in this context. It is used in drawRect: or whenever there is a valid graphics context.
The animation context does not relate to a graphics context. Just pass NULL if you don't need any context in some animation delegate.
Instead of waiting for the selector to finish, I used sleepForTimeInterval: method to pause my thread, using the same duration as my animation.
[NSThread sleepForTimeInterval:0.3];
Related
I have a background task to download from a webservice which runs in background and i want to suspend it if user navigates to other screens meanwhile.
Here is how i tried to download in background:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NewsResponseBO *objNewsRspnc = [objNewsParser getNewsStartingFrom:0 toEndLimit:10];
dispatch_async(dispatch_get_main_queue(), ^{
for (int i=0; i< [objNewsRspnc.arrNewsData count]; i++) {
[arrListOfNews addObject:[objNewsRspnc.arrNewsData objectAtIndex:i]];
}
isDataLoading = NO;
isBottomLoaderAdded = NO;
[loader stopAnimating];
[loader removeFromSuperview];
[bottomViewforLoader removeFromSuperview];
tbv_ListOfNews.frame = CGRectMake(tbv_ListOfNews.frame.origin.x, tbv_ListOfNews.frame.origin.y, tbv_ListOfNews.frame.size.width, tbv_ListOfNews.frame.size.height +80);
tbv_ListOfNews.contentOffset = CGPointMake(0, tbv_ListOfNews.contentSize.height);
[tbv_ListOfNews reloadData];
});
});
and Here is how i navigate on tablecell's selection:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (bottomViewforLoader != nil) {
tbv_ListOfNews.frame = CGRectMake(0, 44, 320, 416+APP_DELEGATE.floatExtraHeight) ;
[bottomViewforLoader removeFromSuperview];
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
NewsDetailViewController *obj_DetailNews = [[NewsDetailViewController alloc]initWithNibName:nil bundle:[NSBundle mainBundle]];
NSLog(#"indexPath.row:%d,arrListOfNews(number of object:%d) ",indexPath.row,[arrListOfNews count]);
obj_DetailNews.obj_newsDetail = [arrListOfNews objectAtIndex:indexPath.row];
[APP_DELEGATE.navController pushViewController:obj_DetailNews animated:YES];
}
any help on how to suspend dispatch_get_global_queue ?
Thanks in Advance.
You cannot suspend/resume the global concurrent queues. Furthermore, the code you've posted appears to execute synchronously. You will not be able to cancel it without modifying it to be be "cancelable" (i.e. -getNewsStartingFrom:toEndLimit: will need to check a variable somewhere indicating cancellation and voluntarily stop itself.) I don't know if you own NewsResponseBO or not, but just off the top of my head, this sound like a good job for NSURLConnection and a concurrent NSOperation owing to NSOperationQueue/NSOperation's support for cancellation (but note that NSOperations still don't get you hard cancellation).
See my answer to this question for more detail on cancellation of pending dispatch jobs and why hard cancellation isn't possible.
In my iOS game with cocos2d-iphone 2.0.0, I have a layer that pops up a sprite that asks the user to buy an in app purchase with a button for the user to click to buy (menuItemBuyButton). When user clicks on this buy button, three things are done:
an activity indicator is started
all menu items on the layer are disabled - particularly, the main menu (this code is on the main menu scene), the buy button itself and the popup's menu
the usual sequence of purchase calls and callbacks are triggered.
When the purchase is completed, the callback (which is on another thread) then needs to:
stop the activity indicator
reenable the disabled menu elements
replace the scene with another director
Now, when I run this sequence, and test by clicking on the buy button repeatedly, etc., just once (and unable to repro ever again) I got a crash in the code - the code and crash logs are below. I suspect (and may be wrong) this is due to the un-thread-safe nature of cocos2d. How do I avoid this crash? I do need to disable the UI elements before starting the buy transaction and have to reenable them once the transaction is finished, which will happen in another thread.
Code is as below:
-(void) startActivityIndicator {
mainMenu.enabled = NO;
scorePopupMenu.enabled = NO;
menuItemBuyButton.isEnabled = NO;
[activityIndicatorView startAnimating];
}
-(void) stopActivityIndicator {
mainMenu.enabled = YES;
scorePopupMenu.enabled = YES;//this is line 744 that crashed
menuItemBuyButton.isEnabled = YES;
if (activityIndicatorView.isAnimating)
[activityIndicatorView stopAnimating];
}
Crash logs:
5 SmartRun 0x00126c4c -[MainMenuLayer stopActivityIndicator] (MainMenuLayer.m:744)
Then disable repeated tap. Just disable touch once it is pressed and reenable once it is processed.
node.touchEnabled = NO; //do this for all menu and layer
(In Cocos2d 1.0 isTouchEnabled to NO)
We also used inAp purchase in cocos2d game with activity indicator from UIKit. What we did is used activity indicator inside Alert view. That blocks user to do other UI switch.
-(void)ShowActivityIndicator
{
if(!mLoadingView) // UIAlertView *mLoadingView;
{
mLoadingView = [[UIAlertView alloc] initWithTitle:#"" message:#"" delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
UIActivityIndicatorView *actInd = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
actInd.frame = CGRectMake(128.0f, 45.0f, 25.0f, 25.0f);
[mLoadingView addSubview:actInd];
[actInd startAnimating];
[actInd release];
UILabel *l = [[UILabel alloc]init];
l.frame = CGRectMake(100, -25, 210, 100);
l.text = #"Please wait...";
l.font = [UIFont fontWithName:#"Helvetica" size:16];
l.textColor = [UIColor whiteColor];
l.shadowColor = [UIColor blackColor];
l.shadowOffset = CGSizeMake(1.0, 1.0);
l.backgroundColor = [UIColor clearColor];
[mLoadingView addSubview:l];
[l release];
}
[mLoadingView show];
}
-(void)StopIndicator
{
[mLoadingView dismissWithClickedButtonIndex:0 animated:NO];
}
I have a tableView, and a "Reload" button that fetches data from a URL and reloads the table, a costly operation (in particular the fetch part).
I want display info to the user while this is happening, so I thought I'd put up a "Loading" UITextView on top, do the fetch/reload operation, then remove the UITextView.
Here's the code:
- (IBAction)refreshData:(id)sender {
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height-30, self.view.frame.size.width, 30)];
[self.view addSubview:textView];
textView.text = #"Loading..";
textView.textColor = [UIColor whiteColor];
textView.backgroundColor = [UIColor grayColor];
textView.editable = NO;
[self fetchData];
[self.tableView reloadData];
[textView removeFromSuperview];
}
The behavior I get is that the fetchData executes before the UITextView renders on screen. I know this because I have NSLogs inside fetchData that execute before the UITextView shows up on screen.
Is there something obvious I'm missing here? Thanks.
Replace [self fetchData]; with
[self performSelectorInBackground:#selector(fetchData) withObject:nil];
Then inside your fetchData method, after your fetch is done, add
[self performSelectorOnMainThread:#selector(fetchDataFinished) withObject:nil waitUntilDone:NO];
where the new method fetchDataFinished is defined as
-(void) fetchDataFinished{
[self.tableView reloadData];
[self.textView removeFromSuperview];
}
Note that you have to make textView a property so it is accessible in this method.
I have not tried out the above for your particular case, but I have used similar constructs successfully in many similar situations.
Grand Central Dispatch is great and reduces the amount of code but why I cannot run something on a background thread?
I have made a sample application to show what I mean (none of the commented work):
- (IBAction)performSel {
[self performSelectorInBackground:#selector(doStuff) withObject:nil];
[NSThread sleepForTimeInterval:3];
[[self.view.subviews lastObject] removeFromSuperview];
}
- (IBAction)gcd {
dispatch_async(dispatch_queue_create("myGCDqueue", NULL), ^(void) {
//dispatch_sync(dispatch_queue_create("myGCDqueue", NULL), ^(void) {
//dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
//dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
//dispatch_async(dispatch_get_main_queue(), ^(void) {
//dispatch_sync(dispatch_get_main_queue(), ^(void) {
[self doStuff]; // only for readability, will move the code on success
});
[NSThread sleepForTimeInterval:3];
[[self.view.subviews lastObject] removeFromSuperview];
}
- (void)doStuff {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
UIView *abortingView = [[UIView alloc]initWithFrame: self.view.bounds];
abortingView.backgroundColor = [UIColor whiteColor];
abortingView.alpha = 0.7;
[self.view insertSubview:abortingView atIndex:10];
[abortingView release];
[pool drain];
}
the [NSThread sleepForTimeInterval:3]; is to simulate a default UI functionality. For example if someone is switching from one navigation view to another.
Simply copy the code in a new view based application, create two buttons and connect them.
UIKit classes should be used only from an application’s main thread. (From iOS4, drawing to a graphics context is thread-safe.) You can't use UIKit stuff in a background thread.
Thus, you can only use dispatch_async(dispatch_get_main_queue(), block) in this situation.
dispatch_async(dispatch_get_main_queue(), ^(void) {
It will invoke the block on the main thread in the runloop of the main thread.
dispatch_async(dispatch_queue_create("myGCDqueue", NULL), ^(void) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
It will invoke the block in a background thread. You can't use it because you want to use UIKit in the block. And be careful dispatch_async(dispatch_queue_create(, it might cause memory leak, you have to release the serial queue that is created by dispatch_queue_create.
dispatch_sync(dispatch_queue_create("myGCDqueue", NULL), ^(void) {
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
dispatch_sync waits until the block is done.
dispatch_sync(dispatch_get_main_queue(), ^(void) {
It causes DEADLOCK.
I simply can't get this to work, I would think it was simple, but no luck.
- (void) animate {
UIView *viewA = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 300.0f, 100.0f)];
[viewA setBackgroundColor:[UIColor blueColor]];
UIView *viewB = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 300.0f, 100.0f)];
[viewB setBackgroundColor:[UIColor brownColor]];
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(10.0f, 10.0f, 300.0f, 100.0f)];
[container addSubview:viewA];
[self.view addSubview:container];
[UIView transitionWithView:container
duration:0.4
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[[[container subviews] objectAtIndex:0] removeFromSuperview];
[container addSubview:viewB];
}
completion:^(BOOL finished){
}];
}
This is how the documentation recommends you do it, make a container, add/remove the two subviews you want to flip between.
I can't get it to work. It will just display viewA, then viewB, as if the animation part is skipped, but the block is carried out?
If I switch the container in the [UIView transitionWithView:container with self.view it flips the entire view (As suspected) but I can not get it to do this with 2 subviews of self.view.
Is there no way around this?
I am looking to do something like the iPad Photos app, where a picture will flip and scale to full screen.
I really hope someone could help me out here, thank you in advance.
You can't put semicolons inside the block. If you want two things done in there, divide them with a ",".
Also CurveEase would probably be nice., if you want it.
This should work.
[UIView transitionWithView:container
duration:0.4
options:UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionCurveEaseInOut
animations:^{
[[[container subviews] objectAtIndex:0] removeFromSuperview],
[container addSubview:viewB];
}
completion:^(BOOL finished){
}];
If it does not, consider making the views instance variables and make sure they're allocated before you try to animate them. If you want to animate on a button push, you can do it directly with sender too, if the whole view is a UIButton.
I hope it helps.