I'm trying to retrieve images from array of url..
I have this function that do the same as I won't but it doesn't work so I tried to use URLSession but didn't know how exactly to make it >>
func downloadImages(imageUrls: [String], completion: @escaping (_ images: [UIImage?]) -> Void) {
var imageArray: [UIImage] = []
var downloadCounter = 0
for link in imageUrls {
let url = NSURL(string: link)
let downloadQueue = DispatchQueue(label: "imageDowmloadQueue")
downloadQueue.sync {
downloadCounter += 1
let data = NSData(contentsOf: url! as URL)
if data != nil {
//image data ready and need to be converted to UIImage
imageArray.append(UIImage(data: data! as Data)!)
if downloadCounter == imageArray.count {
DispatchQueue.main.async {
completion(imageArray)
}
}
} else {
print("couldnt download image")
completion(imageArray)
}
}
}
}
The function I work on :
public func imagesFromURL(urlString: [String],completion: @escaping (_ images: [UIImage?]) -> Void) {
var imageArray: [UIImage] = []
var downloadCounter = 0
let downloadQueue = DispatchQueue(label: "imageDowmloadQueue")
for link in urlString {
downloadQueue.sync {
downloadCounter += 1
let dataTask = URLSession.shared.dataTask(with: NSURL(string: link)! as URL, completionHandler: { (data, response, error ) in
if error != nil {
print(error ?? "No Error")
return
}
if data != nil {
imageArray.append(UIImage(data: data! as Data)!)
if downloadCounter == imageArray.count {
completion(imageArray)
}
} else {
print("couldnt download image")
completion(imageArray)
}
} dataTask.resume()
}
}
}
i want to call the function in the collection cell and get the display the first image only from each artwork array..
//download the first image only to display it:
if artwork.ImgLink != nil && artwork.ImgLink.count > 0 {
downloadImages(imageUrls: [artwork.ImgLink.first!]) { (images) in
self.artworkImage.image = images.first as? UIImage
}
}
UIImage
from an array of urls, you do not design a function trying to download all of them. Instead, try to download from the first url, return the downloaded UIImage
if it succeeds, or continue with the second url if it fails, repeat until you get an UIImage
.DispatchQueue
in a local function looks dangerous to me. A more common practice is to maintain a queue somewhere else and pass it to the function as a parameter, or reuse one of the predefined global queues using DispatchQueue.global(qos:)
if you don't have a specific reason.sync
. sync
blocks the calling thread until your block finishes in the queue. Generally you use async
.Int
counter to control when to finish multiple async tasks (when to call the completion block) works but can be improved by using DispatchGroup
, which handles multiple async tasks in a simple and clear way.Here's two functions. Both work. firstImage(inURLs:completion:)
only return the first UIImage
that it downloads successfully. images(forURLs:completion:)
tries to download and return them all.
func firstImage(inURLs urls: [String], completion: @escaping (UIImage?) -> Void) {
DispatchQueue.global().async {
for urlString in urls {
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
DispatchQueue.main.async {
completion(image)
}
return
}
}
}
DispatchQueue.main.async {
completion(nil)
}
}
}
// Use it.
firstImage(inURLs: artwork.ImgLink) { image in
self.artworkImage.image = image
}
func images(forURLs urls: [String], completion: @escaping ([UIImage?]) -> Void) {
let group = DispatchGroup()
var images: [UIImage?] = .init(repeating: nil, count: urls.count)
for (index, urlString) in urls.enumerated() {
group.enter()
DispatchQueue.global().async {
var image: UIImage?
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
image = UIImage(data: data)
}
}
images[index] = image
group.leave()
}
}
group.notify(queue: .main) {
completion(images)
}
}
// Use it.
images(forURLs: artwork.ImgLink) { images in
self.artworkImage.image = images.first(where: { $0 != nil }) ?? nil
}