In my application,I am allowing user to select one or multiple images from gallery and as the user is selecting the images,I am storing all the url in an array
- (void)elcImagePickerController:(ELCImagePickerController *)picker didFinishPickingMediaWithInfo:(NSArray *)info
{
[self dismissViewControllerAnimated:YES completion:nil];
for (NSDictionary *dict in info) {
url = dict[#"UIImagePickerControllerReferenceURL"];
[self.images addObject:url];
NSLog(#"array value:%#",self.images);
[self.myPDTable reloadData];
}
}
The output I get is
array value:(
"assets-library://asset/asset.JPG?id=74236046-12DA-4E75-9038-A3092D31005B&ext=JPG",
"assets-library://asset/asset.JPG?id=98FC160A-60E1-4F29-80C8-1AED036F1140&ext=JPG",
"assets-library://asset/asset.JPG?id=4D18448E-9796-47AE-A534-FB3E64518B89&ext=JPG",
"assets-library://asset/asset.JPG?id=38099BA1-4988-46CB-ADE9-E1F3F896147A&ext=JPG",
"assets-library://asset/asset.JPG?id=86556040-16C6-47BB-8CBA-F5164771EC9F&ext=JPG"
)
Now my problem is that I don't know how to use these value and display them in my table view cell.
What you've got in each element of your array is an asset URL. To get from an asset URL to an image, start with the ALAssetsLibrary object, call assetForURL:resultBlock:failureBlock: to fetch the corresponding asset (the photo file), and, in the resultBlock, pull out the thumbnail and use it in your interface.
Here's some typical code for getting from an ALAsset to a usable image:
ALAsset* photo = // let's assume you've obtain an asset
CGImageRef im = photo.aspectRatioThumbnail;
UIImage* im = [UIImage imageWithCGImage:im scale:0
orientation:UIImageOrientationUp];
However, you've made a very odd design choice here. assetForURL:resultBlock:failureBlock: is time-consuming and operates asynchronously; it is not suitable for calling in a table view data source method (when you configure your cells), which needs to be fast. In didFinishPickingMediaWithInfo:, why did you ask the info for its UIImagePickerControllerReferenceURL? You don't need an array of URLs; you need an array of images! The info was offering you the image right then (its UIImagePickerControllerOriginalImage); why didn't you take it when it was offered? You could have just grabbed the image, sized it down, and stuck the sized-down image into your table data model, ready for use.
(Also, it is silly and wasteful to call reloadData every time through the for loop. You should call it just once, after you've gathered all the images into your model.)
Related
I have a working UIScroll view with local Images in my app. I want however, that my Images will be downloaded and stored in Cache from a URL. I have seen several example libraries that do this like sdwebimage, kingfisher etc. but the examples use UITableview and cells. I am using a UIImage Array for my scroll view. What I actually want is that I download and cache my images and store them in a Array IconsArray = [icon1, icon2, icon3] where icon1 to icon3 are the downloaded images from URLs. How would I do this? Any nice tutorials out there or someone kind enough to show a rookie some code?
Thanks in advance
If you are downloading many images you will have memory problems, and your work will also get thrown away when your array goes out of scope, but what you probably would want to do, if you want to implement your proposed solution, is to use a dictionary rather than an array. It'll just make it much easier to find the image you're looking for. So you could implement the dictionary like this:
var images = [String : UIImage]()
For the key you can just use the URL string (easy enough solution) so accessing an image safely would look like this:
let urlString = object.imageUrl.absoluteString //or wherever you're getting your url from
if let img = self.images[urlString] {
//Do whatever you want with the image - no need to download as you've already downloaded it.
cell.image = img
} else {
//You need to download the image, because it doesn't exist in your dict
...[DOWNLOAD CODE HERE]...
//Add the image to your dictionary here
self.images[object.imageUrl.absoluteString] = downloadedImage
//And do whatever else you need with it
cell.image = downloadedImage
}
As I said, this has some downsides, but it's a quick implementation of what you're asking for.
When i'm switching from a view (by clicking on a cell in a table) to a view that displays some images, i'm loading a couple of images from a couple of urls.
I want to display an activity indicator animation while the loading occurs.
I'm doing this loading in viewDidLoad of the new view.
If i'm loading the images synchronously, then (not surprisingly) the animation doesn't work since the request is blocking...
If i'm loading the images asynchronously, then (also not surprisingly) the view is opened with blanks instead of images, without waiting for the images to be fetched, which i don't want.
I tried to put all that in the segue code that transforms from the old view to the new one, because i hoped that the view will be switched only after the loading will complete but it didn't matter.
How can I enjoy both worlds? how can i not block the app, display the animation, but transition to the next view only when all the images have been loaded?
If you don't want to use third party libraries, you can use GCD (Grand central dispatch) like this:
dispatch_queue_t imageLoadingQueue = dispatch_queue_create("imageLoadingQueue", NULL);
// start the loading spinner here
dispatch_async(imageLoadingQueue, ^{
NSString * urlString = // your URL string here
NSData * imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]];
UIImage * image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
// stop the loading spinner here and place the image in your view
});
});
Have a look at AFNetworking and their UIImageView Extension:
setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage;
Asynchronously downloads an image from the specified URL, and sets it
once the request is finished.
If the image is cached locally, the
image is set immediately, otherwise the specified placeholder image
will be set immediately, and then the remote image will be set once
the request is finished.
You should take a look at AsyncImageView. It does exactly what you want to do.
You can find it at: https://github.com/nicklockwood/AsyncImageView
If you don’t want to show certain views while you loading images you can simply hide it, like this self.imageView.hidden = YES;
After loading you can set your images, and then set hidden to NO.
If you, for some reason, don’t want to segue to another view, before loading completes, then I think you can make intermediate view, where you’ll download all images, after that segue to your images view, and send that images through prepareForSegue:sender:.
And answer from Aurelien Cobb cleared out how to load images asynchronously.
Check SDWebImage (https://github.com/rs/SDWebImage),
it has something called prefetcher - which will download the images and save them in cache,
after which you will receive a callback, and can transition to the new view.
Needless to say that you will have to show a spinner (SVProgressHUD or MBProgressHUD),
and stop it once prefetch callback returns.
https://github.com/rs/SDWebImage/blob/master/SDWebImage/SDWebImagePrefetcher.h
/**
* Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching,
* currently one image is downloaded at a time,
* and skips images for failed downloads and proceed to the next image in the list
*
* #param urls list of URLs to prefetch
*/
- (void)prefetchURLs:(NSArray *)urls;
To show those images in your imageView, just use setImageWithURL: method (UIImageView+WebCache category).
I have a collectionView with UICollectionViewFlowLayout with some reusability:
- (CategoryCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CategoryCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CategoryCell" forIndexPath:indexPath];
[cell.actIn startAnimating];
cell.cellElementRepresentation = [self.localCategoriesObjects objectAtIndex:indexPath.row];
[cell tryToShow]; //this is a important method to my problem
return cell;
}
I commented for you tryToShow method. Becouse i think this is a problem. When i scroll to the not visible element in the list I could see "old" elemnts first then new element. Why is that? My tryToShow method is basically download manager. I try to describe it in comment. Here is the code:
-(void)tryToShow {
NSString* imageFile = self.linkToImageFileSavedOnDisk //This is my local file saved in documentsDirectory.
if([[NSFileManager defaultManager] fileExistsAtPath:imageFile]) {
[self.overlayButton setBackgroundImage:[UIImage imageWithContentsOfFile:imageFile] forState:UIControlStateNormal];
[self disableActIn]; //Here i stop and remove activity indicator.
} else {
// Here i fire NSURLConnection and try to download. When NSURLConnection finished i put image to overlayButton.
}
}
Here is delegate method:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
//Here do some stuff to convert Data into UIImage... not important.
[self.overlayButton setBackgroundImage:object forState:UIControlStateNormal];
}
The overlayButton is a button on the UICollectionViewCell. I have a custom class for it.
I can't figure it out. How it's possible to image from indexPath.row (for example 1) is visible after scroll ? The image is replaced by new one right after new is successfully download, but this is to late.
As the OP mention, the problem seems to be that you are setting the backgroundImage too late. So I would try this modifications:
First, clear your cell before setting the new values. Since you are reusing the cell, the old values (text and images) will still be present in the cell subviews. Setting those values to nil will clear your cell.
Implement a mechanism to validate or invalidate the result from the image request, since could get the result too late, maybe is no longer needed. Worst, you can set an image no longer correspondent with your data. You should check, before setting a downloaded image, that this image is consistent with the current shown data.
Wait a little before start the image request If you have a large number of cells, you should think that when the user quickly scroll the collection, you can start lots of requests for images that may never be displayed. I would avoid this behavior by waiting a little before start a new request, and checking if the cell has been reused again.
Use the NSURLRequest and configure the cachePolicy attribute as NSURLRequestReturnCacheDataElseLoad. This will store the responses for the images, there is no need to save the images explicitly save the image and check it's existence.
I think you need to set the background image of self.overlayButton to nil in the else clause where you start the web request.
[self.overlayButton setBackgroundImage:nil forState:UIControlStateNormal];
That way the cell won't appear to have an image loaded already when the user scrolls to it. Or, if it's not loading quickly enough (which it should), you could possibly set the background image to nil in the collectionView:didEndDisplayingCell:forItemAtIndexPath: delegate method instead.
But a problem I see is that you will be creating duplicate NSURLConnection objects every time tryToShow is called, i.e., the user scrolls down then revisits the cells at the top.
As #lucaslt89 suggested, you could create a dictionary of cells instead of using the default dequeuing. If you do it this way, make sure you have a property for the URL connection object on the cell, and you check for it before starting a new one.
Or, you may want to create a dictionary of any NSURLConnection objects you create in the view controller, and move the web request code into the view controller. If a web request is needed, the view controller would check if there was already a web request running in the dictionary first. When the request is finished, the request object would be removed from the dictionary. This would be the desirable method because the cell isn't responsible for creating its own data. In UICollectionView the cells have to quickly change roles and it's not wise to associate a particular cell with some data unless there are really few cells.
Either way, you'll avoid creating multiple URL requests for the same image.
Hope this helps!
I'm working on my first more complex app and couldn't find a solution to this one. I have loaded about 64 icons into my app. For a table view, a user can assign to each cell one of these icons. Basically when he edits the cell he gets to a new UIView with all the 64 icons. The currently chosen icon should have a border and if he clicks a different one, the border should move and the icon assigned to that selected item in the tableview.
My problems are now:
a) How do I load these 64 icons into my view? I've created the view with all the UIImageViews, but how do I load them into these imageviews? Where are they saved if I copy them just to my directory and how do I access them?
b) Is there an easier way than placing 64 different UIViews into this view manually and linking them up with IBOutles?
Your problem could easily be resolved using UICollectionView . A UICollectionView just acts like a UITableView .
Go through the link in order to find more about UICollection
http://skeuo.com/uicollectionview-custom-layout-tutorial
Using UICollection view you just need the pass the image object saved in your Array
That's a lot of questions on a wide variety of topics.
How do I load icons/images?
UIImage *image = [UIImage imageNamed:#"fubar.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
If you just copy images in your project they are part of the main bundle and for the most part you and pretty much ignore where they are saved. It's pretty easy to access them. (see above)
Is there an "easier way"... well, you'd have to give us a way if you want to know if there is an easier way. However I think a simple naming convention and a loop would be workable.
NSMutableArray *icons = [NSMutableArray array];
NSString *iconName;
UIImage *image;
UIImageView *imageView; = [[UIImageView alloc] initWithImage:image];
for (int count=0 ; count < 65 ; count++) {
iconName = [NSString stringWithFormat:#"icon%d.png", count];
image = [UIImage imageNamed:iconName];
[icons addObject:image];
}
Then you would use your icons array as the basis of your UITableView. About that though. You might want to look into using a UICollectionView instead. It's like a table, but it's a grid of items instead of just rows. It would lend itself better to a large set of images.
As for how you copy the images into the views, both your UITableView and your UICollectionView have methods where they "ask" for the data and give you a position. Based on the position information you just set the it asks for and it will handle the rest. It might look something like
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
// ...
// remembering 'icons' is our array of image data
cell.imageView.image = icons[indexPath.row];
// ...
}
Edit from Comments: How to tell if a file exists in the app bundle? (my answer is just a cut/paste)
[[NSFileManager defaultManager] fileExistsAtPath:pathAndFileName];
You would have to do this in code. I would suggest you put all your filenames in NSArray, then iterate through it and with each step create a UIImageView on your ViewController programmatically. I suggest though that you subclass UIButton so it is "selected" when you tap it and you can then put any image into it.
you can use gridView a open source component here is the link
GridView
Also to detect you can assign tag to each imageView like from 0 to 63 then when user choose 0th image you can assign that tag to a local variable so that next time when user views grid you can identify that user has previously chose 0th index image. I hope it helps you.
I'm displaying lots of images loaded directly from my app (not downloaded). My table view is slow when I scroll it the first time. It becomes smooth after all my cell has been displayed. I don't really know why.
I have an array of UIImage that I'm loading in the viewDidLoad. Then in my tableview delegate I just get the image at a given index path and set it to an UIImageView of my cell.
Do you know how I can improve performances ?
just to share I have fixed and it worked very well Steps I followed.
1) Set the performSelectorInBAckground function with Cell as parameter passed that holds the scroll view or uiview to put many iamges.
2) In the background function load the image stored from application bundle or local file using imagewithContents of file.
3) Set the image to the imageView using this code.
//// Start of optimisation - for iamges to load dynamically in cell with delay , make sure you call this function in performSelectorinBackground./////
//Setting nil if any for safety
imageViewItem.image = nil;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
UIImage *image = // Load from file or Bundle as you want
dispatch_sync(dispatch_get_main_queue(), ^{
//Set the image to image view not, wither cell.imageview or [cell add subviw:imageview later ]
[imageViewItem setImage:image];
[imageViewItem setNeedsLayout];
});
});
//// End of optimisation/////
This will load all images dynamically and also scroll the table view quite smoothly than previous slow and jerky behaviour.
All the best
You can read the answer I have just submitted here:
Loading image from CoreData at cellForRowAtIndexPath slows down scrolling
The basic idea is to use Grand Central Despatch to move your table-view-image-getting code to a separate thread, filling in your cells back on the main thread as the images become available. Your scrolling will be super-smooth even if there's a delay loading the images into memory from the filesystem.
What I understand from your question is that your images are all set to go and that they are loaded into RAM (stored in an array which is populated in viewDidLoad). In this case, I would want to see the exact code in cellForRowAtIndexPath in order to help out. My instinct tells me that something is being done there that shouldn't be done on the main thread (as He Was suggests). The thing is - if it's only a fetch from an NSArray (worst case O(log(n))), you shouldn't be seeing a performance hit.
I know you're not downloading the images but I would still recommend to do ANY non-UI operation on a background thread. I wrote something that might help you out.