Swift cache base64 string UIImage data - ios

I'm trying to cache UIImage that images come from service(I'm using Alamofire). Service sends me a base64 string and I'm converting base64 to data then print in tableviewcell with
cell.imageview.image = UIImage(data: imageDatas[indexPath.row])
I searched lots of libraries like Kingfisher , AlamofireImage but they are caching URL image can't find anyway to cache image with base64 string.So I find a similar example and try this :
private let cache = NSCache<NSNumber, UIImage>()
private let utilityQueue = DispatchQueue.global(qos: .utility)
private func loadImage(data : Data , completion: #escaping (UIImage?) -> ()) {
utilityQueue.async {
let image = imageDataDecodingClass.imageDataDecoding(imageData: data)
DispatchQueue.main.async {
completion(image)
}
}
}
in cell :
let itemNumber = indexPath.section * 2 + 1
let imageData = (showcaseDatas[indexPath.section * 2 + 1].Document?.Document!)!
if let cachedImage = self.cache.object(forKey: NSNumber(value: itemNumber)) {
cell.showcaseImage.image = cachedImage
} else {
cell.addSubview(progressHUDimage)
self.loadImage(data: imageData, completion: {ret in
cell.showcaseImage.image = ret
self.cache.setObject(cell.showcaseImage.image!, forKey: NSNumber(value: itemNumber))
progressHUDimage.removeFromSuperview()
})
}
Its caching image perfectly but when I scroll tableview ,CPU increased a lot (%70-90) thats why tableview is not smoothing.
So , my question is , How can I cache base64 string image in tableviewcell with smoothing and without CPU increased ? Thanks

If you are caching base64 String or Data there will be always a hit in decoding them as an image. The only way it would be caching the UIImage itself, but this will come with trade off of memory depending on the size of your images.
I can only give you few advices:
Use NSCache (it seems that you are already using it)
Resize your images on another thread to have a perfect fit on the size that you are rendering on screen and cache them only after they have been resized
Be sure that the performance hit you are seeing is not due to other reasons
Cache images by using their index path if you are using for cells
Create your NSCache with a memory limit and a number of element limit
You can also try to create 2 caches, one for ready to go already decode images and the other one as a fallback with Data object.
The fact that your Data objects are small it doesn't means that also images are, it really depends on the compression that has been used to save the image representation.
There are also more advance technique using memory mapping on physical memory.

Related

Does showing system image(SF symbols) use a networking call in Swift?

I'm creating an application in ios where I load images from an api using a UITableView and UITableViewCell.
Since the UITableView reuses cells, old images were appearing when I scroll fast. In order to prevent this, I set a default image using a system image(SF symbols).
I also use a cache to store urls to images.
Everything works as it should but now I think of it I'm sending a network request to retrieve that systemImage each time which seems incredibly inefficient since I was using a cache in order to reduce the total network calls in the first place.
Is there way around this or is this a tradeoff I must make?
Code is below.
//use default image from SF symbols
let defaulticon = UIImage(systemName: "photo")?.withTintColor(.gray, renderingMode: .alwaysOriginal)
DispatchQueue.main.async {
cell.mealImage.image = defaulticon
}
guard cell.meal?.strMealThumb != nil else {
print("Category Image doesn't exist")
return
}
//use cache
if let imageData = model.imagecache.object(forKey: cell.meal!.strMealThumb as NSString) {
print("using cache")
DispatchQueue.main.async {
cell.mealImage.image = imageData
}
}
else {
let url = URL(string: cell.meal!.strMealThumb)
let session = URLSession.shared.dataTask(with: url!) { data, response, error in
if error == nil && data != nil {
let image = UIImage(data: data!)
//self.model.imagecache[cell.meal!.strMealThumb] = image
self.model.imagecache.setObject(image!, forKey: cell.meal!.strMealThumb as NSString)
DispatchQueue.main.async {
cell.mealImage.image = image
}
}
}
session.resume()
}
}
Override prepareForReuse method in UITableViewCell and add code in this function to clean up unrequited data that could persist from previous usage of the cell. In your example assign the default image in this function to produce better result.
You asked:
I set a default image using a system image(SF symbols).
...
Everything works as it should but now I think of it I'm sending a network request to retrieve that systemImage each time which seems incredibly inefficient since I was using a cache in order to reduce the total network calls in the first place.
No, UIImage(systemName:) does not make a network request. And it caches the image, itself, as the documentation says:
This method checks the system caches for an image with the specified name and returns the variant of that image that is best suited for the main screen. If a matching image object is not already in the cache, this method creates the image from the specified system symbol image. The system may purge cached image data at any time to free up memory. Purging occurs only for images that are in the cache but are not currently being used.
FWIW, you can empirically verify that this does not perform a network request disconnecting from the network and trying to use it. You will see it works fine, even when disconnected.
FWIW, there is a very small performance gain (less than a millisecond?) by keeping a reference to that tinted system image and reusing it, rather than fetching the cached system image and re-tinting it. But the performance improvement is negligible.

How to upload an image with Alamofire inside parameters

There's a lot of similar questions on here, but i couldn't find an answer that's help me in solving this problem.
What I want to do is upload an image with Alamofire with parameters
and the image itself should be part of the parameters.
For example the parameters should be like this:
["user_id": 1, "picture": 'and here will be the image that i want to upload']
in the response i will be getting the image as a link like this:
"/uploads/members/'user_id'/images/member_'user_id'/28121284ase2.jpg"
something similar to that.
Also the image should be ' jpg, png and not more than 5MB '.
I'm new to uploading images w\ API so I don't what to do exactly.
I tried every solution I could find in here and google but no luck so far. Any help would be appreciated.
I'm going to assume that you have an instance of UIImage to start with, you'll have to encode it to base64 and send it to your backend as you would any other String parameter. This should help :
extension UIImage {
func toBase64() -> String? {
guard let imageRectoData = jpeg(.low) else {
return nil
}
return imageRectoData.base64EncodedString()
}
enum JPEGQuality: CGFloat {
case lowest = 0
case low = 0.25
case medium = 0.5
case high = 0.75
case highest = 1
}
/// Returns the data for the specified image in JPEG format.
/// If the image object’s underlying image data has been purged, calling this function forces that data to be reloaded into memory.
/// - returns: A data object containing the JPEG data, or nil if there was a problem generating the data. This function may return nil if the image has no data or if the underlying CGImageRef contains data in an unsupported bitmap format.
func jpeg(_ jpegQuality: JPEGQuality) -> Data? {
return jpegData(compressionQuality: jpegQuality.rawValue)
}
}
You can do that by using multipart/form-data request.
your question is relative to this one: Upload image with multipart form-data iOS in Swift

Alamofire .dowload takes a lot memory (ios, swift)

i downloaded images from the server with alamofire framework. I worked with Alamofire.download . Each image has size above 1MB +-, but after each request memory increase a lot. After downloading 4 images the memory used is above 171MB and after that each image give more than 35MB.
Downloading code is:
Alamofire.download(mainReguest, to: self.destination)
.downloadProgress{ progress in
self.progressView.progress = Float(progress.fractionCompleted)
}
.response{ response in
if response.error == nil, let imagePath = response.destinationURL?.path {
let image = UIImage(contentsOfFile: imagePath)
self.addNewImageToTheScrollView(img: image)
}
}
}
Code with destination is:
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory,
in: .userDomainMask,
with: [DownloadRequest.DownloadOptions.removePreviousFile])
The problem is with this...
UIImage(contentsOfFile: imagePath)
Specifically with the fact that you are (presumably) downloading a compressed format of your image.
However, UIImage is uncompressed in memory. So each pixel of your image will take 4 bytes of information (red, green, blue, alpha).
So if you have an image that is (for example) 5 mega pixels.
Then 5,000,000 pixels * 4b = 20MB.
So, I imagine your images are around 10MP?
The best way around this is to optimise your image download. If you're displaying an image on an iPhone then there is no point downloading a 10MP version of it. You might as well resize it to be much much smaller.
Ideally, you should be resizing the images on the backend before the download happens.
Your Alamofire's code is ok. Most likely you have issues somewhere in UI part, for example because of the high image resolution. The first thing you have to do is to localise an issue. To check networking code please comment all UI related code and run your app one more time.
Alamofire.download(mainReguest, to: self.destination)
.downloadProgress{ progress in
// self.progressView.progress = Float(progress.fractionCompleted)
}
.response{ response in
if response.error == nil, let imagePath = response.destinationURL?.path {
let image = UIImage(contentsOfFile: imagePath)
print("Image is successfully downloaded!")
//self.addNewImageToTheScrollView(img: image)
}
}
}

iOS image loader effect

I am using "SVProgressHUD" for loader. When I want to load an image from url I am using async process to load the image in background. After completing the download I am updating the UI. And for that time span I am using a placeholder on the imageview and a loader with "Please wait..". But I want to use a loader like "Instagram". That means, when an image is loading from online it loads a blurry image of the original image and upon downloading the image , the blurry image show the original image.
Can any one please suggest me how can I do this?
The stuff you are talking about is Progressive JPEG.
Progressive JPEG (PJPEG) is an image format that stores multiple,
individual “scans” of a photo, each with an increasing level of
detail. When put together, the scans create a full-quality image. The
first scan gives a very low-quality representation of the image, and
each following scan further increases the level of detail and quality.
When images are downloaded using PJPEG, we can render the image as
soon as we have the first scan. As later scans come through, we update
the image and re-render it at higher and higher quality.
So, you have to make images loadable in this modern progressive format.You can follow a approach where every image before saving on server just convert it into appropriate progressive format.There are several tools avaiable for different type of server. E.g jpegtran
To check whether image is progressive or not you can use this tool or tool2.You can search for online tool a lot of tool is available.
Now for iOS
You can follow this tutorial
Some library
Concorde
DFImageManager
Assuming you are in control of the hosting of the images you can host lower resolution images and make 2 fetches, one for the lower resolution, and one for the higher. If you make the lower resolution image significantly smaller in file size the fetch for it will finish before the fetch for the full image, and you populate the UIImageView with this image until such a time as you can replace it.
Purely example code would look like this:
let imageView = UIImageView()
let lowResOperation = NSBlockOperation {
guard let imageData = NSData(contentsOfURL: NSURL(string: "https://myhost.org/low-res-image")!),
image = UIImage(data: imageData) else { return }
if imageView.image == nil {
imageView.image = image
}
}
let highResOperation = NSBlockOperation {
guard let imageData = NSData(contentsOfURL: NSURL(string: "https://myhost.org/high-res-image")!),
image = UIImage(data: imageData) else { return }
imageView.image = image
}
let backgroundQueue = NSOperationQueue()
backgroundQueue.addOperations([lowResOperation, highResOperation], waitUntilFinished: true)

UIImage from url takes long time to load swift

I have a UITableView with A lot of UITableViewCell, I use this code to load content and images to the UITableCell but it takes long time for the image to show for each UITableViewCell.
var loadOnce : [String] = []
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("ProfileActivities", forIndexPath: indexPath) as! ProfileActivitiesTableViewCell
if !loadOnce.contains("\(indexPath.row)") {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let LocationImage = self.postmap[indexPath.row]
let LocationImageUrl = NSURL(string: LocationImage)
let LocationImageData = NSData(contentsOfURL: LocationImageUrl!)
let ProfileImage = self.postimg[indexPath.row]
let ProfileImageUrl = NSURL(string: ProfileImage)
let ProfileImageData = NSData(contentsOfURL: ProfileImageUrl!)
dispatch_async(dispatch_get_main_queue(), {
cell.locationImg.image = UIImage(data: LocationImageData!)
cell.activitesProfileImg.image = UIImage(data: ProfileImageData!)
});
});
loadOnce.append("\(indexPath.row)")
}
return cell
}
Putting the code in dispatch_async made scrolling smother but its still takes time to load images.
how to make it load images faster considering I have to many images something like Facebook or Instagram.
Thanks.
There will be issue because of Async call and ReUsability. Lets say your Image at IndexPath 1 is loading image but it is still taking time to download and meanwhile user scrolled down at some another index i.e. 15 and Internally the same cell of Index 1 is alloted to Index 15 and Image of cell 1 will be displayed in Cell 15 if you have not managed to handle previous calls for same instance of cell. Thats why its Better to use some caching library like SDWebImage(As #SeanLintern88 said) or AFNetworking's UIImageView Category
AlamoFire's UIImageView (For Swift)
Event if you wanted to do this, then better is to go with subclassing, it will help you to manage the Calls and Its termination.
In your case the Loading of Image might be because of Size of actual image that you are loading from server. CDN kind of service provide some easy to implement techniques to serve different size of images as per the request. So even If User has uploaded 1200x1200 sized image but on list screen you can fetch its 120x120 sized image, This helps is faster loading as well as it will save Physical Memories.
Use one of the following libraries to asynchronously load images to you view :
1) Asyncimageview https://github.com/nicklockwood/AsyncImageView
2) SDWebImage https://github.com/rs/SDWebImage
It sounds like your image sizes could be large as the code looks fine, usually apps like FB/Insta would use low res renditions and then load in higher res after/when needed.
Most people use an image fetching library such as SDWebImage as this will async fetch the images and cache them so you don't have to save them on your model and consume the memory.
https://github.com/rs/SDWebImage

Resources