display downloaded images in tableview - ios

I'm making an app in Swift 2.0 and I'm using a table view with an image view in the prototype cell. The app downloads some images from the facebook server and I want to display them. The images are all downloaded from a different url, so I am using a program to loop trough them and download, just like I did with the names to get them from the internet and display, but I'm a little bit stuck..
func fetchPicture(identifier: String, completion: (image: UIImage) -> Void)
{
let url2 = NSURL (string: "http://graph.facebook.com/" + identifier + "/picture?type=normal")
let urlRequest = NSURLRequest(URL: url2!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) {
(response, data, error) -> Void in
if error != nil
{
print(error)
}
else
{
if let pf = UIImage(data: data!)
{
dispatch_async(dispatch_get_main_queue())
{
completion(image: pf)
self.tableView.reloadData()
}
}
}
}}
var images = [UIImage]()
let queue2 = dispatch_queue_create("images", DISPATCH_QUEUE_SERIAL)
dispatch_apply(newArray.count, queue2) { index in
let identifier = newArray[index]
fetchPicture(identifier) {
image in dispatch_async(queue2)
{
}
}
}
I set the imageview in the cell equal to the variable 'image' so basically I will need to do something like self.image = pf but it needs to be different for each user. With names, I did this with an array, but this isn't working with images I assume..
Help is really appreciated guys!
Thanks!

You would generally want to use a framework that specializes on that kind of stuff. I would not recommend using SDWebImage, it's outdated (no NSURLSession), has a lot of open issues and doesn't work well with Swift (no nullability annotations).
Take a look at those two libraries that are up to date with iOS platform:
DFImageManager - advanced framework written in Objective-C but featuring nullability annotations (works great with Swift). Here's a list of things that make it better, than SDWebImage. Disclosure it's written by me, opinion might be biased.
Kingfisher - lightweight library written in Swift. Similar to SDWebImage, but has much less features that SDWebImage and DFImageManager.

You generally would not want to load all images like this. You definitely don't want to reload the whole table after every image (because that causes it to scroll back to the top of the tableview).
Generally it would be advised to fetch the images lazily (i.e. not until they're needed). You might call fetchPicture from cellForRowAtindexPath and in the completion handler update the cell's image view accordingly.
There are a bunch of details you have to worry about, though:
First, the cell may have been reused by the time the fetch is done, so you have to check to make sure the cell is still visible.
Second, rather than using an array, I'd suggest using a NSCache, and make sure to purge this cache upon memory pressure. You might want to also cache to persistent storage, though some people prefer to rely upon the NSURLCache mechanisms
Third, if the cell scrolls out of view, you might want to cancel the request (so that, for example, if you quickly scroll to the 100th row, that image request doesn't get backlogged behind 99 other image requests for cells that are no longer visible).
Personally, given all the work necessary to do this properly, I might suggest that you consider using a well established asynchronous image fetching mechanism, such as SDWebImage, DFImageManager or Kingfisher. Look at the UIImageView categories/extensions that offer asynchronous image loading. To do all of this properly yourself is a non-trivial exercise, so it's better to use some existing library that provides an asynchronous image view retrieval mechanism.

Related

SDWebImage Prefetching with completion block

My aim is to cover a collectionView with a 'loading screen' which will not be hidden until all the images from a pre-fetched array of images have been loaded into cache by SDWebImage.
In my viewDidLoad I have retrieved an array of image URL's which will be used to populate a collection view. Once they are retrieved I plan on using SDWebImagePrefetcher to process the array.
so far I have the below:
let urls : [URL] = [URL(string: "https://trialwebsite.com/image1.png")!,URL(string: "https://trialwebsite.com/image2.png")!,URL(string: "https://trialwebsite.com/image3.png")!]
SDWebImagePrefetcher.shared().prefetchURLs(urls)
What I am struggling to figure out is how to use a completion block to hide the 'loading' screen once all the images have been processed.
Any help much appreciated.
You could use prefetchURLs:completed: instead of using prefetchURLs, it would has a completion block (closure since you are writing Swift) parameter that contains finishedCount and skippedCount unsigned integers:
as mentioned in the method documentation:
completionBlock
block to be called when prefetching is completed
which is seems to be what are you asking for. So it would be something like:
SDWebImagePrefetcher.shared().prefetchURLs(urls) { finishedCount, skippedCount in
// hide the 'loading' screen...
// you might need to implement your own counting logic
// to make sure that all images have been processed.
}
In Swift v4.1 & SDWebImage v3.8.2
SDWebImagePrefetcher.shared().prefetchURLs(arrayOfURLS, progress: nil, completed: { finishedCount, skippedCount in
print("Prefetch complete!")
})

How can I load Images from URL into an UIImage Array and use them in UIScrollView with Swift

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.

Load images for collection view in background thread

So I have a collection view in which I'm populating 141 images into it, but the problem I'm having is when scrolling through the view, it's not very smooth. I've already tried using low res images for the cells (which helped slightly) and decreasing the swipe speed (or should I say increasing the deceleration when scrolling).
So I figured my next step would be to try loading the cells on a background thread?
Firstly I have no clue how to do this, and Google isn't much help. Secondly, I'm not sure if the fact I'm re-using cells when they go out of view (so I don't create 141 cells, one cell per image) makes a difference.
Here's how I'm loading in the images:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("TreeCell", forIndexPath: indexPath) as? TreeCell {
cell.contentView.backgroundColor = UIColor.clearColor()
let tree = Tree(treeNumber: (indexPath.row + 1))
cell.configureCell(tree)
return cell
}
else {
return UICollectionViewCell()
}
}
TreeCell for whoever asked:
var tree: Tree!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
layer.cornerRadius = 10.0
}
func configureCell(tree: Tree) {
self.tree = tree
treeCellImage.image = UIImage(named: "\(self.tree.treeNumber)")
}
And the Tree class:
private var _treeNumber: Int!
init(treeNumber: Int) {
_treeNumber = treeNumber
}
var treeNumber: Int {
return _treeNumber
}
Don't try to create UICollectionViewCells on a background thread. The cells should be created only via a dequeueReusableCellWithReuseIdentifier variant and UI prefixed classes (UIKit) are not thread-safe unless explicitly stated as such. As you said, it will break cell reuse and potentially cause all sorts of memory issues.
Check out this SO question: Load UIImage in background Thread
If you aren't supporting iOS 8, you could load the images from disk on the background. Once they're loaded, I'd post a NSNotification on the main thread containing the image's unique id (filename?). The cells can all listen to the notification and – if the notification is observed for the cell's current image – display the new UIImage.
If you can use [UIImage imageNamed:], you'll get some nice low-memory-reactive caching benefits for free. Otherwise, you'll probably want to maintain a cache (NSCache or NSDictionary) of the images that you're already loaded.
If you do need to support iOS 8, you can still load image data from disk in the background via NSData, and feed the image data into a UIImage on the main thread. The expensive task is reading from disk. If your images are in Asset Catalogs, this will require a lot more work than imageNamed.
Now, assuming you find a method of safely loading the images on a background thread/queue that works in your specific situation, you have a few options to further optimize performance:
Cap concurrent tasks
Try to limit the number of concurrent image loads otherwise a quick scroll can saturate the system with blocking tasks and thread switching overhead.
Cancellable tasks
You should be able to cancel pending image load operations. If the user is scrolling just a bit faster than you can load the images, you want to be able to cancel loads for images that have already moved off-screen. Otherwise you can fill up the queue with unseen images.
Intelligent image loading
Don't load images that will be scrolled away in milliseconds (i.e. in response to a 'flick' or quick swipe). You can figure this out by hooking into the collection view UIScrollViewDelegate method
-scrollViewWillEndDragging:withVelocity:targetContentOffset:. This method lets you know how fast a user dragged the content and where the collection view will end up. You can even pre-load the images near the target content offset so that they're ready when scrolling ends!
A good for this sort of thing is NSOperationQueue because NSOperation instances support prioritization, concurrency caps, and concurrent execution. If you go with Grand Central Dispatch, you'll need to build in the ability to 'cancel' a task on your own.
Based on your previous comment on the image size, the first thing you should do is to resize your 200 x 200 images to match the 90 x 90 dimensions of your collectionViewCell.
UIKit will resize the 200 x 200 images as you populate your cells. Resizing images in general incur a non trivial cost, and this is done on the main thread. You should see drastic increase in smooth scrolling if you gave your cells 90 x 90 images instead of 200 x 200.

Long delays displaying UIImageView loaded from local file?

I see questions regarding long delays in displaying UIImageViews after downloading, but my question involves long delays when
reading from local storage.
After archiving my hierarchy of UIImageViews to a local file (as per narohi's answer in
How to output a view hierarchy & contents to file? ),
I find that if I want to reload them, it takes 5 to 20 seconds for the views to actually appear on screen,
despite my setting setNeedsDiplay() on the main view and all the subviews.
I can immediately query the data contained in the
custom subclasses of UIView that get loaded -- showing that NSKeyedUnarchiver and all the NS-decoding and all the init()'s have completed -- however
the images just don't appear on the screen for a long time. Surely the next redraw cycle is shorter than 5-20 seconds...?
It seems odd that images from PhotoLibrary appear instantly, but anything loaded from local file storage using NSKeyedUnarchiver takes "forever."
What's going on here, and how can I speed this up?
.
.
To be explicit, the relevant part of my Swift code looks like this:
let view = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as! UIView!
if (nil == view) {
return
}
myMainView.addSubview(view)
view.setNeedsDisplay()
// now do things with the data in view ...which all works fine
I find that, even if I add something like...
for subview in view.subviews {
subview.setNeedsDisplay()
}
...it doesn't speed up the operations.
We are not talking huge datasets either, it could be just a single imageview that's being reloaded.
Now, I do also notice these delays occurring when downloading from the internet using a downloader like the one shown in
https://stackoverflow.com/a/28221670/4259243
...but I have the downloader print a completion message after not only the download but when the (synchronous operation)
data.writeToFile() is complete (and before I try to load it using NSKeyedUnarchiver), so this indicates that the delay
in UIImageView redraws is NOT because the download is still commencing....and like I say, you can query the properties of the data and it's all in memory, just not displaying on the screen.
UPDATE: As per comments, I have enclosed the needsDisplay code in dispatch_async as per Leo Dabus's advice, and done some Time Profiling as per Paulw11's. Link to Time Profiling results is here: https://i.imgur.com/sa5qfRM.png I stopped the profiling immediately after the image appeared on the screen at around 1:00, but it was actually 'loaded' during the bump around 20s. During that period it seems like nothing's happening...? The code is literally just waiting around for a while?
Just to be clear how I'm implementing the dispatch_async, see here:
func addViewToMainView(path: String) {
let view = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as! UIView!
if (nil == view) {
return
}
dispatch_async(dispatch_get_main_queue(), {
self.myMainView.addSubview(view)
view.setNeedsDisplay()
self.myMainView.setNeedsDisplay()
})
}
...Since posting this I've found a few posts where people are complaining about how slow NSKeyedUnarchiver is. Could it just be that? If so, :-(.
SECOND UPDATE: Ahh, the "let view = " needs to be in the dispatch_async. In fact, if you just put the whole thing in the dispatch_async, it works beautifully! so...
func addViewToMainView(path: String) {
dispatch_async(dispatch_get_main_queue(), {
let view = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as! UIView!
if (nil == view) {
return
}
self.myMainView.addSubview(view)
view.setNeedsDisplay()
self.myMainView.setNeedsDisplay()
})
}
This works instantly. Wow.. Credit to Leo Dabus. Leaving this here for others...

UITableView with contact images in Monotouch scrolls very slow

I'm developing a contact app for the iPhone using MonoTouch. I am using a custom UITableViewCell, which shows the contact image (ABPerson.Image), contact name and some other info.
I am using the following code when the contact image is be loaded:
//CustomTableViewDataSource class
if (person.HasImage)
customCellController.LoadImage (person.Image);
//Custom cell controller class
public void LoadImage(NSData data)
{
ThreadPool.QueueUserWorkItem (p => this.loadImage (data));
}
private void loadImage(NSData data)
{
UIImage image = UIImage.LoadFromData(data);
InvokeOnMainThread(delegate
{
this.imageView.Image = image;
});
}
This code works fine, but scrolling is way to slow. Does anybody have a better idea to load the contact images?
Thanks,
Danny
There could be a lot of problems with your implementation, the code snippet is not enough to figure out what is wrong.
You can look at the image downloader that is part of MonoTouch.Dialog as it supports this scenario, while loading the images from the network or a local file system cache.
There is an excellent blog on this topic which can be found here.
Which covers this topic - it appears what you're doing is very similar, except your actually loading the images in the customCellController itself. In this example, they're called the LoadImage() and loadImage in the datasource and passing in the cell object, rather than doing the computation in the cell itself. You may find this will help speed up your scrolling.

Resources