514

I'd like to load an image from a URL in my application, so I first tried with Objective-C and it worked, however, with Swift, I've a compilation error:

'imageWithData' is unavailable: use object construction 'UIImage(data:)'

My function:

@IBOutlet var imageView : UIImageView

override func viewDidLoad() {
    super.viewDidLoad()

    var url:NSURL = NSURL.URLWithString("http://myURL/ios8.png")
    var data:NSData = NSData.dataWithContentsOfURL(url, options: nil, error: nil)

    imageView.image = UIImage.imageWithData(data)// Error here
}

In Objective-C:

- (void)viewDidLoad {
    [super viewDidLoad];

    NSURL *url = [NSURL URLWithString:(@"http://myURL/ios8.png")];
    NSData *data = [NSData dataWithContentsOfURL:url];

    _imageView.image = [UIImage imageWithData: data];
    _labelURL.text = @"http://www.quentinroussat.fr/assets/img/iOS%20icon's%20Style/ios8.png";
 }

Can someone please explain me why the imageWithData: doesn't work with Swift, and how can I solve the problem.

Mick MacCallum
  • 129,200
  • 40
  • 280
  • 281
QuentR
  • 5,227
  • 3
  • 13
  • 12
  • 4
    Try this `imageURL.image = UIImage(data: myDataVar)` – aleclarson Jun 15 '14 at 16:37
  • Perfect it worked ! Thank you However I don't why this method work in Objective C, and not in Swift... Strange – QuentR Jun 15 '14 at 16:40
  • When you have trouble with a Cocoa class, try CMD+clicking the class name and you should be able to see the Swift interface for the class! – aleclarson Jun 15 '14 at 16:43
  • 8
    if let url = NSURL(string: "imageurl") { if let data = NSData(contentsOfURL: url) { imageView.image = UIImage(data: data) } } – Hardik Bar Oct 15 '15 at 10:04
  • @LeoDabus That's fine. It's been my understanding that the "swift" tag will always refer to the current version. The tags that specify a version are used for questions related to a version specific language feature. It's not definitive, but check out [this meta post](http://meta.stackoverflow.com/questions/302314/should-swift2-be-swift-too). – Mick MacCallum Apr 02 '16 at 17:35
  • @LeoDabus I'm not concerned with this specific question. I just saw it in the top voted swift2 section, and thought I remembered editing it before. And you're correct. My intention in this section was to start auditing multiple posts, which I'll begin shortly. – Mick MacCallum Apr 02 '16 at 17:52

39 Answers39

905

Xcode 8 or later • Swift 3 or later

Synchronously:

if let filePath = Bundle.main.path(forResource: "imageName", ofType: "jpg"), let image = UIImage(contentsOfFile: filePath) {
    imageView.contentMode = .scaleAspectFit
    imageView.image = image
}

Asynchronously:

Create a method with a completion handler to get the image data from your url

func getData(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
    URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}

Create a method to download the image (start the task)

func downloadImage(from url: URL) {
    print("Download Started")
    getData(from: url) { data, response, error in
        guard let data = data, error == nil else { return }
        print(response?.suggestedFilename ?? url.lastPathComponent)
        print("Download Finished")
        // always update the UI from the main thread
        DispatchQueue.main.async() { [weak self] in
            self?.imageView.image = UIImage(data: data)
        }
    }
}

Usage:

override func viewDidLoad() {
    super.viewDidLoad()
    print("Begin of code")
    let url = URL(string: "https://cdn.arstechnica.net/wp-content/uploads/2018/06/macOS-Mojave-Dynamic-Wallpaper-transition.jpg")! 
    downloadImage(from: url)
    print("End of code. The image will continue downloading in the background and it will be loaded when it ends.")
}

Extension:

extension UIImageView {
    func downloaded(from url: URL, contentMode mode: ContentMode = .scaleAspectFit) {
        contentMode = mode
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard
                let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
                let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
                let data = data, error == nil,
                let image = UIImage(data: data)
                else { return }
            DispatchQueue.main.async() { [weak self] in
                self?.image = image
            }
        }.resume()
    }
    func downloaded(from link: String, contentMode mode: ContentMode = .scaleAspectFit) { 
        guard let url = URL(string: link) else { return }
        downloaded(from: url, contentMode: mode)
    }
}

Usage:

imageView.downloaded(from: "https://cdn.arstechnica.net/wp-content/uploads/2018/06/macOS-Mojave-Dynamic-Wallpaper-transition.jpg")
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • I'm curious, why do you do `completion(data: NSData(data: data))` instead of just `completion(data: data)`? – Jase Jun 04 '15 at 14:39
  • @Jase there is no need. Just data it is fine – Leo Dabus Jun 04 '15 at 16:36
  • 22
    Just a side note here, you should set an associated object to it; Otherwise you might load images on top of each other. E.g. in a `UITableView` where a cell shows an image and the image is updated when the dequeued cell is returned. If process #1 takes longer then process #2, process #2 will show its image and it will then later be updated by process #1 even though that image is no longer valid for the user. – Paul Peelen Aug 02 '15 at 22:25
  • I think that it's necessary to return operation from the method: getDataFromUrl. It gives opportunity to cancel scheduled operation. – Siarhei Yakushevich Sep 11 '15 at 10:00
  • Is this supposed to work if there is no image? For instance If I give it a url which returns an (s3 no such key error), it simply replaces my image with nothing. I think the data in this case is the error report. Is there a way to additionally check that the data returned is an image file? – BigBoy1337 Oct 16 '15 at 20:10
  • how to use last function in button? – Nasir Khan Dec 10 '15 at 10:46
  • hi, I'm using your extension to implement, and I think my image is not displaying because my UIImage is inside a tableview, and i think i need to refresh the table. Do you know where and how should i implement this refresh? – jo3birdtalk Feb 07 '16 at 03:42
  • @jo3birdtalk you are not using my extension, you are using NSURLConnection. You don't have to refresh anything – Leo Dabus Feb 07 '16 at 04:10
  • @LeoDabus May i refer you to my post, as I'm unsure if I'm implementing your codes correctly. http://stackoverflow.com/questions/35248864/how-to-update-image-view – jo3birdtalk Feb 07 '16 at 04:41
  • 3
    @PaulPeelen could you add more info about how this would be done? It would be great if we could make this awesome answer even better. – AndHeiberg Feb 08 '16 at 11:25
  • 2
    @AndHeiberg Check `objc_setAssociatedObject` and `objc_removeAssociatedObjects`. – Paul Peelen Feb 08 '16 at 12:49
  • @PaulPeelen I did, but it's not the clearest of concepts. Right now I don't have an issue with it so I'm not gonna try and grok it. But it would seem you have the experience so just thought it would be epic if you appended to the answer. – AndHeiberg Feb 08 '16 at 21:43
  • 1
    This solved me my problem. When I try to load image with url in table view, scroll view is really not responding when scroll to top or down. I used UIImageView Extension and it solved my problem. Thanks. – JengGe Chao Mar 07 '16 at 12:07
  • 1
    @PaulPeelen couldn't you solve the UITableView problem also by using `if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) { cellToUpdate.imageView?.image = image }`? If the cell is no longer visible then the image won't get updated and therefore won't overwrite anything. – Crashalot Apr 20 '16 at 19:13
  • @Crashalot it is an old comment. Just use the UIImageView extension – Leo Dabus Apr 20 '16 at 19:26
  • 1
    @LeoDabus ok thanks. and thanks for answering so many questions on SO! you're like a professor of swift and iOS. :) – Crashalot Apr 20 '16 at 19:48
  • @LeoDabus btw do you happen to know the answer to this question? http://stackoverflow.com/questions/36728704/delay-when-using-instantiateviewcontrollerwithidentifier-but-not-performseguewit – Crashalot Apr 20 '16 at 19:52
  • 2
    @LeoDabus how come you used dataTaskWithURL instead of downloadTaskWithURL? – Crashalot Apr 20 '16 at 21:13
  • 4
    The first one downloads to memory the second one to a file – Leo Dabus Apr 20 '16 at 21:48
  • How do I use URL and .scaleAspectFit and URLSession? Are those imports? Which ones? – Michael Yaworski Aug 06 '16 at 04:14
  • @mikeyaworski http://stackoverflow.com/revisions/0ccd4b06-233f-4f1d-9fd1-e7cb5d277455/view-source – Leo Dabus Aug 06 '16 at 05:41
  • @LeoDabus Hey, great solution, can you please explain how it is safe from race condition when used in tableview? Also is there a way to limit "file cache" size? ( e.g. 5 mb ) – Minas Jan 23 '17 at 00:05
  • It works well but the last imagenes of a tableView don't load. The images that are not visible initially, don't load after scroll. – JCarlosR Mar 07 '17 at 00:03
  • when I try, I get: "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value", pointing to : imageView.downloadedFrom(link: "http://www.apple.com/euro/ios/ios8/a/generic/images/og.png"). Wy does this not work? – Tom Tallak Solbu Jun 28 '18 at 15:11
  • @TomTallakSolbu have you checked if your image view it is properly connected to your view controller or custom cell? – Leo Dabus Aug 27 '18 at 14:45
  • The code works fine. Just that when I do `po UIImage(data:data)` *in the debugger* it returns `nil`. Yet in the app, the image is loaded. I'm not sure if there's it's a bug with Xcode or something... – mfaani Oct 21 '18 at 22:15
  • @Honey You need to provide more context on your issue. Are you sure that your data object is a valid image? – Leo Dabus Oct 21 '18 at 22:16
  • Doesn't the fact that the image loads on the screen mean that it's a valid image? – mfaani Oct 22 '18 at 00:06
  • This approach will fail unless you handle async calls properly. – Rizwan Ahmed Dec 11 '18 at 09:44
  • @RizwanAhmed Any approach would fail if you don't know how to handle async calls properly. – Leo Dabus Dec 11 '18 at 10:00
  • @LeoDabus : Implement this code snippet in a UITableViewCell, it should be able to handle multiple requests properly. The current code doesn't. That is what I meant. – Rizwan Ahmed Dec 11 '18 at 10:06
  • 1
    The OP didn't ask how to handle multiple calls in a table view. He is clearly using a single image view in his question. Feel free open a new question and post your issue. – Leo Dabus Dec 11 '18 at 10:08
  • 1
    @Hammad No you are not using the code above – Leo Dabus Mar 24 '21 at 13:52
  • @LeoDabus, One question, should we put that `dataTask` in globalQueue? – Zhou Haibo Oct 10 '21 at 15:35
  • @ChuckZHB no you should call this method on the main thread – Leo Dabus Oct 10 '21 at 15:38
  • @LeoDabus, thanks for reply. May I know why do we put a network request `dataTask` on the main thread? – Zhou Haibo Oct 11 '21 at 02:49
426

(Swift 4 update) To answer the original question directly, here's the swift equivalent of the posted Objective-C snippet.

let url = URL(string: image.url)
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
imageView.image = UIImage(data: data!)

DISCLAIMER:

It's important to note that the Data(contentsOf:) method will download the contents of the url synchronously in the same thread the code is being executed, so do not invoke this in the main thread of your application.

An easy way to make the same code run asynchronously, not blocking the UI, is by using GCD:

let url = URL(string: image.url)

DispatchQueue.global().async {
    let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
    DispatchQueue.main.async {
        imageView.image = UIImage(data: data!)
    }
}

That said, in real life applications, if you want to have the best User Experience and avoid multiple downloads of the same image, you may want to also have them not only downloaded, but cached. There's already quite a few libraries that does that very seamless and they are all really easy to use. I personally recommend Kingfisher:

import Kingfisher

let url = URL(string: "url_of_your_image")
// this downloads the image asynchronously if it's not cached yet
imageView.kf.setImage(with: url) 

Also works with SwiftUI:

var body: some View {
    KFImage(URL(string: "https://example.com/image.png")!)
}

And that's it

Lucas Eduardo
  • 11,525
  • 5
  • 44
  • 49
  • 15
    "Easily" is subjective, and novice coders won't expect this unwanted behaviour, but copy/paste this snipped as it is. Perhaps you can update your answer? – Orlin Georgiev Apr 16 '15 at 08:00
  • 16
    I still do not agree. Some answers should be kept simple, a lot of people that comes here likes to copy and paste small snippets of code. I just translated to Swift the objective-C code from the original question, everything besides that is a bonus. And by the way, there is already a answer in this same question providing this kind of bonus information, which makes less sense to duplicate information. – Lucas Eduardo Apr 17 '15 at 01:36
  • Can you change your example to use an actual URL string? What is the variable `image` with the `url` property? – User Mar 29 '16 at 03:40
  • 2
    @User just replace the `image.url` by any string url, hardcoded or not :) – Lucas Eduardo Mar 29 '16 at 08:41
  • Reference to property `imageView` in closure requires explicit `self.` to make capture semantics explicit: `self.imageView.image = UIImage()` – Jason Sturges Jul 21 '16 at 20:29
  • @JasonSturges if your imageView is a property, yes, you should use .self first. This was a generic example – Lucas Eduardo Jul 22 '16 at 08:54
  • 2
    @IanWarburton Of course you can use whatever you want :). 1) This answer is based in the original question, which was using the same approach in objective-C, so I just helped in the "translation" to swift. 2) It doesn't make sense to remove this approach and put the `URLSession.dataTask` because a lot of other answers here already show how to do it, It's better to keep the different options opened. – Lucas Eduardo Aug 05 '16 at 15:17
  • @LucasEduardo Is URLSession likely to be implemented using GCD? – Ian Warburton Aug 05 '16 at 15:50
  • @IanWarburton the URLSession is most likely to be implemented with NSOperations and NSOperationQueus, which are indeed built on top of GCD. So I guess your answer is yes – Lucas Eduardo Aug 09 '16 at 09:25
  • @CouchDeveloper and why exactly is the code poor? Would love to know your reasons :) – Lucas Eduardo Jan 22 '17 at 02:41
  • @LucasEduardo First I apologise for being just rude and lazy. :/ But this is my opinion on the quality of the answer. Even though many novice OPs do like an overly simplified code snippet that's copy&past without having to read about their misconceptions and having to know about the invalid assumptions which must be fulfilled in order the solution "to work". What I and probably others would like to see is an explanation and better code. See Leo's answer for example - which is not short and simple and not copy&paste - but far better. – CouchDeveloper Jan 22 '17 at 11:35
  • @CouchDeveloper I see your point but... Leo's answer is very good indeed, but also lack any explanation, only contains code. Which is fine, since the original question didn't ask for any. About the code quality, you are assuming that using NSURLSession is better than using the Data's init async, which in this specific case I don't see many advantages (would love to argue with more characters). In the real world, besides downloading the image, you also need a proper cache mechanism and for that a library to handle everything is the best choice anyways. So, don't overreact something simple :) – Lucas Eduardo Jan 23 '17 at 17:26
  • @CouchDeveloper And again, the whole point is keep different options opened and available, it helps in the learning also. No need to repeat things that are already in other answers. – Lucas Eduardo Jan 23 '17 at 17:27
  • @AboveTheGods I indeed certainly do not recommend the usage of the posted snippet in real life applications, I posted it right after the question was made, simply helping the OP in translating his Obj-C code to Swift. Since the question got quite good ranked on google after some time, I updated my answer to contain a disclaimer and recommend the best approach – Lucas Eduardo Dec 12 '17 at 10:03
  • 4
    Heyo, the suggested framework, kingfisher, as stated in the answer will do all of the heavy lifting. Thanks for the suggestion! – Kilmazing Mar 15 '19 at 21:06
  • 1
    It's not good to wait on a sync operation like that on a queue because there is a risk of "overcommitting" it and eventually block for other operations. – aleh Feb 08 '21 at 15:50
69

If you just want to load image (Asynchronously!) - just add this small extension to your swift code:

extension UIImageView {
    public func imageFromUrl(urlString: String) {
        if let url = NSURL(string: urlString) {
            let request = NSURLRequest(URL: url)
            NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
                (response: NSURLResponse?, data: NSData?, error: NSError?) -> Void in
                if let imageData = data as NSData? {
                    self.image = UIImage(data: imageData)
                }
            }
        }
    }
}

And use it this way:

myImageView.imageFromUrl("https://robohash.org/123.png")
skywinder
  • 21,291
  • 15
  • 93
  • 123
52

Xcode 12Swift 5

Leo Dabus's answer is awesome! I just wanted to provide an all-in-one function solution:

if let url = URL(string: "http://www.apple.com/euro/ios/ios8/a/generic/images/og.png") {
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data, error == nil else { return }
        
        DispatchQueue.main.async { /// execute on main thread
            self.imageView.image = UIImage(data: data)
        }
    }
    
    task.resume()
}
aheze
  • 24,434
  • 8
  • 68
  • 125
Mark Moeykens
  • 15,915
  • 6
  • 63
  • 62
46

Swift 2.2 || Xcode 7.3

I got Amazing results!! with AlamofireImage swift library

It provides multiple features like:

  • Asynchronously download
  • Auto Purging Image Cache if memory warnings happen for the app
  • Image URL caching
  • Image Caching
  • Avoid Duplicate Downloads

and very easy to implement for your app

Step.1 Install pods


Alamofire 3.3.x

pod 'Alamofire'

AlamofireImage 2.4.x

pod 'AlamofireImage'

Step.2 import and Use

import Alamofire
import AlamofireImage

let downloadURL = NSURL(string: "http://cdn.sstatic.net/Sites/stackoverflow/company/Img/photos/big/6.jpg?v=f4b7c5fee820")!
imageView.af_setImageWithURL(downloadURL)

that's it!! it will take care everything


Great thanks to Alamofire guys, for making iDevelopers life easy ;)

swiftBoy
  • 35,607
  • 26
  • 136
  • 135
20

Swift 4::

This will shows loader while loading the image. You can use NSCache which store image temporarily

let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
    func loadImageUsingCache(withUrl urlString : String) {
        let url = URL(string: urlString)
        if url == nil {return}
        self.image = nil

        // check cached image
        if let cachedImage = imageCache.object(forKey: urlString as NSString)  {
            self.image = cachedImage
            return
        }

        let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView.init(activityIndicatorStyle: .gray)
        addSubview(activityIndicator)
        activityIndicator.startAnimating()
        activityIndicator.center = self.center

        // if not, download image from url
        URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
            if error != nil {
                print(error!)
                return
            }

            DispatchQueue.main.async {
                if let image = UIImage(data: data!) {
                    imageCache.setObject(image, forKey: urlString as NSString)
                    self.image = image
                    activityIndicator.removeFromSuperview()
                }
            }

        }).resume()
    }
}

Usage:-

truckImageView.loadImageUsingCache(withUrl: currentTruck.logoString)
Jack
  • 13,571
  • 6
  • 76
  • 98
17

swift 3 with error handling

let url = URL(string: arr[indexPath.row] as! String)
if url != nil {
    DispatchQueue.global().async {
        let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
        DispatchQueue.main.async {
            if data != nil {
                cell.imgView.image = UIImage(data:data!)
            }else{
                cell.imgView.image = UIImage(named: "default.png")
            }
        }
    }
}

With Extension

extension UIImageView {

    func setCustomImage(_ imgURLString: String?) {
        guard let imageURLString = imgURLString else {
            self.image = UIImage(named: "default.png")
            return
        }
        DispatchQueue.global().async { [weak self] in
            let data = try? Data(contentsOf: URL(string: imageURLString)!)
            DispatchQueue.main.async {
                self?.image = data != nil ? UIImage(data: data!) : UIImage(named: "default.png")
            }
        }
    }
}

Extension Usage

myImageView. setCustomImage("url")

With Cache support

let imageCache = NSCache<NSString, UIImage>()

extension UIImageView {

    func loadImageUsingCacheWithURLString(_ URLString: String, placeHolder: UIImage?) {

        self.image = nil
        if let cachedImage = imageCache.object(forKey: NSString(string: URLString)) {
            self.image = cachedImage
            return
        }

        if let url = URL(string: URLString) {
            URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in

                //print("RESPONSE FROM API: \(response)")
                if error != nil {
                    print("ERROR LOADING IMAGES FROM URL: \(String(describing: error))")
                    DispatchQueue.main.async { [weak self] in
                        self?.image = placeHolder
                    }
                    return
                }
                DispatchQueue.main.async { [weak self] in
                    if let data = data {
                        if let downloadedImage = UIImage(data: data) {
                            imageCache.setObject(downloadedImage, forKey: NSString(string: URLString))
                            self?.image = downloadedImage
                        }
                    }
                }
            }).resume()
        }
    }
}
Manee ios
  • 1,112
  • 11
  • 14
16

I wrapped the code of the best answers to the question into a single, reusable class extending UIImageView, so you can directly use asynchronous loading UIImageViews in your storyboard (or create them from code).

Here is my class:

import Foundation
import UIKit

class UIImageViewAsync :UIImageView
{

    override init()
    {
        super.init(frame: CGRect())
    }

    override init(frame:CGRect)
    {
        super.init(frame:frame)
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    func getDataFromUrl(url:String, completion: ((data: NSData?) -> Void)) {
        NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) { (data, response, error) in
            completion(data: NSData(data: data))
        }.resume()
    }

    func downloadImage(url:String){
        getDataFromUrl(url) { data in
            dispatch_async(dispatch_get_main_queue()) {
                self.contentMode = UIViewContentMode.ScaleAspectFill
                self.image = UIImage(data: data!)
            }
        }
    }
}

and here is how to use it:

imageView.downloadImage("http://www.image-server.com/myImage.jpg")
dy_
  • 2,433
  • 24
  • 27
  • 2
    Do we really need those overridden inits? Aren't the inits inherited anyway? Seems like we're just overriding here just to call super on itself, which strikes me as redundant. – NYC Tech Engineer Apr 19 '16 at 03:18
13
let url = NSURL.URLWithString("http://live-wallpaper.net/iphone/img/app/i/p/iphone-4s-wallpapers-mobile-backgrounds-dark_2466f886de3472ef1fa968033f1da3e1_raw_1087fae1932cec8837695934b7eb1250_raw.jpg");
var err: NSError?
var imageData :NSData = NSData.dataWithContentsOfURL(url,options: NSDataReadingOptions.DataReadingMappedIfSafe, error: &err)
var bgImage = UIImage(data:imageData)
Anonymous
  • 10,002
  • 3
  • 23
  • 39
user3763002
  • 277
  • 2
  • 3
  • 5
    `fatal error: unexpectedly found nil while unwrapping an Optional value` – User Sep 13 '14 at 00:37
  • Any chance you can write up something asynchronous? – Jed Grant Oct 15 '14 at 21:43
  • @JedGrant you could use dispatch_async to get into another thread and a callback – Matej Oct 29 '14 at 20:53
  • I had to unwrap the NSData with an "!" (note the ! at the end) to get it work like this: var imageData :NSData = NSData(contentsOfURL: url, options: NSDataReadingOptions.DataReadingMappedIfSafe, error: &err)! – botbot Nov 29 '14 at 22:51
13

FYI : For swift-2.0 Xcode7.0 beta2

extension UIImageView {
    public func imageFromUrl(urlString: String) {
        if let url = NSURL(string: urlString) {
            let request = NSURLRequest(URL: url)
            NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
            (response: NSURLResponse?, data: NSData?, error: NSError?) -> Void in
                self.image = UIImage(data: data!)
            }
        }
    }
}
katopz
  • 591
  • 6
  • 14
  • 5
    NSURLConnection is depracated. You have to use NSURLSession. It's better when you code with Swift 2.0 and Xcode 7 – BilalReffas Sep 03 '15 at 19:38
12

Swift 4: A simple loader for small images (ex: thumbnails) that uses NSCache and always runs on the main thread:

class ImageLoader {

  private static let cache = NSCache<NSString, NSData>()

  class func image(for url: URL, completionHandler: @escaping(_ image: UIImage?) -> ()) {

    DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {

      if let data = self.cache.object(forKey: url.absoluteString as NSString) {
        DispatchQueue.main.async { completionHandler(UIImage(data: data as Data)) }
        return
      }

      guard let data = NSData(contentsOf: url) else {
        DispatchQueue.main.async { completionHandler(nil) }
        return
      }

      self.cache.setObject(data, forKey: url.absoluteString as NSString)
      DispatchQueue.main.async { completionHandler(UIImage(data: data as Data)) }
    }
  }

}

Usage:

ImageLoader.image(for: imageURL) { image in
  self.imageView.image = image
}
R. Mohan
  • 2,182
  • 17
  • 30
fethica
  • 759
  • 6
  • 10
  • 1
    private let cache was giving me error so changed it to static and it worked! Thanks – Hammad Tariq May 14 '18 at 08:25
  • I don't think you should be loading `UIImage(data: data as Data)` on the main thread. That should be done in the background thread. – Diz Sep 08 '21 at 22:00
11

swift 5

extension UIImageView {
    func load(url: URL) {
        DispatchQueue.global().async { [weak self] in
            if let data = try? Data(contentsOf: url) {
                if let image = UIImage(data: data) {
                    DispatchQueue.main.async {
                        self?.image = image
                    }
                }
            }
        }
    }
}

for using

override func awakeFromNib() {
    super.awakeFromNib()
   if let url = URL(string:"<imageURLHere>"){
     imgView.load(url: url)
   }
}
Mohammad Daihan
  • 538
  • 4
  • 15
Amro Jaber
  • 185
  • 1
  • 17
8

You’ll want to do:

UIImage(data: data)

In Swift, they’ve replaced most Objective C factory methods with regular constructors.

See:

https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-XID_26

joshstaiger
  • 1,239
  • 1
  • 8
  • 4
  • 2
    Technically mapped, not replaced. If you make your own method `+(instancetype)[MyThing thingWithOtherThing:]`, you would call it as `MyThing(otherThing: ...)` in Swift too. – Brian Nickel Jun 16 '14 at 16:44
7

Swift 2 with error Handle and custom request header

Simply add extension to UIImageView:

extension UIImageView {
    public func imageFromUrl(urlString: String) {
        if let url = NSURL(string: urlString) {
            let request = NSMutableURLRequest(URL: url)
            request.setValue("<YOUR_HEADER_VALUE>", forHTTPHeaderField: "<YOUR_HEADER_KEY>")
            NSURLSession.sharedSession().dataTaskWithRequest(request) {
                (data, response, error) in
                guard let data = data where error == nil else{
                    NSLog("Image download error: \(error)")
                    return
                }

                if let httpResponse = response as? NSHTTPURLResponse{
                    if httpResponse.statusCode > 400 {
                        let errorMsg = NSString(data: data, encoding: NSUTF8StringEncoding)
                        NSLog("Image download error, statusCode: \(httpResponse.statusCode), error: \(errorMsg!)")
                        return
                    }
                }

            dispatch_async(dispatch_get_main_queue(), {
                NSLog("Image download success")
                self.image = UIImage(data: data)
            })
            }.resume()
        }
    }
}

And then, use the new imageFromUrl(urlString: String) to download image

Usage:

imageView.imageFromUrl("https://i.imgur.com/ONaprQV.png")
Cody
  • 4,353
  • 4
  • 39
  • 42
  • in swift 3 , I keep getting this error . What's the problem ? In this line """ URLSession.shared.dataTask(with: url){""""""" Cannot invoke 'dataTask' with an argument list of type '(with: URL, (Data?, URLResponse?, Error?) -> Void)' – Aymen BRomdhane Sep 20 '16 at 09:43
7

Swift 4

This method will download an image from a website asynchronously and cache it:

    func getImageFromWeb(_ urlString: String, closure: @escaping (UIImage?) -> ()) {
        guard let url = URL(string: urlString) else {
return closure(nil)
        }
        let task = URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
            guard error == nil else {
                print("error: \(String(describing: error))")
                return closure(nil)
            }
            guard response != nil else {
                print("no response")
                return closure(nil)
            }
            guard data != nil else {
                print("no data")
                return closure(nil)
            }
            DispatchQueue.main.async {
                closure(UIImage(data: data!))
            }
        }; task.resume()
    }

In use:

    getImageFromWeb("http://www.apple.com/euro/ios/ios8/a/generic/images/og.png") { (image) in
        if let image = image {
            let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
            imageView.image = image
            self.view.addSubview(imageView)
        } // if you use an Else statement, it will be in background
    }
Bobby
  • 6,115
  • 4
  • 35
  • 36
  • How it is caching? And for how long? – Ramis Nov 27 '17 at 04:14
  • It seems to be permanently stored, which is weird, it is stored in the Library>Caches folder. Use print(NSHomeDirectory()) to get to this location on your computer when run on the simulator. – Bobby Nov 27 '17 at 13:30
6

Kingfisher is one of the best library for load image into URL.

Github URL - https://github.com/onevcat/Kingfisher

// If you want to use Activity Indicator.
imageview_pic.kf.indicatorType = .activity
imageview_pic.kf.setImage(with: URL(string: "Give your url string"))

// If you want to use custom placeholder image.
imageview_pic.kf.setImage(with: URL(string: "Give your url string"), placeholder: UIImage(named: "placeholder image name"), options: nil, progressBlock: nil, completionHandler: nil)
Kamani Jasmin
  • 691
  • 8
  • 11
5

a quick hack if you want to quickly check image from url

 let imageURL = NSURL(string: "https://farm2.staticflickr.com/1591/26078338233_d1466b7da2_m.jpg")
 let imagedData = NSData(contentsOfURL: imageURL!)!
 imageView?.image = UIImage(data: imagedData)

I implemented within a tableview with a custom cell that has only a image

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
        
        let cell = tableView.dequeueReusableCellWithIdentifier("theCell", forIndexPath: indexPath) as! customTableViewCell

        let imageURL = NSURL(string: "https://farm2.staticflickr.com/1591/26078338233_d1466b7da2_m.jpg")
        
        let imagedData = NSData(contentsOfURL: imageURL!)!
            
        cell.imageView?.image = UIImage(data: imagedData)
        
        return cell
        
    }
Naishta
  • 11,885
  • 4
  • 72
  • 54
  • 1
    you are right , thats because downloading and compressing is happening on the main thread. you should ideally do these 2 steps asynchronously in a background thread, may be using dispatch_async global queue – Naishta Oct 13 '16 at 20:52
  • Yes, By using background thread we can optimize the speed of downloading and If required we can change the logic instead of this we can use sdWebImage or other framework. – Gourav Joshi Oct 14 '16 at 04:12
5

Here is Working code for Loading / Downloading image from URL. NSCache automatically and Display Placeholder image before download and Load Actual image (Swift 4 | Swift 5 Code).

func NKPlaceholderImage(image:UIImage?, imageView:UIImageView?,imgUrl:String,compate:@escaping (UIImage?) -> Void){
    
    if image != nil && imageView != nil {
        imageView!.image = image!
    }
    
    var urlcatch = imgUrl.replacingOccurrences(of: "/", with: "#")
    let documentpath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
    urlcatch = documentpath + "/" + "\(urlcatch)"
    
    let image = UIImage(contentsOfFile:urlcatch)
    if image != nil && imageView != nil
    {
        imageView!.image = image!
        compate(image)
        
    }else{
        
        if let url = URL(string: imgUrl){
            
            DispatchQueue.global(qos: .background).async {
                () -> Void in
                let imgdata = NSData(contentsOf: url)
                DispatchQueue.main.async {
                    () -> Void in
                    imgdata?.write(toFile: urlcatch, atomically: true)
                    let image = UIImage(contentsOfFile:urlcatch)
                    compate(image)
                    if image != nil  {
                        if imageView != nil  {
                            imageView!.image = image!
                        }
                    }
                }
            }
        }
    }
}

Use Like this :

// Here imgPicture = your imageView
// UIImage(named: "placeholder") is Display image brfore download and load actual image. 

NKPlaceholderImage(image: UIImage(named: "placeholder"), imageView: imgPicture, imgUrl: "Put Here your server image Url Sting") { (image) in }
Nikunj Kumbhani
  • 3,758
  • 2
  • 26
  • 51
  • 1
    The only that worked with me. my issue was I was implementing music player and i wanted to the image to load in the notification when the phone is closed also and the handler takes the type of UIImage so I had to use this method. – JhonnyTawk Oct 01 '19 at 07:46
  • How do you handle occasional cleanups of the download folder? Reloading of images happens faster and smoother compared to NSCache. But it'd prefer not to download images to the disk. – kirqe Oct 28 '20 at 11:19
4

Swift 2.0 :

1)

if let url = NSURL(string: "http://etc...") {
    if let data = NSData(contentsOfURL: url) {
        imageURL.image = UIImage(data: data)
    }        
}

OR

imageURL.image =
    NSURL(string: "http:// image name...")
    .flatMap { NSData(contentsOfURL: $0) }
    .flatMap { UIImage(data: $0) }

2) Add this method to VC or Extension.

func load_image(urlString:String)
{   let imgURL: NSURL = NSURL(string: urlString)!
    let request: NSURLRequest = NSURLRequest(URL: imgURL)

    NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { (response: NSURLResponse?, data: NSData?, error: NSError?) in

        if error == nil {
            self.image_element.image = UIImage(data: data)
        }
    }
}

Usage :

self.load_image(" url strig here")
Alvin George
  • 14,148
  • 92
  • 64
4

Swift 4.1 I have created a function just pass image url, cache key after image is generated set it to completion block.

   class NetworkManager: NSObject {
  
  private var imageQueue = OperationQueue()
  private var imageCache = NSCache<AnyObject, AnyObject>()
  
  func downloadImageWithUrl(imageUrl: String, cacheKey: String, completionBlock: @escaping (_ image: UIImage?)-> Void) {
    
    let downloadedImage = imageCache.object(forKey: cacheKey as AnyObject)
    if let  _ = downloadedImage as? UIImage {
      completionBlock(downloadedImage as? UIImage)
    } else {
      let blockOperation = BlockOperation()
      blockOperation.addExecutionBlock({
        let url = URL(string: imageUrl)
        do {
          let data = try Data(contentsOf: url!)
          let newImage = UIImage(data: data)
          if newImage != nil {
            self.imageCache.setObject(newImage!, forKey: cacheKey as AnyObject)
            self.runOnMainThread {
              completionBlock(newImage)
            }
          } else {
            completionBlock(nil)
          }
        } catch {
          completionBlock(nil)
        }
      })
      self.imageQueue.addOperation(blockOperation)
      blockOperation.completionBlock = {
        print("Image downloaded \(cacheKey)")
      }
    }
  }
}
extension NetworkManager {
  fileprivate func runOnMainThread(block:@escaping ()->Void) {
    if Thread.isMainThread {
      block()
    } else {
      let mainQueue = OperationQueue.main
      mainQueue.addOperation({
        block()
      })
    }
  }
}
forrest
  • 10,570
  • 25
  • 70
  • 132
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
4
class ImageStore: NSObject { 
    static let imageCache = NSCache<NSString, UIImage>()
}

extension UIImageView {
    func url(_ url: String?) {
        DispatchQueue.global().async { [weak self] in
            guard let stringURL = url, let url = URL(string: stringURL) else {
                return
            }
            func setImage(image:UIImage?) {
                DispatchQueue.main.async {
                    self?.image = image
                }
            }
            let urlToString = url.absoluteString as NSString
            if let cachedImage = ImageStore.imageCache.object(forKey: urlToString) {
                setImage(image: cachedImage)
            } else if let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
                DispatchQueue.main.async {
                    ImageStore.imageCache.setObject(image, forKey: urlToString)
                    setImage(image: image)
                }
            }else {
                setImage(image: nil)
            }
        }
    }
}

Usage :

let imageView = UIImageView()
imageView.url("image url")
Anand Khanpara
  • 846
  • 8
  • 8
4

AsyncImage is officially introduced after iOS 15, a view that synchronously loads and displays an image.

 var imageView : AsyncImage

imageView = AsyncImage(url: URL(string: entry.photo))
    .frame(width: 200, height: 200)

It also supports:

See more in doc

RY_ Zheng
  • 3,041
  • 29
  • 36
3

A method for getting the image that is safe and works with Swift 2.0 and X-Code 7.1:

static func imageForImageURLString(imageURLString: String, completion: (image: UIImage?, success: Bool) -> Void) {
    guard let url = NSURL(string: imageURLString),
        let data = NSData(contentsOfURL: url),
        let image = UIImage(data: data)
        else { 
            completion(image: nil, success: false); 
            return 
       }

    completion(image: image, success: true)
}

You would then call this method like so:

imageForImageURLString(imageString) { (image, success) -> Void in
        if success {
            guard let image = image 
                 else { return } // Error handling here 
            // You now have the image. 
         } else {
            // Error handling here.
        }
    }

If you are updating the view with the image, you will have to use this after the "if success {":

    dispatch_async(dispatch_get_main_queue()) { () -> Void in
         guard let image = image 
              else { return } // Error handling here 
         // You now have the image. Use the image to update the view or anything UI related here
         // Reload the view, so the image appears
    }

The reason this last part is needed if you are using the image in the UI is because network calls take time. If you try to update the UI using the image without calling dispatch_async like above, the computer will look for the image while the image is still being fetched, find that there is no image (yet), and move on as if there was no image found. Putting your code inside of a dispatch_async completion closure says to the computer, "Go, get this image and when you are done, then complete this code." That way, you will have the image when the code is called and things will work well.

Ben Patch
  • 1,213
  • 1
  • 9
  • 12
3

Edited for Latest change 09/2021

// It's better to use extension 
extension UIImageView {
func downloadImage(from URLString: String, with completion: @escaping (_ response: (status: Bool, image: UIImage? ) ) -> Void) {
    guard let url = URL(string: URLString) else {
        completion((status: false, image: nil))
        return
    }
    
    URLSession.shared.dataTask(with: url) { data, response, error in
        guard error == nil else {
            completion((status: false, image: nil))
            return
        }
        
        guard let httpURLResponse = response as? HTTPURLResponse,
              httpURLResponse.statusCode == 200,
              let data = data else {
            completion((status: false, image: nil))
            return
        }
        
        let image = UIImage(data: data)
        completion((status: true, image: image))
    }.resume()
}
}

Happy Codding. Cheers:)

iamVishal16
  • 1,780
  • 18
  • 40
3

I recommend using Kingfisher library to download images asynchronously. The best part about using Kingfisher is, it caches all the downloaded images by default with the image url as an id. Next time when you request to download image with that particular URl, it will load it from cache.

Usage:

newsImage.kf.setImage(with: imageUrl!, placeholder: nil, options: nil, progressBlock: nil, completionHandler: { (image, error, cacheType, imageUrl) in
                if error == nil{
                    self.activityIndicator.stopAnimating()
                }else if error != nil{
                    self.activityIndicator.stopAnimating()
                }
            })
Raj Salla
  • 75
  • 8
3

You can use pod SDWebImage to achieve the same. Its easy to use. Yo can get documentaion here SDWebImage

Here is the sample code

self.yourImage.sd_setImage(with: NSURL(string: StrUrl as String ) as URL!, placeholderImage: placeholderImage, options: SDWebImageOptions(rawValue: 0), completed: { (image, error, cacheType, imageURL) in
                if( error != nil)
                {
                    print("Error while displaying image" , (error?.localizedDescription)! as String)
                }
            })
Shruti Thombre
  • 989
  • 4
  • 11
  • 27
3

Image loading from server :-

func downloadImage(from url: URL , success:@escaping((_ image:UIImage)->()),failure:@escaping ((_ msg:String)->())){
    print("Download Started")
    getData(from: url) { data, response, error in
        guard let data = data, error == nil else {
            failure("Image cant download from G+ or fb server")
            return
        }

        print(response?.suggestedFilename ?? url.lastPathComponent)
        print("Download Finished")
        DispatchQueue.main.async() {
             if let _img = UIImage(data: data){
                  success(_img)
            }
        }
    }
}
func getData(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
    URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}

Usage :-

  if let url = URL(string: "http://www.apple.com/euro/ios/ios8/a/generic/images/og.png") {
                        self.downloadImage(from:url , success: { (image) in
                            print(image)

                        }, failure: { (failureReason) in
                            print(failureReason)
                        })
                    }
Abhimanyu Daspan
  • 1,095
  • 16
  • 20
3

Swift 4.2 and AlamofireImage

If using a library is not an issue, you can do it by help of the AlamofireImage. my samples are from its Github

Placeholder Images Example:

let imageView = UIImageView(frame: frame)
let url = URL(string: "https://httpbin.org/image/png")!
let placeholderImage = UIImage(named: "placeholder")!
imageView.af_setImage(withURL: url, placeholderImage: placeholderImage)

it has many handy functions and extension to work with images. from caching to scaling and resizing or even applying filters on the image. if images are important in your app, I suggest to use this framework and save your time.

Hamish
  • 1,685
  • 22
  • 37
2

Swift 2.x answer that downloads image to file (as opposed to Leo Dabus's answer, which stores the image in memory). Based on Leo Dabus's answer and Rob's answer from Get the data from NSURLSession DownloadTaskWithRequest from completion handler:

    // Set download vars
    let downloadURL = NSURL() // URL to download from
    let localFilename = "foobar.png" // Filename for storing locally 

    // Create download request
    let task = NSURLSession.sharedSession().downloadTaskWithURL(downloadURL) { location, response, error in
        guard location != nil && error == nil else {
            print("Error downloading message: \(error)")
            return
        }

        // If here, no errors so save message to permanent location
        let fileManager = NSFileManager.defaultManager()
        do {
            let documents = try fileManager.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
            let fileURL = documents.URLByAppendingPathComponent(localFilename)
            try fileManager.moveItemAtURL(location!, toURL: fileURL)
            self.doFileDownloaded(fileURL, localFilename: localFilename)
            print("Downloaded message @ \(localFilename)")
        } catch {
            print("Error downloading message: \(error)")
        }
    }

    // Start download
    print("Starting download @ \(downloadURL)")
    task.resume()


// Helper function called after file successfully downloaded
private func doFileDownloaded(fileURL: NSURL, localFilename: String) {

    // Do stuff with downloaded image

}
Community
  • 1
  • 1
Crashalot
  • 33,605
  • 61
  • 269
  • 439
1

The only things there is missing is a !

let url = NSURL.URLWithString("http://live-wallpaper.net/iphone/img/app/i/p/iphone-4s-wallpapers-mobile-backgrounds-dark_2466f886de3472ef1fa968033f1da3e1_raw_1087fae1932cec8837695934b7eb1250_raw.jpg");
var err: NSError?
var imageData :NSData = NSData.dataWithContentsOfURL(url!,options: NSDataReadingOptions.DataReadingMappedIfSafe, error: &err)
var bgImage = UIImage(data:imageData!)
1

For better performance in UITableView or UICollectionView use light weight library Smart Lazy Loading You can use this lazy loading approach if you want to load images from url Asynchronous

Smart 'Lazy Loading' in UICollectionView or UITableView using NSOperation and NSOperationQueue in iOS So in this project we can download the multiple images in any View (UICollectionView or UITableView) by optimising the performance of an app by using Operation and OperationQueue for concurrency. following are the key point of this project Smart Lazy Loading: Creating image download Service. Prioritise the downloading based on the visibility of cells.

ImageDownloadService class will create a singleton instance and have NSCache instance to cache the images that have been downloaded. We have inherited the Operation class to TOperation to mauled the functionality according to our need. I think the properties of the operation subclass are pretty clear in terms of functionality. We are monitoring operations changes of state by using KVO.

Jawad Ali
  • 13,556
  • 3
  • 32
  • 49
1

When using SwiftUI, SDWebImageSwiftUI is the best option.

Add the dependancy via XCode's Swift Package Manager: https://github.com/SDWebImage/SDWebImageSwiftUI.git

Then just use WebImage() instead of Image()

WebImage(url: URL(string: "https://nokiatech.github.io/heif/content/images/ski_jump_1440x960.heic"))
Oded Breiner
  • 28,523
  • 10
  • 105
  • 71
1

You can easily download image from image url with Kingfisher.

Firstly import Kingfisher as-

pod 'Kingfisher'

Then import it in your class as -

import Kingfisher

After that add a temporary UIImageView

let imgView = UIImageView()
imgView.kf.setImage(with: yourImageURL)

if let finalImage = imgView.image {
    // finalImage is your image
}
Imran0001
  • 580
  • 4
  • 11
1

Here is the easiest way, you dont have to worry about async, or how it works.

import SDWebImage

imageView.sd_setImage(with: URL(string: ".net/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
Using UIImageView+WebCache category with UI

Here is a detailed blog post about it.

  • Please be sure to read [Stack Overflow's self-promotion policy](https://stackoverflow.com/help/promotion) when referencing your own content. – Jeremy Caney Dec 16 '21 at 00:17
0

Use of Ascyimageview you can easy load imageurl in imageview.

let image1Url:URL = URL(string: "(imageurl)" as String)! imageview.imageURL = image1Url

saurabh rathod
  • 1,090
  • 7
  • 7
-1

For Swift-3 and above:

extension UIImageView {
  public func imageFromUrl(urlString: String) {
    if let url = URL(string: urlString) {
        let request = URLRequest(url: url)
        NSURLConnection.sendAsynchronousRequest(request as URLRequest, queue: .main, completionHandler: { (response, data, error) in
            if let imageData = data as NSData? {
                self.image = UIImage(data: imageData as Data)
            }
        })
    }
  }
}
FARAZ
  • 645
  • 7
  • 6
-1

A clean way:

extension URL {
    var favIcon16: UIImage? { getFav(ofSize: .s) }
    var favIcon32: UIImage? { getFav(ofSize: .m) }
    var favIcon64: UIImage? { getFav(ofSize: .l) }
    var favIcon128: UIImage? { getFav(ofSize: .xl) }
    var favIcon256: UIImage? { getFav(ofSize: .xxl) }
    var favIcon512: UIImage? { getFav(ofSize: .xxxl) }

    private func getFav(ofSize s: FavSize) -> UIImage? {
        guard UIApplication.shared.canOpenURL(self),
              let favUrl = URL(string: "https://www.google.com/s2/favicons?sz=\(s.rawValue)&domain=\(self.absoluteURL)"),
              let data = try? Data(contentsOf: favUrl)
        else { return nil }
        return UIImage(data: data)
 }
    private enum FavSize: Int, CaseIterable { case s = 16, m = 32, l = 64, xl = 128, xxl = 256, xxxl = 512 }
}

and usage:

let myUrl = URL(string: "http://facebook.com")
myImgView.image = myUrl.favIcon16
Mahdi Moqadasi
  • 2,029
  • 4
  • 26
  • 52
-2

Use this code in Swift

imageView.image=UIImage(data: NSData(contentsOfURL: NSURL(string: "http://myURL/ios8.png")!)!
Bibin Joseph
  • 234
  • 2
  • 7
-2
class Downloader {
    
    class func downloadImageWithURL(url: String) -> UIImage! {
        
        let date = NSData(contentsOf: URL(string: url)!)
        return UIImage(data: date! as Data)
    }
}

usage:

let img = Downloader.downloadImageWithURL(url: imageURL)
        self.imageView1.image = img
Mohsen Fard
  • 597
  • 9
  • 22