swift 如何向AsyncImage添加缓存

9q78igpj  于 2023-02-11  发布在  Swift
关注(0)|答案(5)|浏览(344)

我是SwiftUI的新手,正在寻找如何从URL下载图像。我发现在iOS15中你可以使用AsyncImage来处理图像的所有阶段。代码如下所示。

AsyncImage(url: URL(string: urlString)) { phase in
        switch phase {
        case .success(let image):
            image
                .someModifers
        case .empty:
            Image(systemName: "Placeholder Image")
                .someModifers
        case .failure(_):
            Image(systemName: "Error Image")
                .someModifers
        @unknown default:
            Image(systemName: "Placeholder Image")
                .someModifers
        }
    }

我会启动我的应用程序,每次我在我的列表上上下滚动时,它都会再次下载图片。那么我如何才能添加缓存呢?我试着像在Swift中那样添加缓存。类似于这样。

struct DummyStruct {
  var imageCache = NSCache<NSString, UIImage>()
  func downloadImageFromURLString(_ urlString: String) {
    guard let url = URL(string: urlString) else { return }
    URLSession.shared.dataTask(with: url) { data, response, error in
        if let _ = error {
            fatalError()
        }
        
        guard let data = data, let image = UIImage(data: data) else { return }
        imageCache.setObject(image, forKey: NSString(string: urlString))
    }
    .resume()
  }
}

但它并没有去好。所以我想知道有没有一种方法来添加缓存到异步图像?感谢任何帮助。

d7v8vwbk

d7v8vwbk1#

希望这能对其他人有所帮助。我找到了this great video,它谈到了使用下面的代码来构建一个异步图像缓存函数,以供自己使用。

import SwiftUI

struct CacheAsyncImage<Content>: View where Content: View{
    
    private let url: URL
    private let scale: CGFloat
    private let transaction: Transaction
    private let content: (AsyncImagePhase) -> Content
    
    init(
        url: URL,
        scale: CGFloat = 1.0,
        transaction: Transaction = Transaction(),
        @ViewBuilder content: @escaping (AsyncImagePhase) -> Content
    ){
        self.url = url
        self.scale = scale
        self.transaction = transaction
        self.content = content
    }
    
    var body: some View{
        if let cached = ImageCache[url]{
            let _ = print("cached: \(url.absoluteString)")
            content(.success(cached))
        }else{
            let _ = print("request: \(url.absoluteString)")
            AsyncImage(
                url: url,
                scale: scale,
                transaction: transaction
            ){phase in
                cacheAndRender(phase: phase)
            }
        }
    }
    func cacheAndRender(phase: AsyncImagePhase) -> some View{
        if case .success (let image) = phase {
            ImageCache[url] = image
        }
        return content(phase)
    }
}
fileprivate class ImageCache{
    static private var cache: [URL: Image] = [:]
    static subscript(url: URL) -> Image?{
        get{
            ImageCache.cache[url]
        }
        set{
            ImageCache.cache[url] = newValue
        }
    }
}
ncgqoxb0

ncgqoxb02#

AsyncImage使用默认的URLCache。管理该高速缓存的最简单方法是更改默认URLCache的属性

URLCache.shared.memoryCapacity = 50_000_000 // ~50 MB memory space
URLCache.shared.diskCapacity = 1_000_000_000 // ~1GB disk cache space
xqnpmsa8

xqnpmsa83#

用户喜欢这个

ImageView(url: URL(string: "https://wallpaperset.com/w/full/d/2/b/115638.jpg"))
        .frame(width: 300, height: 300)
        .cornerRadius(20)

ImageView(url: URL(string: "https://ba")) {

            // Placeholder
            Text("⚠️")
                .font(.system(size: 120))
        }
        .frame(width: 300, height: 300)
        .cornerRadius(20)

图像视图.swift

import SwiftUI

struct ImageView<Placeholder>: View where Placeholder: View {

    // MARK: - Value
    // MARK: Private
    @State private var image: Image? = nil
    @State private var task: Task<(), Never>? = nil
    @State private var isProgressing = false

    private let url: URL?
    private let placeholder: () -> Placeholder?

    // MARK: - Initializer
    init(url: URL?, @ViewBuilder placeholder: @escaping () -> Placeholder) {
        self.url = url
        self.placeholder = placeholder
    }

    init(url: URL?) where Placeholder == Color {
        self.init(url: url, placeholder: { Color("neutral9") })
    }
    
    
    // MARK: - View
    // MARK: Public
    var body: some View {
        GeometryReader { proxy in
            ZStack {
                placholderView
                imageView
                progressView
            }
            .frame(width: proxy.size.width, height: proxy.size.height)
            .task {
                task?.cancel()
                task = Task.detached(priority: .background) {
                    await MainActor.run { isProgressing = true }
                
                    do {
                        let image = try await ImageManager.shared.download(url: url)
                    
                        await MainActor.run {
                            isProgressing = false
                            self.image = image
                        }
                    
                    } catch {
                        await MainActor.run { isProgressing = false }
                    }
                }
            }
            .onDisappear {
                task?.cancel()
            }
        }
    }
    
    // MARK: Private
    @ViewBuilder
    private var imageView: some View {
        if let image = image {
            image
                .resizable()
                .scaledToFill()
        }
    }

    @ViewBuilder
    private var placholderView: some View {
        if !isProgressing, image == nil {
            placeholder()
        }
    }
    
    @ViewBuilder
    private var progressView: some View {
        if isProgressing {
            ProgressView()
                .progressViewStyle(.circular)
        }
    }
}

#if DEBUG
struct ImageView_Previews: PreviewProvider {

    static var previews: some View {
        let view = VStack {
            ImageView(url: URL(string: "https://wallpaperset.com/w/full/d/2/b/115638.jpg"))
                .frame(width: 300, height: 300)
                .cornerRadius(20)
        
            ImageView(url: URL(string: "https://wallpaperset.com/w/full/d/2/b/115638")) {
                Text("⚠️")
                    .font(.system(size: 120))
            }
            .frame(width: 300, height: 300)
            .cornerRadius(20)
        }
    
        view
            .previewDevice("iPhone 11 Pro")
            .preferredColorScheme(.light)
    }
}
#endif

图像管理器.swift

import SwiftUI
import Combine
import Photos

final class ImageManager {
    
    // MARK: - Singleton
    static let shared = ImageManager()
    
    
    // MARK: - Value
    // MARK: Private
    private lazy var imageCache = NSCache<NSString, UIImage>()
    private var loadTasks = [PHAsset: PHImageRequestID]()
    
    private let queue = DispatchQueue(label: "ImageDataManagerQueue")
    
    private lazy var imageManager: PHCachingImageManager = {
        let imageManager = PHCachingImageManager()
        imageManager.allowsCachingHighQualityImages = true
        return imageManager
    }()

    private lazy var downloadSession: URLSession = {
        let configuration = URLSessionConfiguration.default
        configuration.httpMaximumConnectionsPerHost = 90
        configuration.timeoutIntervalForRequest     = 90
        configuration.timeoutIntervalForResource    = 90
        return URLSession(configuration: configuration)
    }()
    
    
    // MARK: - Initializer
    private init() {}
    
    
    // MARK: - Function
    // MARK: Public
    func download(url: URL?) async throws -> Image {
        guard let url = url else { throw URLError(.badURL) }
        
        if let cachedImage = imageCache.object(forKey: url.absoluteString as NSString) {
            return Image(uiImage: cachedImage)
        }
    
        let data = (try await downloadSession.data(from: url)).0
        
        guard let image = UIImage(data: data) else { throw URLError(.badServerResponse) }
            queue.async { self.imageCache.setObject(image, forKey: url.absoluteString as NSString) }
    
        return Image(uiImage: image)
    }
}
zed5wv10

zed5wv104#

也许后来的党,但我来到了这个确切的问题,关于穷人的异步图像时,与滚动视图/ LazyVStack布局使用。
根据this thread,接缝的问题是在某种程度上由于苹果公司目前的实施和未来的某个时候,它将得到解决.
我认为我们可以使用的最适合未来的方法是类似于response from Ryan Fung的方法,但不幸的是,它使用了旧语法,并且缺少重载的init(有和没有占位符)。
我扩展了这个解决方案,涵盖了this GitHub's Gist上缺失的情况。您可以像使用当前的AsyncImage实现一样使用它,以便当它一致地支持缓存时,您可以将其替换掉。

wn9m85ua

wn9m85ua5#

我遇到了和你一样的问题,我通过编写一个CachedAsyncImage来解决它,它保持了与AsyncImage相同的API,这样它们就可以很容易地互换,同时也考虑到AsyncImage将来对本机缓存的支持。
我做了一个Swift Package来分享它。
CachedAsyncImage具有与AsyncImage完全相同的API和行为,因此您只需更改以下内容:

AsyncImage(url: logoURL)

改为:

CachedAsyncImage(url: logoURL)

除了AsyncImage初始化器,你还可以指定你想要使用的缓存(默认情况下使用URLCache.shared):
一个二个一个一个
请记住,设置缓存时,响应(在本例中是我们的映像)必须不大于磁盘缓存的5%(参见此讨论)。
这是repo

相关问题