Get ImageView Image Original Size - ios

I have a TableViewCell with an ImageView, and I am setting the image like so:
[self.contentImageView setImageWithURL:thumbnail_url];
The contentMode is set to UIViewContentModeScaleAspectFill, and the subviews are clipped.
This works best for images that are either Portrait or Landscape, but UIViewContentModeScaleToFill actually works better for Landscape images, which are far more plentiful.
So, I want to detect the orientation of the image by comparing the width and height, and change the contentMode accordingly.
At first, I tried to inspect/log the ImageView.image property, directly after I set it from the url, but it show's nil. not sure why exactly...?
Next, I decided to put that NSURL into an NSData object, then create an UIImage from that data, and set the ImageView's image property with that iVar, like so:
NSData *data = [NSData dataWithContentsOfURL:thumbnail_url];
UIImage *image = [UIImage imageWithData:data];
[self.contentImageView setImage:image];
// Get image.size etc.
This - particularly the NSData call - slows down the loading of the TableViewCell's considerably, so I'd like to avoid it.
So, I'm wondering if there is anyway to reach into the un-cropped source image properties of the ImageView before the scaling happens to the contentMode?

The problem here is your misunderstanding of how that first method works. setImageWithURL: is a method from one of the open source image loading libraries. Probably either AFNetworking or SDWebImage.
These methods are asynchronous and return immediately. They download the image on a background queue and then return to the main queue to set up the view with it. You are trying to access the image before it is downloaded. The reason the manual NSData approach is working is because it is synchronous and the main queue is stuck while the images download.
Both libraries I mentioned have separate methods with a callback block on the main queue allowing you to act on the response.
For AFNetworking you can use this method:
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
For SDWebImage you can use this method:
- (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock;
In either of these, the callback blocks will let you access the image once it is downloaded (if successful).
You can also make something like this work manually using the dataWithContentsOfURL: approach by using GCD like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^()
{
NSData *data = [NSData dataWithContentsOfURL:thumbnail_url];
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^()
{
[self.contentImageView setImage:image];
});
});
This is a basic pattern used in networking to perform the network request and processing on a background queue before updating the view on the main queue. Keep in mind this particular piece of code is very simplistic and would require more work to work as nicely as the libraries I mentioned above.

Related

Which image loading method should i prefer?

I have app with TableView and cells. In each cell there is UIImageView.
All images are stored on server.
I can use two different methods to load images. Which of them should prefer and why?
Method A :
Use library like SDWebImage to load image and place it in cellForRowAtIndexPath function. So image will be downloaded when cell is created.
Method B :
When i load JSON with image list from server i can create array of UIImages. In each of them i will asynchronously download image from server. And in cellForRowAtIndexPath function i can just assign one of previously created UIImages to current cell image.
Method A - SDWebimage is best for you.
and solve reuse in tableviewcell check this link : Handling download of image using SDWebImage while reusing UITableViewCell
Don't ever use method 2 to handle Images in your application. This is a data and memory wise expensive method. To the best of my understanding, this method would extensively increase the memory pressure. If you create an array of images that would remain in the memory as long as your view controller stays. As the size of this array increases the situation will get worse.
SDWebImage is far far better approach for this task. It saves the images to local storage once downloaded thus creating a cache of images. So you do not have to download the images again and again.
i wish to use AFNetworking if you don't need to cache image , this tools faster than SDWebImage when load image from server . //////
if you use custom cell => replace UITableViewCell with your name file for cell
-(void)fetchImageFromURL:(NSString*)imageURLString Cell:(UITableViewCell*)cell {
/*
_ This Function will accept the string url for Image
_ Also the cell that have image icon
_ After take paramter will make request to fetch image
_
1- if return image => will show Image
*/
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#", imageURLString]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
__weak UITableViewCell *weakCell = cell;
// get image with request
[[cell imageView ] setImageWithURLRequest:request placeholderImage:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[[weakCell ImageIcon ] setImage:image];
[weakCell setNeedsLayout];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"couldn't load image with url :%#", url);
}];
}
The best choice in my opinion is to rely on a stable, fresh, followed and secure framework.
Recently come to the scene a great framework written in Swift called Nuke (Swift 3 - Xcode 8) . I think you could try Nuke, his power is the "preheat images" (preheating/prefetching means loading images ahead of time in anticipation of its use.), it's full compatible with Alamofire , already knowed by community (raywenderlich.com) and now is at v.4.x (stable and mature). This library have custom handlers and requests with many options.

UIImage initWithData: blocking UI thread from async dispatch?

The following code is blocking UI (can't tap another tab until images finish loading).
I have verified it is the UIImage *imageToShow = [[UIImage alloc] initWithData:data]; call that is the culprit by commenting that line out and keeping the download (which I know happens asynchronously and calls completion handler on main thread).
In the case where only the initWithData: line is commented, the UI response is fine. But how can that line be the line holding up the UI if it is clearly dispatched in background?
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
...
[objectStore getFileInContainer:#"public_images" filename:filename completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *imageToShow = [[UIImage alloc] initWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
collectionImageView.image = imageToShow;
});
});
}];
...
}
UIImage doesn't read and decode the image until the first time it's actually used/drawn. To force this work to happen on a background thread, you have to use/draw the image on the background thread before doing the main thread -setImage: call. Many folks find this counter-intuitive. I explained this in considerable detail in another answer.
The part most likely to cause the UI to hang is the actual setting of the image to the image view, especially if the image is large, because this does (and must) occur on the main thread.
I would suggest resizing or scaling down the image first while you are still operating in the background thread. Then once you have the image resized, hop back on the main thread and assign it to the ``imageView.image```.
Here is a simple example of one way to resize a UIImage: Resize UIImage by keeping Aspect ratio and width

How to asynchronously load an image in an UIImageView?

I have an UIView with an UIImageView subview. I need to load an image in the UIImageView without blocking the UI. The blocking call seems to be: UIImage imageNamed:. Here is what I thought solved this problem:
-(void)updateImageViewContent {
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage * img = [UIImage imageNamed:#"background.jpg"];
dispatch_sync(dispatch_get_main_queue(), ^{
[[self imageView] setImage:img];
});
});
}
The image is small (150x100).
However the UI is still blocked when loading the image. What am I missing ?
Here is a small code sample that exhibits this behaviour:
Create a new class based on UIImageView, set its user interaction to YES, add two instances in a UIView, and implement its touchesBegan method like this:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.tag == 1) {
self.backgroundColor= [UIColor redColor];
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:[UIImage imageNamed:#"woodenTile.jpg"]];
});
[UIView animateWithDuration:0.25 animations:
^(){[self setFrame:CGRectInset(self.frame, 50, 50)];}];
}
}
Assign the tag 1 to one of these imageViews.
What happens exactly when you tap the two views almost simultaneously, starting with the view that loads an image? Does the UI get blocked because it's waiting for [self setImage:[UIImage imageNamed:#"woodenTile.jpg"]]; to return ? If so, how may I do this asynchronously ?
Here is a project on github with ipmcc code
Use a long press then drag to draw a rectangle around the black squares. As I understand his answer, in theory the white selection rectangle should not be blocked the first time the image is loaded, but it actually is.
Two images are included in the project (one small: woodenTile.jpg and one larger: bois.jpg). The result is the same with both.
Image format
I don't really understand how this is related to the problem I still have with the UI being blocked while the image is loaded for the first time, but PNG images decode without blocking the UI, while JPG images do block the UI.
Chronology of the events
The blocking of the UI begins here..
.. and ends here.
AFNetworking solution
NSURL * url = [ [NSBundle mainBundle]URLForResource:#"bois" withExtension:#"jpg"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[self.imageView setImageWithURLRequest:request
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
NSLog(#"success: %#", NSStringFromCGSize([image size]));
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"failure: %#", response);
}];
// this code works. Used to test that url is valid. But it's blocking the UI as expected.
if (false)
if (url) {
[self.imageView setImage: [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]]; }
Most of the time, it logs: success: {512, 512}
It also occasionnaly logs: success: {0, 0}
And sometimes: failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...
But the image is never changed.
The problem is that UIImage doesn't actually read and decode the image until the first time it's actually used/drawn. To force this work to happen on a background thread, you have to use/draw the image on the background thread before doing the main thread -setImage:. This worked for me:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage * img = [UIImage imageNamed:#"background.jpg"];
// Make a trivial (1x1) graphics context, and draw the image into it
UIGraphicsBeginImageContext(CGSizeMake(1,1));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), [img CGImage]);
UIGraphicsEndImageContext();
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
[[self imageView] setImage: img];
});
});
EDIT: The UI isn't blocking. You've specifically set it up to use UILongPressGestureRecognizer which waits, by default, a half a second before doing anything. The main thread is still processing events, but nothing is going to happen until that GR times out. If you do this:
longpress.minimumPressDuration = 0.01;
...you'll notice that it gets a lot snappier. The image loading is not the problem here.
EDIT 2: I've looked at the code, as posted to github, running on an iPad 2, and I simply do not get the hiccup you're describing. In fact, it's quite smooth. Here's a screenshot from running the code in the CoreAnimation instrument:
As you can see on the top graph, the FPS goes right up to ~60FPS and stays there throughout the gesture. On the bottom graph, you can see the blip at about 16s which is where the image is first loaded, but you can see that there's not a drop in the frame rate. Just from visual inspection, I see the selection layer intersect, and there's a small, but observable delay between the first intersection and the appearance of the image. As far as I can tell, the background loading code is doing its job as expected.
I wish I could help you more, but I'm just not seeing the problem.
You can use AFNetworking library , in which by importing the category
"UIImageView+AFNetworking.m"
and by using the method as follows :
[YourImageView setImageWithURL:[NSURL URLWithString:#"http://image_to_download_from_serrver.jpg"]
placeholderImage:[UIImage imageNamed:#"static_local_image.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
//ON success perform
}
failure:NULL];
hope this helps .
I had a very similar issue with my application where I had to download lot of images and along with that my UI was continuously updating. Below is the simple tutorial link which resolved my issue:
NSOperations & NSOperationQueues Tutorial
this is the good way:
-(void)updateImageViewContent {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage * img = [UIImage imageNamed:#"background.jpg"];
[[self imageView] setImage:img];
});
}
Why don't you use third party library like AsyncImageView? Using this, all you have to do is declare your AsyncImageView object and pass the url or image you want to load. An activity indicator will display during the image loading and nothing will block the UI.
-(void)touchesBegan: is called in the main thread. By calling dispatch_async(dispatch_get_main_queue) you just put the block in the queue. This block will be processed by GCD when the queue will be ready (i.e. system is over with processing your touches). That's why you can't see your woodenTile loaded and assigned to self.image until you release your finger and let GCD process all the blocks that have been queued in the main queue.
Replacing :
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:[UIImage imageNamed:#"woodenTile.jpg"]];
});
by :
[self setImage:[UIImage imageNamed:#"woodenTile.jpg"]];
should solve your issue… at least for the code that exhibits it.
Consider using SDWebImage: it not only downloads and caches the image in the background, but also loads and renders it.
I've used it with good results in a tableview that had large images that were slow to load even after downloaded.
https://github.com/nicklockwood/FXImageView
This is an image view which can handle background loading.
Usage
FXImageView *imageView = [[FXImageView alloc] initWithFrame:CGRectMake(0, 0, 100.0f, 150.0f)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.asynchronous = YES;
//show placeholder
imageView.processedImage = [UIImage imageNamed:#"placeholder.png"];
//set image with URL. FXImageView will then download and process the image
[imageView setImageWithContentsOfURL:url];
To get an URL for your file you might find the following interesting:
Getting bundle file references / paths at app launch
When you are using AFNetwork in an application, you do not need to use any block for load image because AFNetwork provides solution for it. As below:
#import "UIImageView+AFNetworking.h"
And
Use **setImageWithURL** function of AFNetwork....
Thanks
One way i've implemented it is the Following: (Although i do not know if it's the best one)
At first i create a queue by using Serial Asynchronous or on Parallel Queue
queue = dispatch_queue_create("com.myapp.imageProcessingQueue", DISPATCH_QUEUE_SERIAL);**
or
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
**
Which ever you may find better for your needs.
Afterwards:
dispatch_async( queue, ^{
// Load UImage from URL
// by using ImageWithContentsOfUrl or
UIImage *imagename = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// Then to set the image it must be done on the main thread
dispatch_sync( dispatch_get_main_queue(), ^{
[page_cover setImage: imagename];
imagename = nil;
});
});
There is a set of methods introduced to UIImage in iOS 15 to decode images and create thumbnails asynchronously on background thread
func prepareForDisplay(completionHandler: (UIImage?) -> Void)
Decodes an image asynchronously and provides a new one for display in views and animations.
func prepareThumbnail(of: CGSize, completionHandler: (UIImage?) -> Void)
Creates a thumbnail image at the specified size asynchronously on a background thread.
You can also use a set of similar synchronous APIs, if you need more control over where you want the decoding to happen, e.g. specific queue:
func preparingForDisplay() -> UIImage?
func preparingThumbnail(of: CGSize) -> UIImage?

how to combine drawRect: and ImageCache to speed up table view scrolling

I need to implement my custom drawRect: method in my custom table cell in order to speed up tableview scrolling, however, there's an image the app should download from web, so I want to add image cache to my app. The origin implementation of my app is using SDWebImage library, which implement an imageView which offer download image method. If I want to add image in drawRect:, how to implement downloading image from web and cache them?
You need to use SDWebImage to download and cache the image. Then in delegate/completion block of that download operation, you need to assign the image to your a property and call setNeedsDisplay. setNeedsDisplay will cause your view to be redrawn. This should be called on mainQueue.
[[SDWebImageManager sharedManager] downloadWithURL:[NSURL URLWithString:theArticle.imageURL] options:SDWebImageLowPriority progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
dispatch_async(bakground_queue, ^{
//Here you probably want to dispatch_async to a background queue to do all the image resizing first before drawing on main queue
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.thumbnailImage = image;
[weakSelf setNeedsDisplay];
});
});
}];
In your drawRect, you need to draw from that property. For e.g
[self.image drawInRect:rect]
This is just basic principle of how this can be achieved. Probably needs more work to really optimize it (e.g cancel the downloading operation when not needed)
Edited:
SDWebImageManager has a delegate method to resize/transform image before storing it to disk cache. You might want to use that method instead of dispatch_async to background queue like above.

UICollectionView bad acces on -> UICollectionViewData _setLayoutAttributes:atGlobalIndex:

I use an UICollectionView to display a lot of images with a batch of 32. Every time the i reach the end of the collection view i load an other batch of 32 images and resize the collectionView contentsize.width to accepte the new items. The loading is made by calling a webservice with AFNetworking.
When i scroll very fast from the start to the end and to the end to the start i receive a EXC_BAD_ACCESS.
It also happend when a reach the end of the CollectionView. It's like it tries to load some attributes that are not already available.
I tried to figure it out since 1 day without any success. I tried with instrument / NSZombie enabled/ guardmalloc ...
EDIT
There is also a very strange think: This bad access only appeared when i replaced PSTCollectionView with the real UICollectionView. So to be sure i just made de inverse move and replace UICollectionView with PSTCollectionView and the badaccess disappeared.
I'm totaly lost :)
END EDIT
I'm using both arc and non arc files in the project.
The only think i'm able to spot is this stack trace :
Your help will be more than Welcome.
Blockquote
The issue may be that the images are just too big. What you may want to do is resize the image from the downloaded image before setting the image property on each UIImageView.
Bill Dudney published a very useful iBook on the topic which goes into detail on the consequences of images on memory and how to optimize for it. It is $4.99 and very helpful.
https://itunes.apple.com/us/book/all-image-io-you-need-to-know/id601759073?mt=11
The following method will resize an image to the given size which will decrease the memory footprint and hopefully prevent your issue.
- (UIImage *)resizeImage:(UIImage *)image size:(CGSize)size {
UIGraphicsBeginImageContext(size);
[image drawInRect: CGRectMake(0, 0, width, height)];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resizedImage;
}
Now if you are using AFNetworking to set the image directly from the URL using the AFNetworking category you may want to use the alternate method so you can intervene and resize the image. The code below will do that.
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:imageURL];
[request addValue:#"image/*" forHTTPHeaderField:#"Accept"];
[imageView setImageWithURLRequest:request placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
// resize image
// set image on imageView
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
// handle error
}];

Resources