UIScrollView loading images -makes scroller be slowly - objective-c

I have a scroll view with many UIViews, and these views have UIImageViews-that i load images into, while scrolling.
So every page has 4 of these views,that are preloaded to scroller at start, and i load the next page images while scrolling.
I do that in another thread , and still, scroller not entirely scrolls fast, if you look good it has some tiny flicks .
here is the function that loads them :
-(void)loadMainImageToIndex:(int)index
{
NSDictionary *dic=[mainData objectAtIndex:index];
NSString *userImageUrl=[dic objectForKey:#"url"];
NSURL *userUrl=[NSURL URLWithString:userImageUrl];
[self downloadImageWithURL:userUrl completionBlock:^(BOOL succeeded, NSData *tdata)
{
if (succeeded)
{
//load back
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^
{
UIImage *image=[UIImage imageWithData:tdata scale:0.25];
if (image)
{
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
[image drawAtPoint:CGPointZero];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
dispatch_async(dispatch_get_main_queue(), ^
{
UIImageView *view=[mainImagesArray objectAtIndex:index];
});
}
}];
The url is loaded Asynch.

Turned out that the views shadow cause this thing :
//self.layer.shadowOffset=CGSizeMake(6.0, 6.0);
//self.layer.shadowColor=[UIColor blackColor].CGColor;
//self.layer.shadowRadius = 3;
//self.layer.shadowOpacity = 0.7;
Removing this solves the problem.
self.layer.shouldRasterize = YES; //solve the problem,

Related

App crashs in drawViewHierarchyInRect

My app is used large images (5MB) to markup. But app is getting crashed without any error messages.
Below is the code I used
UIGraphicsBeginImageContext(CGSizeMake(_drawingUV.drawingImgView.image.size.width, _drawingUV.drawingImgView.image.size.height));
{
CGContextRef context = UIGraphicsGetCurrentContext();
if ([_drawingUV.drawingImgView respondsToSelector:#selector(drawViewHierarchyInRect:afterScreenUpdates:)])
{
[_drawingUV.drawingImgView drawViewHierarchyInRect:_drawingUV.drawingImgView.bounds afterScreenUpdates:YES];
}
else
{
[_drawingUV.drawingImgView.layer renderInContext:context];
}
screenShot = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
I have tried all possibilities to find a solution last two days. But could not.
This function is used below UI objects.
main view has view and inside that it has UIScrollView and inside that UIScrollView has UIImageView. So I need to get screenshoto this UIImageView. I use these views to scroll and markup the the image view.
Any idea for this crash?
I use this all the time without issue:
- (UIImage *)screenshot {
UIWindow *mainWindow = [[UIApplication sharedApplication] keyWindow];
UIGraphicsBeginImageContextWithOptions(mainWindow.bounds.size, NO, 1.0);
[mainWindow drawViewHierarchyInRect:mainWindow.bounds afterScreenUpdates:NO];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}

App Crashes when too many images are in TableView

I have a table that I am populating with information stored in Runner Objects. Each Runner object has a property called fileName that corresponds to an image saved in the document directory.
If I start with an empty table, I can add runners and their photos just fine until I reach about 8 runners. When I try to add a photo for the 8th or so runner, the app crashes. It seems to be a memory issue, but I don't see how this can be when all I'm storing locally are references to images in the directory.
I pass a runner object to my set up cell method in my RunnerCell class and set up the cell like so:
if (currentRunner.fileName != nil) {
if ([manager fileExistsAtPath:fullPath]){
UIImage *originalImage = [[UIImage alloc] initWithContentsOfFile:fullPath];
if ([currentRunner.photoOrientation isEqual: #"portrait"]) {
CGImageRef imageRef = [originalImage CGImage];
UIImage *rotatedImage = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:UIImageOrientationRight];
self.runnerImageView.image = rotatedImage;
} else {
self.runnerImageView.image = originalImage;
}
self.addPhotoLabel.hidden = true;
}
}
As per documentation states that you mustn't load image to a UITableViewCell on main thread. That is why your app is crashing.
Because when you load image on main thread it became too much laggy when you scroll because dequereusable cell reassign cell and again load image for upcoming indexpath's cell and until the image loads it block the main thread which cause laggy behaviour of tableview.
Please load your cached image in a background thread. use this code.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
if ([currentRunner.photoOrientation isEqual: #"portrait"]) {
CGImageRef imageRef = [originalImage CGImage];
UIImage *rotatedImage = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:UIImageOrientationRight];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
self.runnerImageView.image = rotatedImage;
});
} else {
dispatch_sync(dispatch_get_main_queue(), ^(void) {
self.runnerImageView.image = originalImage;
});
}
});

Animation using array of images in sequence

I have an array of images which I want to animate by playing these images one after the other in a sequence. I want to repeat the whole loop several times. I am developing a game for iPad. Suggest to me a method to achieve this functionality in Objective-C with the Cocoa framework.
NSArray *animationArray = [NSArray arrayWithObjects:
[UIImage imageNamed:#"images.jpg"],
[UIImage imageNamed:#"images1.jpg"],
[UIImage imageNamed:#"images5.jpg"],
[UIImage imageNamed:#"index3.jpg"],
nil];
UIImageView *animationView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0,320, 460)];
animationView.backgroundColor = [UIColor purpleColor];
animationView.animationImages = animationArray;
animationView.animationDuration = 1.5;
animationView.animationRepeatCount = 0;
[animationView startAnimating];
[self.view addSubview:animationView];
[animationView release];
add your own images in the array.repeat Count 0 means infinite loop.You can give your own number also.
There are at least 3 ways to animate an array of images through a UIImageView. I'm adding 3 links to download sample code for the 3 possibilities.
The first one is the one that everyone knows. The other ones are less known.
- UIImageView.animationImages
Example Link
The problem of this one is that do not have Delegate to tell us in which moment the animation is finished. So, we can have problems if we want to display something after the animation.
In the same way, there is no possibility to kept the last image from the animation in the UIImageView automatically. If we combine both problems we can have a gap at the end of the animation if we want to kept the last frame on screen.
self.imageView.animationImages = self.imagesArray; // the array with the images
self.imageView.animationDuration = kAnimationDuration; // static const with your value
self.imageView.animationRepeatCount = 1;
[self.imageView startAnimating];
- CAKeyframeAnimation
Example Link
This way to animate works through CAAnimation. It have an easy delegate to use and we can know when the animation finish.
This is probably the best way to animate an array of images.
- (void)animateImages
{
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
keyframeAnimation.values = self.imagesArray;
keyframeAnimation.repeatCount = 1.0f;
keyframeAnimation.duration = kAnimationDuration; // static const with your value
keyframeAnimation.delegate = self;
// keyframeAnimation.removedOnCompletion = YES;
keyframeAnimation.removedOnCompletion = NO;
keyframeAnimation.fillMode = kCAFillModeForwards;
CALayer *layer = self.animationImageView.layer;
[layer addAnimation:keyframeAnimation
forKey:#"girlAnimation"];
}
Delegate:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (flag)
{
// your code
}
}
- CADisplayLink
Example Link
A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
This way to do it is really interesting and opens a lot of possibilities to manipulate what are we showing in screen.
DisplayLink getter:
- (CADisplayLink *)displayLink
{
if (!_displayLink)
{
_displayLink = [CADisplayLink displayLinkWithTarget:self
selector:#selector(linkProgress)];
}
return _displayLink;
}
Methods:
- (void)animateImages
{
self.displayLink.frameInterval = 5;
self.frameNumber = 0;
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
- (void)linkProgress
{
if (self.frameNumber > 16)
{
[self.displayLink invalidate];
self.displayLink = nil;
self.animationImageView.image = [UIImage imageNamed:#"lastImageName"];
self.imagesArray = nil;
return;
}
self.animationImageView.image = self.imagesArray[self.frameNumber++];
self.frameNumber++;
}
GENERAL PROBLEM:
Even though we have this 3 possibilities, if your animation is with a lot of big images, consider using a video instead. The usage of memory will decrease a lot.
A General problem you will face doing this is in the moment of the allocation of the images.
If you use [UIImage imageNamed:#"imageName"] you will have cahe problems.
From Apple:
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method locates and loads the image data from disk or asset catelog, and then returns the resulting object. You can not assume that this method is thread safe.
So, imageNamed: stores the image in a private Cache.
- The first problem is that you can not take control of the cache size.
- The second problem is that the cache did not get cleaned in time and if you are allocating a lot of images with imageNamed:, your app, probably, will crash.
SOLUTION:
Allocate images directly from Bundle:
NSString *imageName = [NSString stringWithFormat:#"imageName.png"];
NSString *path = [[NSBundle mainBundle] pathForResource:imageName
// Allocating images with imageWithContentsOfFile makes images to do not cache.
UIImage *image = [UIImage imageWithContentsOfFile:path];
Small problem:
Images in Images.xcassets get never allocated. So, move your images outside Images.xcassets to allocate directly from Bundle.
See the animationImages property of UIImageView. It’s hard to say if it fits your needs as you don’t give us details, but it’s a good start.
I have added a swift 3.0 extension for this
extension UIImageView {
func animate(images: [UIImage], index: Int = 0, completionHandler: (() -> Void)?) {
UIView.transition(with: self, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.image = images[index]
}, completion: { value in
let idx = index == images.count-1 ? 0 : index+1
if idx == 0 {
completionHandler!()
} else {
self.animate(images: images, index: idx, completionHandler: completionHandler)
}
})
}
}
Best solution for me use CADisplayLink. UIImageView doesn't have completion block and you can't catch steps of animation. In my task i must changing background of view with image sequencer step by step. So CADisplayLink allows you handling steps and finishing animation. If we talk about usage of memory, i think best solution load images from bundle and delete array after finishing
ImageSequencer.h
typedef void (^Block)(void);
#protocol ImageSequencerDelegate;
#interface QSImageSequencer : UIImageView
#property (nonatomic, weak) id <ImageSequencerDelegate> delegate;
- (void)startAnimatingWithCompletionBlock:(Block)block;
#end
#protocol ImageSequencerDelegate <NSObject>
#optional
- (void)animationDidStart;
- (void)animationDidStop;
- (void)didChangeImage:(UIImage *)image;
#end
ImageSequencer.m
- (instancetype)init {
if (self = [super init]) {
_imagesArray = [NSMutableArray array];
self.image = [self.imagesArray firstObject];
}
return self;
}
#pragma mark - Animation
- (void)startAnimating {
[self startAnimatingWithCompletionBlock:nil];
}
- (void)startAnimatingWithCompletionBlock:(Block)block {
self.frameNumber = 0;
[self setSuccessBlock:block];
self.displayLink.frameInterval = 5;
if (self.delegate && [self.delegate respondsToSelector:#selector(animationDidStart)]) {
[self.delegate animationDidStart];
}
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
-(void)stopAnimating {
self.image = [self.imagesArray lastObject];
[self.displayLink invalidate];
[self setDisplayLink:nil];
Block block_ = [self successBlock];
if (block_) {
block_();
}
if (self.delegate && [self.delegate respondsToSelector:#selector(animationDidStop)]) {
[self.delegate animationDidStop];
}
[self.imagesArray removeAllObjects];
}
- (void)animationProgress {
if (self.frameNumber >= self.imagesArray.count) {
[self stopAnimating];
return;
}
if (self.delegate && [self.delegate respondsToSelector:#selector(didChangeImage:)]) {
[self.delegate didChangeImage:self.imagesArray[self.frameNumber]];
}
self.image = self.imagesArray[self.frameNumber];
self.frameNumber++;
}
#pragma mark - Getters / Setters
- (CADisplayLink *)displayLink {
if (!_displayLink){
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(animationProgress)];
}
return _displayLink;
}
- (NSMutableArray<UIImage *> *)imagesArray {
if (_imagesArray.count == 0) {
// get images from bundle and set to array
}
return _imagesArray;
}
#end
This is a simple and working code for animation./
-(void)move
{
[UIView animateWithDuration:5
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
[_imgbox setFrame:CGRectMake(100, 100, _imgbox.frame.size.width, _imgbox.frame.size.height)];
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}

UITableViewCell's imageView fit to 40x40

I use the same big images in a tableView and detailView.
Need to make imageView filled in 40x40 when an imags is showed in tableView, but stretched on a half of a screen. I played with several properties but have no positive result:
[cell.imageView setBounds:CGRectMake(0, 0, 50, 50)];
[cell.imageView setClipsToBounds:NO];
[cell.imageView setFrame:CGRectMake(0, 0, 50, 50)];
[cell.imageView setContentMode:UIViewContentModeScaleAspectFill];
I am using SDK 3.0 with build in "Cell Objects in Predefined Styles".
I put Ben's code as an extension in my NS-Extensions file so that I can tell any image to make a thumbnail of itself, as in:
UIImage *bigImage = [UIImage imageNamed:#"yourImage.png"];
UIImage *thumb = [bigImage makeThumbnailOfSize:CGSizeMake(50,50)];
Here is .h file:
#interface UIImage (PhoenixMaster)
- (UIImage *) makeThumbnailOfSize:(CGSize)size;
#end
and then in the NS-Extensions.m file:
#implementation UIImage (PhoenixMaster)
- (UIImage *) makeThumbnailOfSize:(CGSize)size
{
UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
// draw scaled image into thumbnail context
[self drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *newThumbnail = UIGraphicsGetImageFromCurrentImageContext();
// pop the context
UIGraphicsEndImageContext();
if(newThumbnail == nil)
NSLog(#"could not scale image");
return newThumbnail;
}
#end
I cache a thumbnail version since using large images scaled down on the fly uses too much memory.
Here's my thumbnail code:
- (UIImage *)thumbnailOfSize:(CGSize)size {
if( self.previewThumbnail )
return self.previewThumbnail; // returned cached thumbnail
UIGraphicsBeginImageContext(size);
// draw scaled image into thumbnail context
[self.preview drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *newThumbnail = UIGraphicsGetImageFromCurrentImageContext();
// pop the context
UIGraphicsEndImageContext();
if(newThumbnail == nil)
NSLog(#"could not scale image");
self.previewThumbnail = newThumbnail;
return self.previewThumbnail;
}
Just make sure you properly clear the cached thumbnail if you change your original image (self.preview in my case).
I have mine wrapped in a UIView and use this code:
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight;
[self addSubview:imageView];
imageView.frame = self.bounds;
(self is the wrapper UIView, with the dimensions I want - I use AsyncImageView).
I thought Ben Lachman's suggestion of generating thumbnails in advance rather than on the fly was smart, so I adapted his code so it could handle a whole array and to make it more portable (no hard-coded property names).
- (NSArray *)arrayOfThumbnailsOfSize:(CGSize)size fromArray:(NSArray*)original {
NSMutableArray *temp = [NSMutableArray arrayWithCapacity:[original count]];
for(UIImage *image in original){
UIGraphicsBeginImageContext(size);
[image drawInRect:CGRectMake(0,0,size.width,size.height)];
UIImage *thumb = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[temp addObject:thumb];
}
return [NSArray arrayWithArray:temp];
}
you might be able to use this?
yourTableViewController.rowImage = [UIImage imageNamed:#"yourImage.png"];
and/or
cell.image = yourTableViewController.rowImage;
and if your images are already 40x40 then you shouldn't have to worry about setting bounds and stuff... but, i'm also new to this, so, i wouldn't know, haven't played around with Table View row/cell images much
hope this helps.
I was able to make this work using interface builder and a tableviewcell. You can set the "Mode" properties for an image view to "Aspect Fit". I'm not sure how to do this programatically.
Try setting UIImageView.autoresizesSubviews and/or UIImageView.contentStretch.

Image from WebView dragged onto IKImageView

I have WebView where I load content of webarchive. In the same view I have IKImageView outlet. Image drag n drop from web view onto image view doesn't work for me.
What is weird, it works when I drag photo e.g. from iPhoto onto the same image view.
Also, I can drag image from my web view onto NSScrollView (which creates a link to the image) and I can drag the same photo onto a new Mail message (created an image as expected).
IKImageView has "Supports Drag and Drop" enabled in the IB.
What am I missing here?
IKImageView is probably expecting a pasteboard of NSFilenamesPboardType, how does the webview handle dragging images?
It turned out, that the best way to handle d'n'd in my case is via WebArchivePboardType.
Then:
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];
// Create image data from webarchive stored in a pasteboard.
NSData *image = [pboard dataForType:WebArchivePboardType];
WebArchive *webArchive = [[WebArchive alloc] initWithData:image];
// Let's see what are we dragging.
for (WebResource *subresource in [webArchive subresources])
{
NSString *mimeType = [subresource MIMEType];
if ([mimeType hasPrefix:expectedMimeTypeStartsWith])
{
NSData *data = [subresource data];
CFDataRef imgData = (CFDataRef)data;
CGDataProviderRef imgDataProvider = CGDataProviderCreateWithCFData (imgData);
CGImageRef image;
if ([mimeType hasSuffix:#"png"])
{
image = CGImageCreateWithPNGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault);
}
else if ([mimeType hasSuffix:#"jpeg"])
{
image = CGImageCreateWithJPEGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault);
}
[self setImage:image imageProperties:nil];
}
}
return YES;
}