首页 > 解决方案 > SwiftUI 无法从下载地址获取图片

问题描述

我有以下代码从下载 url 加载图像并将其显示为 UIImage。

我希望它可以工作,但不知何故,仅显示占位符图像'ccc',而不是下载网址中的实际图像。怎么会这样?

我的网址是从数据库中获取的,看起来像这样:

https://firebasestorage.googleapis.com/v0/b/.../o/P...alt=media&token=...-579f...da

struct ShelterView: View {
    var title: String
    var background: String
    var available: Bool
    var distance: Double
    var gender: String

    @ObservedObject private var imageLoader: Loader

    init(title: String, background: String, available: Bool, distance: Double, gender: String) {
        self.title = title
        self.background = background
        self.available = available
        self.distance = distance
        self.gender = gender
        self.imageLoader = Loader(background)
    }

    var image: UIImage? {
        imageLoader.data.flatMap(UIImage.init)
    }

    var body: some View {
        VStack {
            VStack(alignment: .leading) {
                VStack(alignment: .leading, spacing: 0) {
                    Text(title)
                        .font(Font.custom("Helvetica Now Display Bold", size: 30))
                        .foregroundColor(.white)
                        .padding(15)
                        .lineLimit(2)
                    HStack(spacing: 25) {
                        IconInfo(image: "bed.double.fill", text: String(available), color: .white)
                        if gender != "" {
                            IconInfo(image: "person.fill", text: gender, color: .white)
                        }
                    }
                    .padding(.leading, 15)
                }
                Spacer()
                IconInfo(image: "mappin.circle.fill", text: String(distance) + " miles away", color: .white)
                    .padding(15)
            }
            Spacer()
        }
        .background(
            Image(uiImage: image ?? UIImage(named: "ccc")!) <-- HERE
                .brightness(-0.11)
                .frame(width: 255, height: 360)
        )
            .frame(width: 255, height: 360)
            .cornerRadius(30)
            .shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
    }
}
final class Loader: ObservableObject {

    var task: URLSessionDataTask!
    @Published var data: Data? = nil

    init(_ urlString: String) {
        print(urlString)
        let url = URL(string: urlString)
        task = URLSession.shared.dataTask(with: url!, completionHandler: { data, _, _ in
            DispatchQueue.main.async {
                self.data = data
            }
        })
        task.resume()
    }
    deinit {
        task.cancel()
    }
}

标签: swiftswiftui

解决方案


您的图像是一个普通的旧图像var,在构建视图时恰好为零。SwiftUI 只会重建自身以响应、 或的更改@ObservedObject,因此将您的图像移动到您的属性中,它将起作用。这是我的缓存图像视图:@State@Binding@PublishedimageLoader

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 url = url else {
      return Empty().eraseToAnyPublisher()
    }
    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
  private let contentMode: ContentMode
  init(url: URL?, contentMode: ContentMode = .fit) {
    imageModel = ImageModel(url: url)
    self.contentMode = contentMode
  }
  var body: some View {
    imageModel
      .image
      .map { Image(uiImage:$0).resizable().aspectRatio(contentMode: contentMode) }
      ?? Image(systemName: "questionmark").resizable().aspectRatio(contentMode: contentMode)
  }
}

推荐阅读