Search code examples
iosjsonswiftnsdictionary

Display image from URL, Swift 4.2


I am a fairly decent Objective C developer, and I am now learning Swift (of which I am finding quite difficult, not only because of new concepts, such as optionals, but also because Swift is continually evolving, and much of the available tutorials are severely outdated).

Currently I am trying parse a JSON from a url into an NSDictionary and then use one of its value to display an image (which is also a url). Something like this:


URL -> NSDictionary -> init UIImage from url -> display UIImage in UIImageView


This is quite easy in Objective C (and there may even be a shorter answer):

NSURL *url = [NSURL URLWithString:@"https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"];
NSData *apodData = [NSData dataWithContentsOfURL:url];
NSDictionary *apodDict = [NSJSONSerialization JSONObjectWithData:apodData options:0 error:nil];

The above code snippet gives me back a standard NSDictionary, in which I can refer to the "url" key to get the address of the image I want to display:


"url" : "https://apod.nasa.gov/apod/image/1811/hillpan_apollo15_4000.jpg"


This I then convert into a UIImage and give it to a UIImageView:

NSURL *imageURL = [NSURL URLWithString: [apodDict objectForKey:@"url"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *apodImage = [UIImage imageWithData:imageData];

UIImageView *apodView = [[UIImageView alloc] initWithImage: apodImage];

Now, I am basically trying to replicate the above Objective C code in Swift but continuously run into walls. I have tried several tutorials (one of which actually did the exact same thing: display a NASA image), as well as find a few stack overflow answers but none could help because they are either outdated or they do things differently than what I need.

So, I would like to ask the community to provide the Swift 4 code for the these problems:

1. Convert data from url into a Dictionary
2. Use key:value pair from dict to get url to display an image

If it is not too much already, I would also like to ask for detailed descriptions alongside the code because I would like the answer to be the one comprehensive "tutorial" for this task that I believe is currently not available anywhere.

Thank you!


Solution

  • First of all I'm pretty sure that in half a year you will find Objective-C very complicated and difficult. 😉

    Second of all even your ObjC code is discouraged. Don't load data from a remote URL with synchronous Data(contentsOf method. Regardless of the language use an asynchronous way like (NS)URLSession.

    And don't use Foundation collection types NSArray and NSDictionary in Swift. Basically don't use NS... classes at all if there is a native Swift counterpart.

    In Swift 4 you can easily decode the JSON with the Decodable protocol directly into a (Swift) struct,
    the URL string can be even decoded as URL.

    Create a struct

    struct Item: Decodable {
        // let copyright, date, explanation: String
        // let hdurl: String
        // let mediaType, serviceVersion, title: String
        let url: URL
    }
    

    Uncomment the lines if you need more than the URL.

    And load the data with two data tasks.

    let url = URL(string: "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")! 
    
    let task = URLSession.shared.dataTask(with: url) { (data, _, error) in
        if let error = error { print(error); return }
        do {
           let decoder = JSONDecoder()
           // this line is only needed if all JSON keys are decoded
           decoder.keyDecodingStrategy = .convertFromSnakeCase
           let result = try decoder.decode(Item.self, from: data!)
           let imageTask = URLSession.shared.dataTask(with: result.url) { (imageData, _, imageError) in
               if let imageError = imageError { print(imageError); return }
               DispatchQueue.main.async {
                   let apodImage = UIImage(data: imageData!)
                   let apodView = UIImageView(image: apodImage)
                   // do something with the image view
               }
           }
           imageTask.resume()
       } catch { print(error) }
    }
    task.resume()