URLImage

Follow me on Twitter

URLImage 是一个 SwiftUI 视图,用于显示从提供的 URL 下载的图像。URLImage 会为您管理远程图像的下载和本地缓存(包括内存和磁盘)。

使用 URLImage 非常简单

URLImage(url: url) { image in
    image
        .resizable()
        .aspectRatio(contentMode: .fit)
}

请在演示应用程序中查看一些示例。

目录

特性

安装

可以使用 Swift Package Manager 安装 URLImage

  1. 在 Xcode 中,打开 File/Swift Packages/Add Package Dependency... 菜单。

  2. 复制并粘贴包 URL

https://github.com/dmytro-anokhin/url-image

有关更多详细信息,请参阅 Adding Package Dependencies to Your App 文档。

用法

您可以使用 URL 和 ViewBuilder 创建 URLImage 以显示下载的图像。

import URLImage // Import the package module

let url: URL = //...

URLImage(url) { image in
    image
        .resizable()
        .aspectRatio(contentMode: .fit)
}

注意:URLImage 初始化的第一个参数是 URL 类型,如果您有一个 String,您必须首先创建一个 URL 对象。

视图自定义

URLImage 视图管理并在 4 个下载状态之间转换

每个状态都有一个单独的视图。 您可以使用 ViewBuilder 参数自定义一个或多个。

URLImage(item.imageURL) {
    // This view is displayed before download starts
    EmptyView()
} inProgress: { progress in
    // Display progress
    Text("Loading...")
} failure: { error, retry in
    // Display error and retry button
    VStack {
        Text(error.localizedDescription)
        Button("Retry", action: retry)
    }
} content: { image in
    // Downloaded image
    image
        .resizable()
        .aspectRatio(contentMode: .fit)
}

选项

URLImage 允许使用 URLImageOptions 结构控制某些方面。 例如,是否下载图像或使用缓存、何时启动和取消下载、如何配置网络请求、最大像素大小等。

URLImageOptions 是环境值,可以使用 \.urlImageOptions 键路径设置。

URLImage(url) { image in
    image
        .resizable()
        .aspectRatio(contentMode: .fit)
}
.environment(\.urlImageOptions, URLImageOptions(
    maxPixelSize: CGSize(width: 600.0, height: 600.0)
))

在环境值中设置 URLImageOptions 允许为您的整个或部分视图层次结构设置选项。

@main
struct MyApp: App {

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.urlImageOptions, URLImageOptions(
                    maxPixelSize: CGSize(width: 600.0, height: 600.0)
                ))
        }
    }
}

图像信息

如果需要有关图像的信息(例如实际大小)或访问底层 CGImage 对象,可以使用 ImageInfo 结构。 ImageInfocontent 视图构建器闭包的参数。

URLImage(item.imageURL) { image, info in
    if info.size.width < 1024.0 {
        image
            .resizable()
            .aspectRatio(contentMode: .fit)
    } else {
        image
            .resizable()
            .aspectRatio(contentMode: .fill)
    }
}

放大

如果想添加缩放图像的功能,请考虑查看 AdvancedScrollView 包。

import AdvancedScrollView
import URLImage

URLImage(url) { image in
    AdvancedScrollView(magnificationRange: 1.0...4.0) { _ in
        image
    }
}

缓存

URLImage 还可以缓存图像以降低网络带宽或供离线使用。

默认情况下,URLImage 使用协议缓存策略,即 Cache-Control HTTP 标头和 URLCache。 这与图像在 Web 上的工作方式相对应,并且需要网络连接。

或者,如果您想离线查看图像,则必须配置文件存储。 配置后,URLImage 将不再使用协议缓存策略,而是遵循 URLImageOptions.FetchPolicy 设置。

import URLImage
import URLImageStore

@main
struct MyApp: App {

    var body: some Scene {

        let urlImageService = URLImageService(fileStore: URLImageFileStore(),
                                          inMemoryStore: URLImageInMemoryStore())

        return WindowGroup {
            FeedListView()
                .environment(\.urlImageService, urlImageService)
        }
    }
}

确保在目标设置的“Frameworks, Libraries,and Embedded Content”下包含 URLImageStore 库。

存储使用场景

您可能会问何时使用协议缓存或自定义缓存。 URLImage 旨在服务于两种使用场景

当应用程序只能连接到互联网时,使用协议缓存策略。 电子商务应用程序(如购物、旅游、活动预订应用程序等)就是这样工作的。 遵循协议缓存策略,您可以确保图像以 CDN 定义的方式缓存,仍然可以快速访问,并且不会占用用户设备上不必要的空间。

为需要在离线访问或在后台下载的内容配置 URLImageStore。 这可以是一个阅读器应用程序,您可能希望在用户打开文章之前下载它们,也许在应用程序处于后台时。 此内容应保留相当长一段时间。

高级

开始加载

当图像视图呈现时,URLImage 开始加载。 在某些情况下(如使用 List 时),您可能希望在视图出现时开始加载,并在视图消失时取消加载。 您可以使用 URLImageOptions.LoadOptions 选项自定义此设置。 您可以组合多个选项以实现最适合您的 UI 的行为。

List(/* ... */) {
    // ...
}
    .environment(\.urlImageOptions, URLImageOptions(loadOptions: [ .loadOnAppear, .cancelOnDisappear ]))

注意:3.1 之前的版本在出现时开始加载,并在视图消失时取消加载。 3.1 版本在视图呈现时开始加载。 这是因为在没有上下文的情况下,onAppearonDisappear 回调是相当不可预测的。

创建你自己的 URLImage

或者,您可以创建自己的 URLImage 来自定义外观和行为以满足您的需求。

struct MyURLImage: View {

    @ObservedObject private var remoteImage: RemoteImage

    init(service: URLImageService, url: URL) {
        remoteImage = service.makeRemoteImage(url: url, identifier: nil, options: URLImageOptions())
    }

    var body: some View {
        ZStack {
            switch remoteImage.loadingState {
                case .success(let value):
                    value.image

                default:
                    EmptyView()
            }
        }
        .onAppear {
            remoteImage.load()
        }
    }
}

您可以从封闭视图访问服务环境值:@Environment(\.urlImageService) var service: URLImageService

获取图像

您可能希望在没有视图的情况下下载图像。 这可以使用 RemoteImagePublisher 对象来实现。 RemoteImagePublisher 可以缓存图像以供 URLImage 视图将来使用。

将图像下载为 CGImage 并忽略任何错误

cancellable = URLImageService.shared.remoteImagePublisher(url)
    .tryMap { $0.cgImage }
    .catch { _ in
        Just(nil)
    }
    .sink { image in
        // image is CGImage or nil
    }

将多个图像下载为 [CGImage?] 数组

let publishers = urls.map { URLImageService.shared.remoteImagePublisher($0) }

cancellable = Publishers.MergeMany(publishers)
    .tryMap { $0.cgImage }
    .catch { _ in
        Just(nil)
    }
    .collect()
    .sink { images in
        // images is [CGImage?]
    }

使用 RemoteImagePublisher 对象下载图像时,所有选项都像对 URLImage 对象一样适用。 默认情况下,下载的图像将缓存在磁盘上。 这可以加快在应用程序的后期阶段显示图像的速度。 此外,这是目前在 iOS 14 小部件中显示图像的唯一支持方式。

在 iOS 14 Widget 中下载图像

不幸的是,WidgetKit 中的视图无法运行异步操作:https://developer.apple.com/forums/thread/652581。 推荐的方法是在 TimelineProvider 中加载您的内容,包括图像。

您仍然可以使用 URLImage 来实现此目的。 想法是您使用 RemoteImagePublisher 对象在 TimelineProvider 中加载图像,并在 URLImage 视图中显示它。

迁移说明 v2 到 v3

常见问题

视图重新加载时图像重新加载

如果您将 URLImageTextField 或另一个更新触发视图更新的状态的控件一起使用,这是一个常见问题。 由于 URLImage 是异步的并且最初为空,它将在显示下载的图像之前重置为空状态。 为避免这种情况,请在您的 App 中的某个位置设置 URLImageInMemoryStore

import SwiftUI
import URLImage
import URLImageStore

@main
struct MyApp: App {
    var body: some Scene {
        let urlImageService = URLImageService(fileStore: nil, inMemoryStore: URLImageInMemoryStore())

        return WindowGroup {
            ContentView()
                .environment(\.urlImageService, urlImageService)
        }
    }
}

注意:您可以使用 URLImageInMemoryStoreremoveImageWithURLremoveImageWithIdentifierremoveAllImages 方法重置缓存的图像。

导航/工具栏中的图像显示为单色矩形

这不是 Bug。 导航/工具栏使用 .renderingMode(.template) 将图像显示为模板(将所有非透明像素呈现为前景色)。 重置它的方法是指定 .renderingMode(.original)

URLImage(url) { image in
    image.renderingMode(.original)
}

报告 Bug

使用 GitHub Issues 报告 Bug。 尽可能包含以下信息

摘要和/或背景; 操作系统以及您使用的设备; URLImage 库的版本; 您期望会发生什么; 实际发生了什么; 附加信息:显示 Bug 的屏幕截图或视频; 崩溃日志; 示例代码,尝试隔离它,以便它可以在没有依赖项的情况下编译; 测试数据:如果您使用公共资源,请提供图像的 URL。

请确保存在可重现的场景。 理想情况下,提供示例代码。 如果您提交示例代码 - 确保它可以编译 ;)

请求功能

使用 GitHub Issues 请求功能。

贡献

欢迎贡献。 请在提交 Pull Request 之前创建一个 GitHub Issue 来计划和讨论实现。