I want to use imageView.af_setImageWithURL(URL) together with the AutoPurgingImageCache from AlamofireImage.
How can I be informed that af_setImageWithURL fetched the image, so I can put it into my cache? Because this is done in Background.
I'd prefer something like: imageView.af_imageFromCache(URL) which sets the image from cache if it is already in there or otherwise downloads async, then sets the image and saves it in the cache.
The shared ImageDownloader that the UIImageView uses already puts the image into the AutoPurgingImageCache automatically. Every time you use the imageView.af_setImageWithURL(URL) it will attempt to pull the image out of the image cache if it exists. If the image is not already in the cache, it will start the download on the ImageDownloader instance.
SWIFT 4.0
Set 'AutoPurgingImageCache' in Appdelegate so that i can access it in any class
var imageCache:AutoPurgingImageCache?
imageCache = AutoPurgingImageCache(
memoryCapacity: 100_000_000,
preferredMemoryUsageAfterPurge: 60_000_000
)
Then set image in my service class
class func setImage(url : String,imageview:UIImageView) {
let imageCache = appDelegate.imageCache!
// Fetch
if let cachedAvatar = imageCache.image(withIdentifier: url){
imageview.image = cachedAvatar
}else {
Alamofire.request(url).responseImage { response in
debugPrint(response)
debugPrint(response.result)
if let image = response.result.value {
imageview.image = image
// Add
imageCache.add(image, withIdentifier: url)
}
}
}
}
Related
Earlier used like below.,
var imagesURL: [String] = [] {
didSet {
guard !imagesURL.isEmpty else { return }
let urls = imagesURL.map { URL(string: $0)! }
backgroundImageView.animationDuration = 5.0
backgroundImageView.sd_setAnimationImages(with: urls)
}
}
As per docs [https://github.com/SDWebImage/SDWebImage/wiki/5.0-Migration-guide][1] we can use SDWebImagePrefetcher with memory cache to grab the batch loaded images
I tried SDWebImagePrefetcher, unable to find way to download all images at once.
we can set all animationImages on imageView.
After reading a number of answers on SO and other articles (see below) what is the best method to manage memory when you are loading multiple images into an animation array?
http://www.alexcurylo.com/2009/01/13/imagenamed-is-evil/
Here are my goals:
Create Animation Array (see code below)
After the Animation plays flush the cache and load another animation (rinse, wash, repeat, you get it :)
As Apple states
https://forums.developer.apple.com/thread/17888
Using the UIImage(named: imageName) caches the images, but in my case after playing 2-3 animations in a row iOS terminates the App (the OS does not respond to a low memory warning and instead terminates the App - see end of code below)
I don't want to cache the images and rather we could either:
Flush the memory each time an animation completes and then load a new animation; or
Flush the memory when the user moves to a new Scene
Here is my code:
// create the Animation Array for each animation
func createImageArray(total: Int, imagePrefix: String) -> [UIImage]{
var imageArray: [UIImage] = []
for imageCount in 0..<total {
let imageName = "\(imagePrefix)-\(imageCount).png"
let image = UIImage(named: imageName)! // here is where we need to address memory and not cache the images
//let image = UIImage(contentsOfFile: imageName)! // maybe this?
imageArray.append(image)
}
return imageArray
}
// here we set the animate function values
func animate(imageView: UIImageView, images: [UIImage]){
imageView.animationImages = images
imageView.animationDuration = 1.5
imageView.animationRepeatCount = 1
imageView.startAnimating()
}
// Here we call the animate function
animation1 = createImageArray(total: 28, imagePrefix: "ImageSet1")
animation2 = createImageArray(total: 53, imagePrefix: "ImageSet2")
animation3 = createImageArray(total: 25, imagePrefix: "ImageSet3")
func showAnimation() {
UIView.animate(withDuration: 1, animations: {
animate(imageView: self.animationView, images: self.animation1)
}, completion: { (true) in
//self.animationView.image = nil // Maybe we try the following?
//self.animationView.removeFromSuperview()
//self.animationView = nil
})
}
Based on SO responses, it looks like this may be the best method to prevent the images from being cached, but it doesn't seem to work in my code:
let image = UIImage(contentsOfFile: imageName)!
I have also tried this but it doesn't seem to work either:
func applicationDidReceiveMemoryWarning(application: UIApplication) {
NSURLCache.sharedURLCache().removeAllCachedResponses()
}
I also tried the following article (removeFromSuperview) in the completion block but I couldn't get this to work either (see my code above):
https://www.hackingwithswift.com/example-code/uikit/how-to-animate-views-using-animatewithduration
New Code:
// create the Animation Array for each animation
func createImageArray(total: Int, imagePrefix: String) -> [UIImage]{
var imageArray: [UIImage] = []
for imageCount in 0..<total {
let imageName = "\(imagePrefix)-\(imageCount).png"
//let image = UIImage(named: imageName)! // replaced with your code below
if let imagePath = Bundle.mainBundle.path(forResource: "ImageSet1" ofType: "png"),
let image = UIImage(contentsOfFile: imagePath) {
//Your image has been loaded }
imageArray.append(image)
}
return imageArray }
It's pretty simple. UIImage(named:) caches images, and UIImage(contentsOfFile:) does not.
If you don't want your images to be cached, use UIImage(contentsOfFile:) instead. If you can't get that to work then post your code and we'll help you debug it.
Be aware that UIImage(contentsOfFile:) does not look for files in your app bundle. It expects a full path to the image file. You will need to use Bundle methods to find the path to the file and then pass that path to UIImage(contentsOfFile:):
if let imagePath = Bundle.mainBundle.path(forResource: "ImageSet1" ofType: "png"),
let image = UIImage(contentsOfFile: imagePath) {
//Your image has been loaded
}
Your code is loading all the images for all 3 animations into an array of images and never releasing them, so the fact that the system caches those images seems pretty irrelevant. In a low memory condition the system should flush the cached copies of the images, but your code will still hold all those images in your arrays so the memory won't get freed. It looks to me like it's your code that's causing the memory problem, not the system's images caching.
Your code might look like this:
func createImageArray(total: Int, imagePrefix: String) -> [UIImage]{
var imageArray: [UIImage] = []
for imageCount in 0..<total {
let imageName = "\(imagePrefix)-\(imageCount)"
if let imagePath = Bundle.main.path(forResource: imageName,
ofType: "png"),
let image = UIImage(contentsOfFile: imagePath) {
imageArray.append(image)
}
}
return imageArray
}
I have set a placeholder image for my image views. The user can change these to a photo from their library. Once they have done this, they can then upload these images to a database. The user can upload a single image or many, either way, they are uploaded as an [UIImage]. However, I do not want the placeholder images to be uploaded.
I have managed to achieve this, but in a very ungraceful manner. I have done this by firstly subclassing UIImageView, adding a property called isSet and setting all my image views to this class:
class ItemImageViewClass: UIImageView {
//keeps track of whether the image has changed from the placeholder image.
var isSet = Bool()
}
and then when the image has been set after the user has selected an image, the isSet property is set to true.
To check if the image in the image view has been changed from the placeholder image (i.e. isSet == true) I used the following code:
var imageArray: [UIImage]? = nil
if imageViewOne.isSet {
guard let mainImage = imageViewOne.image else {return}
if (imageArray?.append(mainImage)) == nil {
imageArray = [mainImage]
}
}
if imageViewTwo.isSet {
guard let imageTwo = imageViewTwo.image else {return}
if (imageArray?.append(imageTwo)) == nil {
imageArray = [imageTwo]
}
}
guard imageArray != nil else {
print("imageArray is nil")
alertMessage("Hey!", message: "Please add some images!")
return
}
When at least one image has been selected, the array will be saved to the database.
This seems like a very messy way to do it; subclassing UIImageView and having to check that each image has changed using a series of if statements. Is there a more elegant way to achieve this? Thanks
There is a solution by extending UIImageView to avoid subclassing. And, using filter and flatMap to by pass the if-statements. This requires that you use the same reference to the placeholder image for each UIImageView.
// In this example, the placeholder image is global but it doesn't have to be.
let placeHolderImage = UIImage()
extension UIImageView {
// Check to see if the image is the same as our placeholder
func isPlaceholderImage(_ placeHolderImage: UIImage = placeHolderImage) -> Bool {
return image == placeHolderImage
}
}
Usage:
let imageViewOne = UIImageView(image: placeHolderImage)
imageViewOne.isPlaceholderImage() // true
let imageViewTwo = UIImageView()
imageViewTwo.isPlaceholderImage() // false
let imageViewThree = UIImageView()
imageViewThree.image = UIImage(named: "my-image")
imageViewThree.isPlaceholderImage() // false
Filter and map for images that are not placeholders or nil:
let imageArray = [imageViewOne, imageViewTwo, imageViewThree].filter
{ !$0.isPlaceholderImage() }.flatMap { $0.image }
print(imageArray) // imageViewThree.image
If the extension does not work for your requirements, I would definitely consider "filter and flatMap" to avoid that if-statement.
I would recommend creating a new UI component for your needs with a background UIImageView(placeholder) and an optional foreground UIImageView (user selected). Then just check for the existence of the optional foreground UIImageView as you can rely upon this being the UIImageView containing the user chosen image.
I'm using Alamofire to fetch data from server and then put them in an array of CarType objects which CarType is my struct. what I get from server is name , id and iconUrl. from iconUrls i want to download icons and put them in icon. after that I'll use icon and name in a collection view. my Alamofire request is:
var info = [CarType]()
Alamofire.request(.GET,"url")
.responseJSON { response in
for (_,subJson):(String, JSON) in json["result"]
{
let name = subJson["name"].string
let iconUrl = subJson["icon"].string
let id = subJson["id"].int
info.append(CarType(id: id!, name: name!, iconUrl: iconUrl! , image: UIImage()))
}
my struct is:
import Foundation
import UIKit
struct CarType {
var name : String
var id : Int
var iconUrl : String
var icon : UIImage
}
I want to download images before using them in collectionView.
How can i download images (using AlamofireImage) and put them in related carType icon property?
What you are asking is really bad practice in mobile app. Just a case, for example, you made a request and got like 20 items in an array, and in order to put all UIImages in your models, you have to make 20 more requests, also you even don't know if your users will eventually use (view) those icons or no.
Instead, you could fetch the images when the cell (I guess, you will be displaying those icons in a cell) is displayed, for this purpose you could use libs like SDWebImage(objective c) or Kingfisher(swift) which have extensions for UIImageView to make it simple to fetch and display image on. Those libs can also cache the downloaded image.
Also, another suggestion for object mapping. Currently, you are mapping json to your model manually. There are a lot of good libs to handle that for you, which could automate your object mapping process, for example - ObjectMapper
Hope, this was helpful. Good Luck!
I have done functionalities thing in UITableview, added following in CellForRowIndex method:
getDataFromUrl(urlString){(data,response,error) -> Void in
if error == nil {
// Convert the downloaded data in to a UIImage object
let image = UIImage(data: data!)
// Store the image in to our cache
if((image) != nil){
// Store the image in to our cache
self.imageCacheProfile[urlString] = image
// Update the cell
DispatchQueue.main.async(execute: {
cell.imgvwProfile?.image = image
})
}
else{
cell.imgvwProfile!.image = UIImage(named: "user")
}
}
}
func getDataFromUrl(_ strUrl:String, completion: #escaping ((_ data: Data?, _ response: URLResponse?, _ error: NSError? ) -> Void)) {
let url:URL = URL(string: strUrl)!
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {data, response, err in
print("Entered the completionHandler")
}.resume()
}
You also need to declare imageCache to store downloaded images.
var imageCache = [String:UIImage]()
You can use above code in your method and it should work just.
[UPDATE]
I have added actual code snippet in order to make my question clear.
Say we want to store uiimages into an array, which are fetched from the internet.
I have this code snippet:
// Somewhere in a loop
{
var story = Story()
story.imgUrl = "http:\(imgUrl)"
/// Donwload image, and replace in the top
if let imgUrl = story.imgUrl {
if let url = NSURL(string: imgUrl) {
let request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response, data, error) -> Void in
if let data = data {
story.image = UIImage(data: data)
var i = 0
for a in self.Stories {
print("iv image \(i++) is \(a.image)")
}
print("Received image for story as \(story.image) into story \(story)")
// Should one set the image view?
if let _ = self.imageView {
if let indexPath = self.tableView?.indexPathForSelectedRow {
if stories.count == indexPath.section { // is that the currently selected section?
self.imageView?.image = story.image
self.imageView?.hidden = false
print("Set imageView withstory \(story.tag.string)")
}
}
}
}
}
}
}
stories.append(story)
}
/// Switch data source
self.Stories = stories
This doesn't store the image property value into the destination array.
Though the image is ok in the block, if I iterate through the destination array, my image is nil
image: Optional(<UIImage: 0x7ff14b6e4b20> size {100, 67} orientation 0 scale 1.000000))
iv image 0 is nil
How to achieve above functionality?
[INITIAL QUESTION]
Say we want to store element i.e UIImages which i have fetched from the internet. I have this code snippet:
var array = []
let imageView = UIImageView(image:nil)
array.append(imageView)
// and later or, in an synch block
imageView.image = image
This doesn't store the image property value in the array.
How could I do that?
Gotcha!
The point is that Story was defined as struct. And structs are passed by values unlike classes.
To make the code working, I just changed from struct Story {} to class Story {}, and voila!