SwiftUI看起来很酷,但有些事情对我来说似乎很难。即便如此,我还是更愿意理解如何最好地用SwiftUI的方式来做一些事情,而不是包装预swiftui控制器,然后用旧的方法来做一些事情。因此,让我从一个简单的问题开始--显示给定URL的web图像。有解决方案,但它们不是那么容易找到的,也不是所有容易理解的。
我有一个解决方案,并希望得到一些反馈。下面是我想做的一个例子(图片来自Open Images)。
struct ContentView: View {
    @State var imagePath: String = "https://farm2.staticflickr.com/440/19711210125_6c12414d8f_o.jpg"
    var body: some View {
        WebImage(imagePath: $imagePath).scaledToFit()
    }
}我的解决方案需要在正文的顶部放一点代码来开始图像下载。图像路径有一个@Binding属性包装器--如果它改变了,我想要更新我的视图。还有一个带有@State属性包装器的myimage变量--当它被设置时,我还想更新我的视图。如果图像加载一切顺利,将设置myimage并显示图像。最初的问题是,更改正文中的状态将导致视图无效,并触发另一个下载,ad infinitum。解决方案看起来很简单(代码如下)。只需检查imagePath,查看自上次加载某些内容以来是否发生了更改。请注意,在download中,我立即设置了prev,这将触发body的另一次执行。条件会导致状态更改被忽略。
我在某处读到,@State检查相等性,如果值不变,将忽略set。对于UIImage,这种相等性检查将失败。我期望对body进行三次调用:初始调用、设置prev时的调用和设置image时的调用。我想我可以为prev添加一个可变值(即一个简单的类),并避免第二次调用。
请注意,加载web内容可以使用扩展和闭包来完成,但这是另一个问题。这样做会将WebImage缩小到只有几行代码。
那么,有没有更好的方法来完成这项任务呢?
//
//  ContentView.swift
//  Learn
//
//  Created by John Morris on 11/26/19.
//  Copyright © 2019 John Morris. All rights reserved.
//
import SwiftUI
struct WebImage: View {
    @Binding var imagePath: String?
    @State var prev: String?
    @State var myimage: UIImage?
    @State var message: String?
    var body: some View {
        if imagePath != prev {
            self.downloadImage(from: imagePath)
        }
        return VStack {
            myimage.map({Image(uiImage: $0).resizable()})
            message.map({Text("\($0)")})
        }
    }
    init?(imagePath: Binding<String?>) {
        guard imagePath.wrappedValue != nil else {
            return nil
        }
        self._imagePath = imagePath
        guard let _ = URL(string: self.imagePath!) else {
            return nil
        }
    }
    func getData(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
        URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
    }
    func downloadImage(from imagePath: String?) {
        DispatchQueue.main.async() {
            self.prev = imagePath
        }
        guard let imagePath = imagePath, let url = URL(string: imagePath) else {
            self.message = "Image path is not URL"
            return
        }
        getData(from: url) { data, response, error in
            if let error = error {
                self.message = error.localizedDescription
                return
            }
            guard let httpResponse = response as? HTTPURLResponse else {
                self.message = "No Response"
                return
            }
            guard (200...299).contains(httpResponse.statusCode) else {
                if httpResponse.statusCode == 404 {
                    self.message = "Page, \(url.absoluteURL), not found"
                } else {
                    self.message = "HTTP Status Code \(httpResponse.statusCode)"
                }
                return
            }
            guard let mimeType = httpResponse.mimeType else {
                self.message = "No mimetype"
                return
            }
            guard mimeType == "image/jpeg" else {
                self.message = "Wrong mimetype"
                return
            }
            print(response.debugDescription)
            guard let data = data else {
                self.message = "No Data"
                return
            }
            if let image = UIImage(data: data) {
                DispatchQueue.main.async() {
                    self.myimage = image
                }
            }
        }
    }
}
struct ContentView: View {
    var images = ["https://c1.staticflickr.com/3/2260/5744476392_5d025d6a6a_o.jpg",
                  "https://c1.staticflickr.com/9/8521/8685165984_e0fcc1dc07_o.jpg",
                  "https://farm1.staticflickr.com/204/507064030_0d0cbc850c_o.jpg",
                  "https://farm2.staticflickr.com/440/19711210125_6c12414d8f_o.jpg"
    ]
    @State var imageURL: String?
    @State var count = 0
    var body: some View {
        VStack {
            WebImage(imagePath: $imageURL).scaledToFit()
            Button(action: {
                self.imageURL = self.images[self.count]
                self.count += 1
                if self.count >= self.images.count {
                    self.count = 0
                }
            }) {
                Text("Next")
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}发布于 2019-12-03 10:15:56
我会建议两件事。首先,您通常希望在下载图像时允许使用占位符视图。其次,你应该缓存图像,否则如果你有像tableView这样的东西,它会滚动到屏幕上,你就会一遍又一遍地下载图片。下面是我的一个应用程序中的一个示例,说明我是如何解决这个问题的:
import SwiftUI
import Combine
import UIKit
class ImageCache {
  enum Error: Swift.Error {
    case dataConversionFailed
    case sessionError(Swift.Error)
  }
  static let shared = ImageCache()
  private let cache = NSCache<NSURL, UIImage>()
  private init() { }
  static func image(for url: URL) -> AnyPublisher<UIImage?, ImageCache.Error> {
    guard let image = shared.cache.object(forKey: url as NSURL) else {
      return URLSession
        .shared
        .dataTaskPublisher(for: url)
        .tryMap { (tuple) -> UIImage in
          let (data, _) = tuple
          guard let image = UIImage(data: data) else {
            throw Error.dataConversionFailed
          }
          shared.cache.setObject(image, forKey: url as NSURL)
          return image
        }
        .mapError({ error in Error.sessionError(error) })
        .eraseToAnyPublisher()
    }
    return Just(image)
      .mapError({ _ in fatalError() })
      .eraseToAnyPublisher()
  }
}
class ImageModel: ObservableObject {
  @Published var image: UIImage? = nil
  var cacheSubscription: AnyCancellable?
  init(url: URL) {
    cacheSubscription = ImageCache
      .image(for: url)
      .replaceError(with: nil)
      .receive(on: RunLoop.main, options: .none)
      .assign(to: \.image, on: self)
  }
}
struct RemoteImage : View {
  @ObservedObject var imageModel: ImageModel
  init(url: URL) {
    imageModel = ImageModel(url: url)
  }
  var body: some View {
    imageModel
      .image
      .map { Image(uiImage:$0).resizable() }
      ?? Image(systemName: "questionmark").resizable()
  }
}https://stackoverflow.com/questions/59129946
复制相似问题