SDWebImage Prefetching with completion block - ios

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!")
})

Related

How to quickly open a view controller without executing it's processes?

I have to show various folders which contain a heavy amount of images and videos , but when I click on the folder it takes few seconds to load which seems like it's lagging or hanging ...
I wanna open view controller first and then show the process of loading....
How to do that ???
you can load data in background like this
DispatchQueue.global(qos: .background).async {
// load data here
}
after that need to load UI in main thread
DispatchQueue.main.sync {
// show data here
}
you can solve this problem by using SDWebImage for problem of photos or images loading...
myimageview.sd_setImage(with: imageURL, placeholderImage: UIImage(named: "empty_image_icon"))
//imageURL is URL of image.
//empty_image_icon id the default image which will show during process.
Fetch data in viewWillAppear() method not in viewDidLoad() method.
You can fetch your data/media in viewDidAppear() method and start animating your loading indicator in viewDidLoad() method.
If you load data in viewWillAppear() than your viewController take a time with few second.
You can call your fetching data function after your view created. The viewDidAppear() method is working after your view created but also, this method works whenever the view you create is visible on the screen so it could not be the best solution for every case. It means that, your data could be fetching unnecessarily. For example, viewDidAppear() method is working whenever you change your view controller and come back that screen again or switch your application or your app comes foreground.
Therefore, using dispatchQueue or SDWebImage library could be your answer.

Update UIView before calling other function?

I am working in Swift. When a user presses a UIButton it calls a function ButtonPressed(). I would like ButtonPressed() to do two things:
Update the UIView by removing the current buttons and texts, then uploading some new text.
Call function TimeConsumingCalculation(). TimeConsumingCalculation is the complicated part of my app and does some calculations which take about 20 seconds or so to complete.
Right now, I have the code in the basic order:
ButtonPressed(){
self.Button.removeFromSuperview()
TimeConsumingCalculation()
}
However, it will not remove the button or do any other UI updates or additions until after the TimeConsumingCalculation is complete. I have read and attempted a few guides on closures and asynchronous functions, but have had no luck. Is there a special property with UIView that is causing it to be updated last?
As a side note - I have already attempted putting all UI actions in a separate function and calling it first. It doesn't work. The time consuming function does not take any variables from the buttons or UI or anything like that.
Thanks!
It seems like timeConsumingCalculation() is blocking the main queue, which is in charge of UI updates. Try calling it like this instead and use the isHidden property to hide the button instead of removing it from the view completely.
ButtonPressed(){
self.Button.isHidden = true
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async {
self.timeConsumingCalculation()
}
}
here you call timeConsumingCalculation() asynchronously on a background thread. The quality of service we give it is userInitiated, read more about quality of service classes here

display downloaded images in tableview

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.

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...

Is there any way to force async NSURLConnection to call connectionDidFinishLoading in the background?

I download images in the background that I show inside a UITableView cell.
images are not shown until UITableView stops scrolling.
I want to show the downloaded image in a quicker way. For example, I would like to do this while UITableView begins decelerating.
To do this, I need to know when the download is finished and I can't know it until connectionDidFinishLoading is called. This method is not called while UITableView is moving. (or probably while the interface is busy)
Is it possible to somehow make it call connectionDidFinishLoading while UI is busy, without synchronously using NSURLConnection?
Here's what you do:
Have your net connection update objects in Core Data.
Setup an NSFetchedResultsController somewhere around your UITableView or Controller to query for objects of that type, e.g. images collection on an article object.
Bind the UiTableView datasource to the NSFetchedResultsController - i.e. sections and rows of the table come from the results.
At this point, the table view should successfully show any old/current items as you scroll. Now, ...
When your download completes (maybe an image associated with the e.g. article), update the object in CoreData somehow -- for e.g. simply inserting image into a collection on articles called images -- something that the results controller will "see" based on the initial predicate you setup. If the indexPath of the new object (article, image, whatever) is currently viewable in the tableView, the tableView and results controller will hash it out for you.
You should actually be done at this point, and the new images may just show up. The results controller is KVO observing for you. You do need to take action with the results controller gets changes, but there are plenty of examples out there, like this (and there's much much variance between the approaches). If not, you likely need to do something innocuous - e.g. for a scroll to top when new articles appear; avoid calling reloadData unless you absolutely have to.
Update
If you are just trying to enforce NSURLConnection to operate synchronously or asynchronously, then all you have to do is setup the NSURLConnection using + (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id < NSURLConnectionDelegate >)delegate (or the related initializers) and - (void)start instead of + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error. The first method loads data asynchronously, while the latter is synchronous.
However, since you mention connectionDidFinishLoading, it seems you are setting up the connection asynchronously, no?
A simple way to make a synchronous NSURLConnection run in the background is using GCD.
dispatch_async(dispatch_get_global_queue(0, 0), ^
{
// Do your synchronous NSURLConnection and handle the response
dispatch_async(dispatch_get_main_queue(), ^{
// update UI
});
});
You can also use SDWebImage that handles image loading nicely.
https://github.com/rs/SDWebImage

Resources