I'm trying to load views asynchronously. The problem is that the frame of the views to be loaded depends on the data that's loaded asynchronously. In other words there are some long calculations that decide where to actually display the UIViews.
I know that there are problems when trying to actually display a UIView in a thread and that you should always load them back in the main thread, so this is the code I've been trying out:
asyncQueue = [[NSOperationQueue alloc] init];
[asyncQueue addOperationWithBlock:^{
// Do work to load the UIViews and figure out where they should be
UIButton *test = [[UIButton alloc] initWithFrame:[self doWorkToGetFrame]];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self addSubview:test];
}
}];
}];
This all resides in a UIView container.
Try something like this:
UIButton *test = [[UIButton alloc] initWithFrame:CGRectZero];
test.hidden = YES;
test.alpha = 0.0f;
[self addSubview:test];
dispatch_queue_t downloadQueue = dispatch_queue_create("myDownloadQueue",NULL);
dispatch_async(downloadQueue, ^
{
// do work to load UIViews and calculate frame
CGRect frameButton = ...;
dispatch_async(dispatch_get_main_queue(), ^
{
test.hidden = NO;
[UIView animateWithDuration:0.4f animations:^
{
test.frame = frameButton;
test.alpha = 1.0f;
}
completion:^(BOOL finished){}];
});
});
dispatch_release(downloadQueue);
This adds your button in the main thread, but makes it invisible initially. After your background work is done, you'll make it visible and set the frame using an animation.
Related
I have an issue which is not a big deal but driving me crazy.
I have a view which subclasses UITableView.
Pressing on one of the cells leader to an lengthy HTTP requests, which I run on another thread using this code in didSelectRowAtIndexPath:
case SECTION_SEND:
[self addActivityIndicatorToView];
[self performSelectorInBackground:#selector(sendEmail) withObject:nil];
break;
The activityIndicator is behaving correctly. Displaying on the screen and then removing itself when the operation ends. But if the app goes to the background or another tab, the activity indicator disappears, even though the task did not finish.
Here are the methods I am using:
Then:
-(UIActivityIndicatorView*)createActivityIndicator {
//Setting up activity indicator
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
spinner.color = [UIColor whiteColor];
spinner.backgroundColor = [UIColor blackColor];
spinner.alpha = 0.8;
CGFloat width = 120;
spinner.frame = CGRectMake(self.tableView.bounds.size.width/2-width/2, self.tableView.bounds.size.height/2-width/2, width, width);
//spinner.center = CGPointMake( [UIScreen mainScreen].bounds.size.width/2,[UIScreen mainScreen].bounds.size.height/2);
spinner.layer.cornerRadius = 20;
spinner.layer.masksToBounds = YES;
return spinner;
}
-(void)addActivityIndicatorToView {
//[[[[UIApplication sharedApplication] delegate] window] addSubview:ai];
_ai = [self createActivityIndicator];
[self.tableView.superview addSubview:_ai];
[_ai startAnimating];
}
-(void) removeActivityIndicatorFromView {
[_ai stopAnimating];
[_ai removeFromSuperview];
}
I am calling [self removeActivityIndicatorFromView] when the operation ends.
As I said, it behaves properly unless I leave the screen. I have check my viewdidload and viewwilldisappear viewwillappear and init methods and I have nothing overriding _ai.
Any ideas?
I'm trying to get a UIActivityIndicatorView to show on screen while my CSV import method is running, but I can't get it right. With the code below, the ActivityIndicator subview shows for a second or so then disappears, even if the import operation is still running. How can I make it stay on screen until the NSOperationQueue is finished? I'm using iOS 7.1 on my test device.
User taps 'Yes' on an UIAlertView to import the data:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if(buttonIndex == 0){
//clicked Yes
[self loadingSpinner];
operationQueue = [NSOperationQueue new];
NSInvocationOperation *importOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(importCSVData:) object:self.importURL];
[operationQueue addOperation:importOperation];
}
else if(buttonIndex == 1){
//clicked No
}
}
Method to show a UIActivityIndicatorView on top of everything else on screen:
-(void)loadingSpinner{
self.overlayView = [[UIView alloc] init];
self.overlayView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
UIView *topView = [UIApplication sharedApplication].keyWindow.rootViewController.view;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
self.overlayView.frame = [UIScreen mainScreen].bounds;
}
else{
self.overlayView.frame = topView.frame;
}
[self.overlayView setUserInteractionEnabled:NO];
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.spinner.center = self.overlayView.center;
[self.overlayView addSubview:self.spinner];
[self.spinner startAnimating];
[topView addSubview:self.overlayView];
}
At the end of the import operation to remove the activity indicator:
[self.overlayView removeFromSuperview];
Use addOperationWithBlock: method on NSOperationQueue with which you will have more control. You would edit your code like this to use block version,
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperationWithBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self loadingSpinner];
}];
[self importCSVData:self.importUrl];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.overlay removeFromSuperView];
}];
}];
You can also use GCD to implement this task.
Create dispatch queue:
dispatch_queue_t queue = dispatch_queue_create("Other Q", NULL);
or you can use the main queue like:
dispatch_get_main_queue()
Finally replace your code with the following:
dispatch_async(queue, ^{
dispatch_sync(dispatch_get_main_queue(),^{
[self loadingSpinner];
});
[self importCSVData:self.importURL];
});
You might want to check for the title of you button clicked on your UIAlertView.
To do so change this:
if(buttonIndex == 1)
With this:
if([[alertView buttonTitleAtIndex:buttonIndex]isEqual:#"YES"])
Let me know if this helps.
I have a UIScrollView that has a bunch of items in it, similar to a facebook feed. In that feed I have a bunch of images that are being loaded from the internet. When the images are loaded they fade in. The problem is that if I am scrolling the scroll view, then the image animation to fade in doesn't occur until my finger is lifted off the scrollview. Is there any way to update the subviews of the UIScrollView view while scrolling?
-(void)loadStuff:(DipticView*)diptic{
//DipticView is a UIView subclass that holds the imageview and some other information
ConnectionManager *conman = [[ConnectionManager alloc] init];
WebImage *webimage = [diptic.item.images firstObject]; //webimage holds the imageurl and the image
[conman getImageWithURL:webimage.image_URL inBackgroundWithBlock:^(NSString *error, id image) {
webimage.image = image;
diptic.imageView.image = image;
diptic.imageView.alpha = 0;
diptic.userInteractionEnabled = NO;
[UIView animateWithDuration:.4 animations:^{
diptic.imageView.alpha = 1;
} completion:^(BOOL finished) {
diptic.userInteractionEnabled = YES;
}];
//The diptics are all subviews of the scrollview.
}];
}
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:.4 animations:^{
diptic.imageView.alpha = 1;
} completion:^(BOOL finished) {
diptic.userInteractionEnabled = YES;
}];
});
But I'm not sure that this method can work well with animateWithDuration.
This has been asked before but none of the answers are straightforward enough to help little old me.
What exactly does .isAnimating in an UIIimage view do? And how to properly use it?
I am emulating the tap to focus animation in the iPhone camera (yellowish square that pops up and then shrinks when you tap on the preview). That works fine, but I want it to not happen multiple times if it is already animating. This code does the animation but multiple taps gives multiple animationed squares. I suspect it is not just .isAnimating but I'm also doing something else wrong because I tried it with my own boolean too and that fails.
-(void)focusSquarePopUp:(CGPoint)touchPoint;
{
UIImage *focusSquareImage = [UIImage imageNamed:#"yellowFocusSquare"];
UIImageView *tmpView = [[UIImageView alloc] initWithImage:focusSquareImage];
if (!(tmpView.isAnimating))
{
tmpView.center = touchPoint;
tmpView.opaque = YES;
tmpView.alpha = 1.0f;
[self.view addSubview:tmpView];
tmpView.hidden = NO;
// shrink to half size in .3 second
[UIView animateWithDuration:0.3 delay:0 options:0 animations:^{
tmpView.transform = CGAffineTransformMakeScale(.5, .5);
NSLog(#"in animation isAnimating %hhd", tmpView.isAnimating);
} completion:^(BOOL finished) {
// Once the animation is completed hide the view for good
tmpView.hidden = YES;
}];
}
NSLog(#"done animating isAnimating %hhd", tmpView.isAnimating);
[tmpView release];
}
If there is question where this has a solid answer, that would be great.
EDIT - here is the working code.
-(void)focusSquarePopUp:(CGPoint)touchPoint;
{
if (animationInProgress)
return;
animationInProgress = true;
UIImageView *tmpView = [[UIImageView alloc] initWithImage:sp_ui->focus_square];
tmpView.center = touchPoint;
tmpView.opaque = YES;
tmpView.alpha = 1.0f;
[self.view addSubview:tmpView];
tmpView.hidden = NO;
// shrink to half size in .3 second
[UIView animateWithDuration:0.3 delay:0 options:0 animations:^{
tmpView.transform = CGAffineTransformMakeScale(.5, .5);
} completion:^(BOOL finished) {
// Once the animation is completed hide the view for good
tmpView.hidden = YES;
animationInProgress = false;
}];
[tmpView release];
}
I suspect on your code. On your tapping on method focusSquarePopUp: you create new instances of tmpView and focusSquareImage and it adding on self view. That why you found number of animated square equal to number of tap. When you create new instance of those variable then sure isAnimating is firstly you got NO value and it enter animation block code.
Why not you create instance of tmpView and focusSquareImage in .h file of class?? In fact its problem of variable declarations and scope of variables.
Your code should be like this,
Declare instance in animation class (i.e. self.view) .h file
UIImage *focusSquareImage;
UIImageView *tmpView;
Now in .m file viewDidLoad method,
in viewDidLoad
focusSquareImage = [UIImage imageNamed:#"yellowFocusSquare"];
tmpView = [[UIImageView alloc] initWithImage:focusSquareImage];
Your animation method implementation should be,
-(void)focusSquarePopUp:(CGPoint)touchPoint;
{
if (!(tmpView.isAnimating))
{
tmpView.center = touchPoint;
tmpView.opaque = YES;
tmpView.alpha = 1.0f;
[self.view addSubview:tmpView];
tmpView.hidden = NO;
// shrink to half size in .3 second
[UIView animateWithDuration:0.3 delay:0 options:0 animations:^{
tmpView.transform = CGAffineTransformMakeScale(.5, .5);
NSLog(#"in animation isAnimating %hhd", tmpView.isAnimating);
} completion:^(BOOL finished) {
// Once the animation is completed hide the view for good
tmpView.hidden = YES;
[tmpView release];
}];
}
}
Another option of method implementation,
-(void)focusSquarePopUp:(CGPoint)touchPoint;
{
if(!tmpView){
focusSquareImage = [UIImage imageNamed:#"yellowFocusSquare"];
tmpView = [[UIImageView alloc] initWithImage:focusSquareImage];
if (!(tmpView.isAnimating))
{
tmpView.center = touchPoint;
tmpView.opaque = YES;
tmpView.alpha = 1.0f;
[self.view addSubview:tmpView];
tmpView.hidden = NO;
// shrink to half size in .3 second
[UIView animateWithDuration:0.3 delay:0 options:0 animations:^{
tmpView.transform = CGAffineTransformMakeScale(.5, .5);
NSLog(#"in animation isAnimating %hhd", tmpView.isAnimating);
} completion:^(BOOL finished) {
// Once the animation is completed hide the view for good
tmpView.hidden = YES;
[tmpView release];
tmpView = nil;
}];
}
}
}
isAnimating is UIImageView property and it has nothing to do with UIView animation methods
isAnimating is used when you want a UIImageView to alternate between multiple images. For example in the following code the UIImageView display 3 images and alternate between them
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
UIImage *image1 = [UIImage imageNamed:#"image1.png"];
UIImage *image2 = [UIImage imageNamed:#"image2.png"];
UIImage *image3 = [UIImage imageNamed:#"image3.png"];
imageView.animationImages = #[image1, image2, image3];
imageView.animationDuration = 1;
NSLog(#"isAnimating %d",imageView.isAnimating); // isAnimating 0
[imageView startAnimating];
NSLog(#"isAnimating %d",imageView.isAnimating); // isAnimating 1
[self.view addSubview:imageView];
Your case is completely different you are using UIView animation blocks and to know if the animation in its block is finished or not you will need to add a Boolean flag instance variable animationFinished and set it to YES in the completion block of the animation
I'm pretty new on iOs programming. And I'm stuck at a (i'm sure) very simple issue.
Don't know what i'm doing wrong...
-My viewDidLoad:
[super viewDidLoad];
CGRect frame = CGRectMake(0, 0, 768, 1024);
UIView *uno=[[[UIView alloc] initWithFrame:frame] autorelease];
UIImageView *mainView = [[[UIImageView alloc] initWithFrame:frame] autorelease];
mainView.image = [UIImage imageNamed:#"photo.jpg"];
[uno addSubview:mainView];
UIView *dos=[[[UIView alloc] initWithFrame:frame] autorelease];
UIImageView *mainViewDos = [[[UIImageView alloc] initWithFrame:frame] autorelease];
mainViewDos.image = [UIImage imageNamed:#"Default.png"];
[dos addSubview:mainViewDos];
//
[self.view addSubview:uno];
//
[self anima:uno:dos];
And my anima method:
-(void) anima:(UIView *)uno:(UIView *)dos{
[UIView transitionFromView:uno
toView:dos
duration:2.0
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:nil];
}
It changes the view but without transition...
Thanks
You can't perform an animation within your viewDidLoad--the view changes you make in there are all executed before the view is actually displayed, which is what you're seeing.
Are you trying to show this animation when the view is first displayed? If so, you can get it to work by putting the animation on a timer. Note that with this approach, you'll also have to refactor your anima method a bit to take a single argument.
In your viewDidLoad:
NSDictionary *views = [NSDictionary dictionaryWithObjectsAndKeys:uno, #"uno", dos, #"dos", nil];
[self performSelector:#selector(anima) withObject:views afterDelay:0.1];
Then change your anima method to:
-(void) anima:(NSDictionary *)views {
[UIView transitionFromView:[views objectForKey:#"uno"]
toView:[views objectForKey:#"dos"]
duration:2.0
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:nil];
}