Faster way to take snapshots ios - ios

I have a UICollectionView. I want to generate two snapshots for each objects.
Sample Code:
for (int index = 0; index < 2; index++) {
UIView *dummyView = [[UIView alloc] init];
dummyView.frame = some frame....,
dummyView.backgroundColor = [UIColor yellowColor];
UIGraphicsBeginImageContextWithOptions(dummyView.bounds.size, NO, [UIScreen mainScreen].scale);
[dummyView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSData *imgData= UIImageJPEGRepresentation(outputImage, 1.00f);
//Write file.
}
I have written the code in cellForItemAtIndexPath. If there is no image in the path, it will create automatically. So when I launch the application for the first time, the UI is not responsive since there are many snapshots are getting created. This slow down the user interaction. Is there any work around for this?

Use drawViewHierarchyInRect instead of drawInContext as it's much faster
See the iOS 7 example here: https://coderwall.com/p/56wcfq/fast-snapshot-any-views-on-ios-7

Related

NSMutableArray creates issue for images

I have an array with dictionaries in it. Each dictionary contains UIImage & String values.
But when I try to delete object using
[arr removeObjectAtIndex:button.tag];
then it just decreases the count but image (Object) is not deleted.
So, my Images are overlapping when try to delete.
It also creates problem when I try to add objects in the array
[arr insertObject:dict atIndex:0];
so, I used
[_postObj.arr_images addObject:dict]; instead of insertObject
Help me to solve this
Objects use reference counting and both NSMutableArray and UIImageView will retain the UIImage object.
This means that removing it from the array will not automatically remove it from the UIImageView and you must remove it from both explicitly:
[arr removeObjectAtIndex:button.tag];
_imageView.image = nil;
Problem is in your frame setting of UIImageView. Please try using below code (not tested by myself) and see if this fixes the issue for you. Basically, I am adjusting X position of images based on previous image's width.
CGFloat imageXM = 5.0;
CGFloat imageXPos = 0;
for (UIImage *image in arr_images) {
CGRect imageFrame = CGRectMake(imageXPos + imageXM, 0.0, image.size.width, image.size.height);
imageXPos = imageXPos + image.size.width;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:imageFrame];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = image;
[scl addSubview:imageView];
}
As in your code
[picker dismissViewControllerAnimated:YES completion:^{
[arr_images insertObject:chosenImage atIndex:0];
[self scrollImages_Setup2];
}];
[arr_images insertObject:chosenImage atIndex:0]; that means you need only one image to display on scroll view. Then why you take a loop:
-(void)scrollImages_Setup2
{
for (int k=0; k<arr_images.count; k++) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake((CGRectGetWidth(scl.frame) * k) + CGRectGetWidth(scl.frame), 0, CGRectGetWidth(scl.frame), CGRectGetHeight(scl.frame))];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image=[arr_images objectAtIndex:k] ;
[scl addSubview:imageView];
}
scl.contentSize = CGSizeMake((CGRectGetWidth(scl.frame) * arr_images.count)+CGRectGetWidth(scl.frame), CGRectGetHeight(scl.frame));
}
You just right the code only for display the Image. you don't need to scroll view for displaying only one image. I think you are not clear what you want.
Your problem regarding the overwriting the images because of loop. so remove the loop, when you display a single image. Please remove the scrollview contentSize, because image_array will increase, when you add the multiple images in array and size width will be large. so remove arr_images.count as well.

CAEmitter any way to insert self-animated particles?

I have a CAEmitterCell :
CAEmitterCell * spriteCell = [CAEmitterCell emitterCell];
spriteCell.contents = (id) [[UIImage imageNamed:#"emitterSprite.png"] CGImage];
I would like the sprite to be an animated png sequence (or so) instead of a single image.
The only technique I know makes use of an UIImageView :
NSMutableArray *animatedImagesArray = [NSMutableArray new];
imageView = [[UIImageView alloc] initWithFrame:CGRectMake(...)];
for (int i = 1; i <= 10; i++)
{[animatedImagesArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"sequenceNo-%d%#", i, #".png"]]];}
imageView.animationImages = animatedImagesArray;
[view addSubview:imageView];
CAEmitterCell.contents doesn't seem to accept UIImageView. Is there a way to achieve this ?
For reasons of performance in the operating system what you are trying to do is not possible. If you have simple transformations like rotation, scale, colour tint you can edit those values in the properties of your the CAEmitterCell. If you require a more complex animation you will need to use a custom emitter that can handle UIImageViews. But be mindful of the performance implications.

How to prevent the flash when calling drawViewHierarchyInRect:afterScreenUpdates: on iOS 8?

Under iOS 8 a call to UIView's
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
results in a 1 frame flash of the image. You can see it for a split second on the screen. It only happens when afterScreenUpdates parameter is YES.
This is my complete code to take screenshot:
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, sf);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
CGContextConcatCTM(ctx, [self.layer affineTransform]);
if ([self respondsToSelector:#selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { // iOS 7+
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
} else { // iOS 6
[self.layer renderInContext:ctx];
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
CGContextRestoreGState(ctx);
UIGraphicsEndImageContext();
Is there a workaround?
Provide #3x launch images to be used in iPhone6 and iPhone6 plus and this problem goes away.
Here's a possible workaround that solved my issue.
Use CATransaction.completionBlock to call UIView drawViewHierarchyInRect:afterScreenUpdates with the screen updates flag set to NO. I used this to workaround the same problem. It is semantically equivalent to setting the flag to YES in my case since I'm only invoking it after the current CATransaction has finished and the screen is in the desired state. That said, in my case, I don't have any animations, so this is pretty much immediate and captures exactly what I want. If there were animations, this may not be appropriate.
For my case, I have a collection view that is a collection of files. Each item is rendered as a snapshot of what the file looks like once the user opens it. Some files cause the UI to have hundreds of layers. When these complicated views were being snapshotted, I would get a black screen flash; or the last/current animation to partially rerun. My collection view is w/in a navigation controller, I was seeing partial reruns of the pop animation. I also have noticed reruns of rotation animations. For simpler files, there was often no issue. I noticed this on my iPad Air (iOs 8.1) and on various simulators. I tried putting appIcons and launchImages in place but they had no effect.
Here was the original code. It initializes a view controller, then adds the controller's view to the a utility view.
UIViewController *vlController = [[ComplicatedViewController alloc]init];
UIView *innerView = vlController.view;
[v addSubview:innerView];
innerView.transform = CGAffineTransformMakeScale(scalar, scalar);
innerView.center = CGRectCenterPoint(v.bounds);
auto sz = v.bounds.size;
innerView.bounds = {{0,0}, {sz.width/scalar, sz.height/scalar}};
UIGraphicsBeginImageContextWithOptions(v.bounds.size, YES, 0);
[v drawViewHierarchyInRect:v.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[innerView removeFromSuperview];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[v addSubview:imageView];
The new code just moves the bottom half to the completion block of UIView animateWithDuration:completion. Notice that the duration of the UIView animation is 0, but that still seems to give that extra refresh cycle to update the screen with the new UI.
UIViewController *vlController = [[ComplicatedViewController alloc]init];
UIView *innerView = vlController.view;
[UIView animateWithDuration:0.0 animations:^{
[v addSubview:innerView];
innerView.transform = CGAffineTransformMakeScale(scalar, scalar);
innerView.center = CGRectCenterPoint(v.bounds);
auto sz = v.bounds.size;
innerView.bounds = {{0,0}, {sz.width/scalar, sz.height/scalar}};
} completion:^(BOOL finished) {
UIGraphicsBeginImageContextWithOptions(v.bounds.size, YES, 0);
BOOL drawResult = [v drawViewHierarchyInRect:v.bounds afterScreenUpdates:NO];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[innerView removeFromSuperview];
[self add:image forFile:v.filename withOrientation:v.orientation];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[v addSubview:imageView];
}];

Delay loading images in UIImageView

I'm trying to loop through some images in a single UIImageView when I tap a button. The image must disappear 0.1 seconds after the button is pressed.
Here's the code:
int tapCount = 0;
UIImage *image0 = [UIImage imageNamed:#"0.jpg"];
UIImage *image1 = [UIImage imageNamed:#"1.jpg"];
UIImage *image2 = [UIImage imageNamed:#"2.jpg"];
imagesArray = [[NSArray alloc] initWithObjects:image0, image1, image2, nil];
-(IBAction)backgroundButton:(id)sender{
self.myImageView.image = [imagesArray objectAtIndex:tapCount%3];
tapCount++;
[self performSelector:#selector(eraseImage) withObject:self afterDelay:0.1];
}
-(void)eraseImage{
self.myImageView.image = nil;
}
The problem is that the images don't appear until I've completed one entire loop (at the 4th tap).
I'm guessing that somehow I must initialize the images in the UIImageView because it takes some time between the tapping and the image appearing, and since it disappears after 0.1 seconds...it doesn't show at all.
I've tried loading them inside viewDidLoad like this:
for(int i = 0; i<[imagesArray count]; i++){
self.myImageView.image = [imagesArray objectAtIndex : i];
}
But it only works with the last image that loads (image2 in this case).
Should I loop between different UIImageView instead of looping through different UIImage inside a single UIImageView?
Any other hints?
Creating a UIImage doesn't actually load the image data (you need to render it to a context for that to happen). So, if your images are large then you could be hiding them before they are actually rendered to the screen. You won't be able to hold many images in memory at the same time, but you can force the image data to be loaded by creating a context and drawing the image into it (which can be done in the background, using CGContextDrawImage).
There are a few 3rd party bits of code which do this, like this or check this discussion.
Use the animationImages and animationDuration property of the UIImageView
https://developer.apple.com/library/ios/documentation/uikit/reference/UIImageView_Class/Reference/Reference.html#//apple_ref/occ/instp/UIImageView/animationImages
I think there is a much simpler way to achieve that animation you are going for. Try the following code:
-(IBAction)backgroundButton:(id)sender{
[UIView animateWithDuration:0.2
delay:nil
options:UIViewAnimationCurveEaseIn
animations:^{
self.myImageView.image = [imagesArray objectAtIndex:tapCount%3];
self.myImageView.image = nil;
}
completion:nil
];
tapCount++;
if (tapCount == 2) {
tapCount = 0;
}
}
I finally managed to work this around using this solution:
First I preload all the images in the background thread
-(void)preload:(UIImage *)image{
CGImageRef ref = image.CGImage;
size_t width = CGImageGetWidth(ref);
size_t height = CGImageGetHeight(ref);
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, width * 4, space, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(space);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), ref);
CGContextRelease(context);
}
Then I execute the same action I had in the beginning:
-(IBAction)backgroundButton:(id)sender{
self.myImageView.image = [imagesArray objectAtIndex:tapCount%3];
tapCount++;
[self performSelector:#selector(eraseImage) withObject:self afterDelay:0.1];
}
-(void)eraseImage{
self.myImageView.image = nil;
}

UIImageView's start animation does not animate flipped images

I have a set of images facing one side(Man facing right side), and I'm flipping it using this method and then adding it into a uiimageview's animationImages. However after adding in the flipped images, and then start to animate, the animation is still facing the right side.
manRunAnimations = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)];
NSArray *manAniRunArray = #[[UIImage imageNamed:#"robot1"],
[UIImage imageNamed:#"robot2"],
[UIImage imageNamed:#"robot3"],
[UIImage imageNamed:#"robot4"],
[UIImage imageNamed:#"robot5"],
[UIImage imageNamed:#"robot6"],
];
manRunAnimationsf = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)];
NSMutableArray *flippedRunAnimationImages = [[NSMutableArray alloc] init];
for( UIImage *image in manAniRunArray)
{
UIImage * flippedImage = [UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:UIImageOrientationRightMirrored];
[flippedRunAnimationImages addObject:flippedImage];
}
manRunAnimationsf.animationImages = (NSArray *)flippedRunAnimationImages;
testImgView.image = [manRunAnimationsf.animationImages objectAtIndex:0];
manRunAnimationsf.animationDuration = runAnimationSpeedSlider.value;
[manRunAnimationsf startAnimating];
I've even tested it using
testImgView.image = [manRunAnimationsf.animationImages objectAtIndex:5];
This would display one of the flipped images properly on screen just before I do a
[manRunAnimationsf startAnimating];
Once it starts, the animations are not flipped at all!!
Anyone know why?
I can't believe no one knows why, but I found way around it. Is to add the following before I startAnimating:
manRunAnimationsf.transform = CGAffineTransformMake(-1,0,0,1,0,0);
I didn't even need to do the CGImage flips in that for loop.
But the manual image flip should have worked with the imageWithCGImage method, and I really want to know why!! You guys disappoint me. :p

Resources