Отображать изображение из URL, Swift 4.2

Я довольно приличный разработчик Objective C, и сейчас я изучаю Swift (который мне кажется довольно сложным не только из-за новых концепций, таких как дополнительные опции, но и потому, что Swift постоянно развивается, а большая часть доступных руководств - сильно устарел).

В настоящее время я пытаюсь проанализировать JSON с URL-адреса в NSDictionary, а затем использовать одно из его значений для отображения изображения (которое также является URL-адресом). Что-то вроде этого:


URL -> NSDictionary -> инициализировать UIImage из URL -> отобразить UIImage в UIImageView


Это довольно просто в Objective C (и может быть даже более короткий ответ):

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];

Приведенный выше фрагмент кода возвращает мне стандартный NSDictionary, в котором я могу обратиться к ключу «url», чтобы получить адрес изображения, которое я хочу отобразить:


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


Затем я конвертирую его в UIImage и передаю его UIImageView:

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

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

Теперь я в основном пытаюсь воспроизвести приведенный выше код Objective C в Swift, но постоянно натыкаюсь на стены. Я пробовал несколько руководств (один из которых фактически делал то же самое: отображал изображение НАСА), а также нашел несколько ответов о переполнении стека, но ни один из них не помог, потому что они либо устарели, либо работают не так, как мне нужно.

Итак, я хотел бы попросить сообщество предоставить код Swift 4 для следующих проблем:

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

Если это еще не слишком много, я также хотел бы попросить подробные описания вместе с кодом, потому что я хотел бы, чтобы ответ был единственным исчерпывающим «учебником» для этой задачи, который, как мне кажется, в настоящее время нигде недоступен.

Спасибо!

В Swift следует использовать URL, а не NSURL. Используйте Data, а не NSData. Используйте словарь Swift, а не NSDictionary.

rmaddy 11.11.2018 20:30
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
11 925
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

Во-первых, я почти уверен, что через полгода вы найдете Objective-C очень сложным и трудным. ?

Во-вторых, даже ваш код ObjC не рекомендуется. Не загружайте данные с удаленного URL-адреса синхронным методом Data(contentsOf. Независимо от языка используйте асинхронный способ, например (NS)URLSession.

И не используйте в Swift типы коллекций Foundation NSArray и NSDictionary. По сути, вообще не используйте классы NS..., если есть собственный аналог Swift.

В Swift 4 вы можете легко декодировать JSON с помощью протокола Decodable непосредственно в структуру (Swift),
. строка URL может быть даже декодирована как URL.

Создать структуру

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

Раскомментируйте строки, если вам нужно больше, чем URL.

И загрузите данные двумя задачами с данными.

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()

Вадиан, кажется, ты знаешь, что делаешь, спасибо за ответ! Однако я хотел бы попросить вас внести несколько изменений, прежде чем я приму это: 1. Не могли бы вы изменить свою основную функцию, чтобы она просто возвращала объект NSDictionary (из-за чистого кода, поэтому каждая функция выполняет только одну задачу). 2. Не могли бы вы прокомментировать свой код, чтобы я знал, что делает каждая строка и почему / как? Это важно для понимания вашего ответа, а не просто для копирования / вставки. Я знаю, что это много, но я думаю, что это необходимо, чтобы заполнить пустоту, в которой сейчас не хватает руководств для этой задачи.

Gergely Kovacs 11.11.2018 20:58

Как я уже сказал, не используйте NSDictionary в Swift. Структура быстрее более безопасна и эффективна. Асинхронная задача ничего не возвращение, поэтому вам понадобится (или два) обработчика завершения. Здесь на SO есть бесчисленное множество вопросов о URLSession и обработчиках завершения.

vadian 11.11.2018 21:09

Извините, это не NSDictionary, struct. Ты прав! Но важнее то, что я до сих пор не понимаю ваш код. :( На данный момент все, что я могу сделать, это скопировать и вставить его, а это не то, что я ищу; я хочу понять, что я делаю. Я также считаю, что важно придерживаться чистых правил кодирования и позволить функциям обрабатывать конкретные задачи, такие как декодирование JSON в объект данных, совместимый с языком. Позже это также поможет мне лучше понять ваше решение, поскольку оно будет разбито на более мелкие задачи.

Gergely Kovacs 11.11.2018 21:19

вам нужно преобразовать URL-адрес в строку и данные для добавления в просмотр изображений

let imageURL:URL=URL(string: YourImageURL)!
let data=NSData(contentsOf: imageURL)
Yourimage.image=UIImage(data: data! as Data)

Поскольку загрузка изображений является тривиальной и в то же время задачей, которую можно реализовать разными способами, я бы порекомендовал вам не «изобретать колесо» и взглянуть на библиотеку загрузки изображений, такую ​​как Ядерная бомба, поскольку она уже охватывает большинство кейсов, которые могут вам понадобиться в процессе разработки.

Это позволяет вам загружать и показывать изображение асинхронно в вашем представлении, используя простой api:

Nuke.loadImage(with: url, into: imageView)

А также при необходимости - указать как, изображение должно быть загружено и представлено:

let options = ImageLoadingOptions(
placeholder: UIImage(named: "placeholder"),
failureImage: UIImage(named: "failure_image"),
contentModes: .init(
    success: .scaleAspectFill,
    failure: .center,
    placeholder: .center
)
)
Nuke.loadImage(with: url, options: options, into: imageView)

Сначала добавьте модуль в Podfile капсула 'Аламофайр', под "AlamofireImage" вы можете проверить эту ссылку для установки pods => https://cocoapods.org/pods/AlamofireImage

// Используйте эту функцию для загрузки изображения из URL-адреса в просмотре изображений

imageView.af_setImage(
    withURL: url,
    placeholderImage: placeholderImage //its optional if you want to add placeholder
)

Проверьте эту ссылку для метода alamofireImage https://github.com/Alamofire/AlamofireImage/blob/master/Documentation/AlamofireImage%203.0%20Migration%20Guide.md

Создайте расширение UIIimageView и следующий код

extension UIImageView {
public func imageFromServerURL(urlString: String) {
    self.image = nil
    let urlStringNew = urlString.replacingOccurrences(of: " ", with: "%20")
    URLSession.shared.dataTask(with: NSURL(string: urlStringNew)! as URL, completionHandler: { (data, response, error) -> Void in

        if error != nil {
            print(error as Any)
            return
        }
        DispatchQueue.main.async(execute: { () -> Void in
            let image = UIImage(data: data!)
            self.image = image
        })

    }).resume()
}}

а также

self.UploadedImageView.imageFromServerURL(urlString: imageURLStirng!)

Я только что расширил ответ Вадиана, разделил некоторые проблемы, чтобы четко понять основы. Его ответа должно хватить.

Во-первых, вы должны построить свою структуру. Это будет представлять структуру JSON, полученную вами из веб-службы.

struct Item: Codable {
    let url, hdurl : URL,
    let copyright, explanation, media_type, service_version, title : String
}

Тогда заставьте вас запросить методы. Обычно я создаю для него отдельный файл. Теперь Вадиан упомянул об обработчиках завершения. Они представлены бегущими замыканиями. Здесь closure () -> передается обеим функциям и вызывается с декодированными данными в качестве аргумента.

struct RequestCtrl {

    func fetchItem(completion: @escaping (Item?)->Void) {

         let url = URL(string: "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")!
         //URLSessionDataTask handles the req and returns the data which you will decode based on the Item structure we defined above.
         let task = URLSession.shared.dataTask(with: url) { (data, _, _) in 
             let jsonDecoder = JSONDecoder()
             if let data = data,
                let item = try? jsonDecoder.decode(Item.self, from: data){
                //jsonDecoder requires a type of our structure represented by .self and the data from the request.  
                completion(item)
             } else {
                 completion(nil)
             }
          }
         task.resume()
    }


    func fetchItemPhoto(usingURL url: URL, completion: @escaping (Data?)-> Void) {
         let task = URLSession.shared.dataTask(with: url) { (data, _, _) in
            if let data = data { completion(data) } else { completion(nil) }
          }
         task.resume()
    }
}

Теперь в вашем ViewController вызовите свой запрос и обработайте выполнение вашего закрытия.

  class ViewController: UIViewController {

      let requestCtrl = RequestCtrl()

      override func viewDidLoad() {
         super.viewDidLoad()

         requestCtrl.fetchItem { (fetchedItem) in
            guard let fetchedItem = fetchedItem else { return }
            self.getPhoto(with: fetchedItem)
         }

      }

      func getPhoto(with item: Item) {
           requestCtrl.fetchItemPhoto(usingURL: item.url) { (fetchedPhoto) in
                 guard let fetchedPhoto = fetchedPhoto else { return }
                 let photo = UIImage(data: fetchedPhoto)
                  //now you have a photo at your disposal 
           }
      }
  }

Это не лучшие практики, так как я все еще учусь, поэтому обязательно проведите небольшое исследование по темам, особенно по закрытию, параллелизму ios и URLComponents в документации Apple :)

Вы можете использовать это расширение

extension UIImage {

    public static func loadFrom(url: URL, completion: @escaping (_ image: UIImage?) -> ()) {
        DispatchQueue.global().async {
            if let data = try? Data(contentsOf: url) {
                DispatchQueue.main.async {
                    completion(UIImage(data: data))
                }
            } else {
                DispatchQueue.main.async {
                    completion(nil)
                }
            }
        }
    }

}

С использованием

guard let url = URL(string: "http://myImage.com/image.png") else { return }

UIImage.loadFrom(url: url) { image in
    self.photo.image = image
}

Другие вопросы по теме