显示来自 URL、Swift 4.2 的图像

Display image from URL, Swift 4.2

我是一个相当不错的 Objective C 开发人员,我现在正在学习 Swift(我发现其中相当困难,不仅因为新概念,例如可选值,还因为Swift 不断发展,许多可用的教程已经严重过时了)。

目前我正在尝试将 JSON 从 url 解析为 NSDictionary,然后使用其值之一来显示图像(也是 url)。像这样:


URL -> NSDictionary -> 从 url 初始化 UIImage -> 在 UIImageView 中显示 UIImage


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

现在,我基本上是在尝试在 Swift 中复制上面的 Objective C 代码,但不断地 运行 到墙上。我尝试了几个教程(其中一个实际上做了完全相同的事情:显示 NASA 图像),并找到了一些堆栈溢出答案,但 none 可能会有所帮助,因为它们已经过时或者它们做的事情与我需要的。

所以,我想请社区提供 Swift 4 代码来解决这些问题:

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

如果还不算太多,我还想在代码旁边索取详细说明,因为我希望答案是针对这项任务的全面 "tutorial",我认为目前还没有随处可用。

谢谢!

首先我很确定半年后你会发现Objective-C非常复杂和困难。

其次,甚至不鼓励使用 ObjC 代码。不要使用同步 Data(contentsOf 方法从远程 URL 加载数据。无论使用哪种语言,都使用异步方式,如 (NS)URLSession.

并且不要在 Swift 中使用 Foundation 集合类型 NSArrayNSDictionary。如果有本地 Swift 对应项,基本上根本不要使用 NS... 类。

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

需要将url转换成字符串和数据添加到imageview

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

由于图像加载是一项微不足道的任务,同时可以通过多种不同的方式实现,我建议您不要 "reinvent the wheel" 并查看图像加载库,例如 Nuke,因为它已经涵盖了你在开发过程中可能需要的大部分情况。

它允许您使用简单的 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中添加pod 吊舱 'Alamofire', 吊舱 'AlamofireImage' 你可以检查这个 link 安装 pods => https://cocoapods.org/pods/AlamofireImage

// 使用此函数从 URL 加载图像到 imageview

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

检查此 link 以获取 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!)

我刚刚扩展了 vadian 的回答,分离了一些问题以清楚地了解基础知识。他的回答应该足够了。

首先,您必须构建自己的结构。这将代表您从网络服务中检索到的 JSON 结构。

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

然后让你请求方法。我通常为它创建一个单独的文件。现在,vadian 提到了完成处理程序。这些由转义闭包表示。在这里,闭包 ()-> 在两个函数上传递,并以解码数据作为参数调用。

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

您可以使用此扩展程序

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
}

更新 Xcode 13.3 , Swift 5

要从 URL 字符串异步加载图像,请使用此扩展:

extension UIImageView {

public func getImageFromURLString(imageURLString: String) {
    guard let imageURL = URL(string: imageURLString) else { return}
    Task {
        await requestImageFromURL(imageURL)
    }
}

private func requestImageFromURL(_ imageURL: URL) async{
    let urlRequest = URLRequest(url: imageURL)
    do {
        let (data, response) = try await URLSession.shared.data(for: urlRequest)
        
        if let httpResponse = response as? HTTPURLResponse{
            if httpResponse.statusCode == 200{
                print("Fetched image successfully")
            }
        }
        // Loading the image here
        self.image = UIImage(data: data)
    } catch let error {
        print(error)
    }
}
}

用法:

imageView.getImageFromURLString(imageURLString: "https://apod.nasa.gov/apod/image/1811/hillpan_apollo15_4000.jpg")