Slow UICollectionView with asynchronous loading - ios

I have an UICollectionView which loads images of the iPad's memory and displays them in a grid,like Apple's Photos app. The UICollectionViewCell loads thumbnails asynchronously:
func setImage(img:String){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//load the image in the background
let image = UIImage(contentsOfFile: img)
//when done, assign it to the cell's UIImageView
dispatch_async(dispatch_get_main_queue(), {
if let imageView = self.imageView{
imageView.image = UIImage(contentsOfFile: img)
}
})
})
}
However, while scrolling the view lags as if it is waiting for the images to load, especially with Retina graphics. The cells and images are about 240x180px big. Is there anything wrong with the image loading above or further optimisations need to be made?
UPDATE: Time profiler results

You've already found that you're loading the UIImage again on the main queue; fixing that will help.
UIImage lazy loads its internal image data in most cases. One trick is to call its CGImage property while still on the background queue to force it to actually create its internal image data instead of lazily loading it when the image view is drawn the first time:
func setImage(img:String){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//load the image in the background
let image = UIImage(contentsOfFile: img)
image.CGImage // <- Force UIImage to not lazy load the data
//when done, assign it to the cell's UIImageView
dispatch_async(dispatch_get_main_queue(), {
if let imageView = self.imageView {
imageView.image = image
}
})
})
}
Note: If you have a lot of images you may end up getting a memory warning fairly quickly doing this. If you do, this probably won't help because the memory warning will typically cause UIImage to clear its internal image data again to free up resources.

On the line
imageView.image = UIImage(contentsOfFile: img)
I was loading the image again on the main thread, not the image loaded asynchronously. Changed to
imageView.image = image
Scrolling is a bit better, but yet choppy. The time profiler shows similar results as before. May the bottleneck be in the UIImageView drawing? It works fine with non-retina thumbnails.

The bottleneck was that some thumbnails were scaled improperly and were a lot bigger than the UIImageView, which caused the longer loading time. I also presume this caused slower drawing since the UIImage had to be downscaled to fit in the UIImageView.
I fixed the thumbnail-generating code and scrolling is smooth again.
P.S. I also fixed the misplaced UIImage(contentsOfFile: img) variable in the initial code, see my other answer.

in Swift:
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
let imagem = UIImage(named :"image")
dispatch_async(dispatch_get_main_queue()) {
cell.IMG_Novidades.image = imagem
}
}
//THIS IS VERY IMPORTANT
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale

Related

SceneKit UIImage material is black

I'm loading images from the photo library via UIImagePickerController and with that image I'm setting the material of a SCNPlane:
let imageView = UIImageView(image: image)
imageView.contentMode = .scaleAspectFit
plane.firstMaterial?.diffuse.contents = imageView
With some images, this works fine, no problem at all, the image shows up properly. However, with other images, the texture of the plane shows up as entirely black and Xcode prints "Unsupported IOSurface format: 0x00000000". It seems to be only images that were screenshots causing this, although some screenshot images work just fine.
SceneKit can display both PNGs and JPGs, some PNGs just seem to cause issues. Not sure why or whether it's a bug or not. What you can do that's probably better than converting everything to JPGs (so you can retain transparency) is set your material contents to the image's CGImage:
if let cgImage = image.cgImage {
geometry.firstMaterial?.diffuse.contents = cgImage
}
You can also convert your image to a JPEG via something like this, as SceneKit doesn't seem to have issues with JPGs:
if let jpegData = image.jpegData(compressionQuality: 1.0) {
image = UIImage(data: jpegData)!
}
You will lose transparency doing it this way however.

UIImageView image does not update visibly when image property is set

I have a UIImageView whose user interaction is true and to which I have given a tap gesture recognizer, whose action handler is as follows:
#IBAction func tap(_ sender:UITapGestureRecognizer) {
let iv = sender.view as! UIImageView
let im = iv.image!
let im2 = UIGraphicsImageRenderer(size:im.size).image { _ in
UIColor.red.setFill()
UIBezierPath(rect: CGRect(origin:.zero, size:im.size)).fill()
}
iv.image = im2
}
I expect the image displayed, when I tap the image view, to be replaced by a solid red image. This works fine on my High Sierra machine running Xcode 9.4. But on my Sierra MacBook running Xcode 9.2, nothing visibly happens.
It's weird. By pausing in the debugger, I can see that the new image is being constructed correctly:
The image is being replaced, but the image view isn't being redrawn. Adding calls like setNeedsDisplay does nothing.
Moreover, if I then proceed to replace the image view's image with a different image, I see the red image!
iv.image = im2
delay(0.5) {
iv.image = im // causes im2 to appear!
}
Some sort of behind-the-scenes caching is evidently causing the image view to get behind in its display by one image.
Can anyone shed light on this? It's presumably a bug in iOS itself, and perhaps in 9.2 specifically; how would one work around it? (Obviously one could substitute another image view wholesale, but that wouldn't tell us what's going on with the caching.)
This seems to be a workaround:
iv.image = im2
delay(0.05) {
iv.image = nil
iv.image = im2
}
But what a horror... Omitting any of those assignments, or reducing the delay to zero (e.g. by calling DispatchQueue.main.async instead), causes the workaround to fail.
Encountered this problem in Xcode 13. Set the contentModel to center in the xib file
or
iv.contentMode = .center

the memory of using drawInRect to resize picture

recently i use PHAssets replace the old Assets in my project.However,when i use my app to scale some pictures ,i found it usually crashes.
i use the debug mode, found it is the memory problem.
i use the code below to resize picture
+(UIImage*)scaleRetangleToFitLen:(UIImage *)img sWidth:(float)wid sHeight:(float)hei{
CGSize sb = img.size;;
if (img.size.height/img.size.width > hei/wid) {
sb = CGSizeMake(wid,wid*img.size.height/img.size.width);
}else{
sb = CGSizeMake(img.size.width*hei/img.size.height,hei);
}
if (sb.width > img.size.width || sb.height > img.size.height) {
sb = img.size;
}
UIImage* scaledImage = nil;
UIGraphicsBeginImageContext(sb);
[img drawInRect:CGRectMake(0,0, sb.width, sb.height)];
scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
img = nil;
return scaledImage;
}
the memory will increase about 50M when the code
[img drawInRect:CGRectMake(0,0, sb.width, sb.height)]
runs and it will not be setted free even thought the method is finished.
the width and the height is 304*228 ,the image original size is about 3264*2448,the returned image is 304*228;it means the real image i wanted at last is just a 304*228 size image,however it takes 50+M memory..
Is there any way to free the memory the drawInRect: function takes?
(the #autoreleasepool does not work ~ 😢 😢)
When loading an image, iOS usually doesn't decompress it until it really needs to. So the image you pass into your function is most likely a JPEG or PNG image that iOS keeps in memory in it's compressed state. The moment you draw it, it will be decompressed first and therefore the memory increases significantly. I would expect an increase by 3264 x 2448 x 4 = 35MB (and not 50MB).
To get rid of the memory again, you will make sure you release all reference to the image you pass into your function. So the problem is outside the code you show in your question.
For a more specific answer, you'll need to show all the code that works with the original image.

UIImage slow loading in a UIPageViewController

I have a UIPageViewController showing a UIViewController that contains a UIImageView.
The images displayed in the UIImageView originally come from the camera roll. I copy them in the application's folder. When I display the UIPageViewController, it loads the image for the current page and displays it:
if let image = UIImage(contentsOfFile: imagePath) {
imageView.image = image
}
The problem is that it's really slow. Loading the first image is slow and then swiping is very slow.
I am testing this on my iPhone 5. I installed the app through XCode. Is there a way to make it faster? Should I save a smaller version of the image when I save the images from the camera roll into my application's folder?
You could preload the images like Sergii suggested or load the images a background thread to avoid blocking the main thread.
NSOperationQueue().addOperationWithBlock {
if let image = UIImage(contentsOfFile: imagePath) {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.imageView.image = image
}
}
}
You can load images you'll need before transitioning to UIPageViewController. Also, take a look at some projects for image caches
FastImageCache, SDWebImage, Haneke
Also, you can be interest in this article Avoiding Image Decompression Sickness
To increase the speed of swiping you'd need to move where you are assigning the image to the UIImageView from the main thread to a background thread.
There are a few ways to do this...
Using NSOperationQueue
NSOperationQueue().addOperationWithBlock {
if let image = UIImage(contentsOfFile: imagePath) {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.imageView.image = image
}
}
}
Or you can use GCD and calling dispatch_async()
if let image = UIImage(contentsOfFile: imagePath) {
dispatch_async(dispatch_get_main_queue()) {
imageView.image = image
}
}
With regards to saving an image from the Camera Roll when/if you save it to your apps file system you could try using:
let qualityFloat = 1.0
let imageData = UIImageJPEGRepresentation(imageFromCameraRoll, qualityFloat)
Playing around with qualityFloat (between 0.0 - 1.0) will change the file size of the image which could help you with the loading times.

Memory warning while load image in UIImageView in Swift

I have a viewController with an UIImageView, I change image when slide with Swipe.
While I change some images, I received memory warnings and App crash sometimes. Images have 300KB aprox...
I load images with:
image1.image = UIImage(named: "myImage")!
How can resolve this? In Swift haven't release.. How can do this?
Thanks!
The problem here is the Method,
[UIImage imageNamed: ];//In objective c
image1.image = UIImage(named: "myImage")! //in Swift
This itself is a very memory using method. instead of this use:
init?(contentsOfFile path: String)

Resources