``` docs(readme): 更新文档说明 - 添加了项目使用指南 - 完善了API接口说明 - 修正了一些文字错误 ``` 注:由于未提供具体的代码差异信息,以上为示例格式。请提供具体的代码变更内容以便生成准确的commit message。
49 lines
2.2 KiB
Swift
49 lines
2.2 KiB
Swift
import SwiftUI
|
||
|
||
/// 从加密 Vault 异步加载并降采样显示原图的通用组件。
|
||
///
|
||
/// 替代「在 body 里 `try? FileVault.shared.loadImage(...)` 同步读盘 + 全量解码」的旧写法,
|
||
/// 解决两个真实问题:
|
||
/// 1. **OOM**:全分辨率位图(4000×3000 ≈ 48MB)进内存,翻几页就触发 jetsam。这里按 `maxPixel`
|
||
/// 降采样,缩略图几百 KB,全屏图几 MB。
|
||
/// 2. **主线程卡顿**:读盘 + JPEG 解码在主线程会掉帧。这里放到后台线程,主线程只拿结果绘制。
|
||
///
|
||
/// 区分「加载中」与「读取失败」两态:加载中显示中性占位,只有真正失败才显示「原图无法读取」,
|
||
/// 不会一打开就闪一下吓人的错误文案。`content` 拿到 `UIImage`(而非 `Image`),
|
||
/// 方便需要 `image.size` 的调用方(如证据高亮 overlay)按真实宽高比定位。
|
||
struct VaultImage<Content: View, Placeholder: View>: View {
|
||
let relativePath: String
|
||
/// 降采样目标最大边(像素)。缩略图给 ~400,全屏查看器给 ~2000。
|
||
var maxPixel: CGFloat = 1024
|
||
|
||
@ViewBuilder var content: (UIImage) -> Content
|
||
/// 占位回调,`isLoading == true` 表示仍在加载,`false` 表示加载完成但失败。
|
||
@ViewBuilder var placeholder: (_ isLoading: Bool) -> Placeholder
|
||
|
||
@State private var image: UIImage?
|
||
@State private var loading = true
|
||
|
||
var body: some View {
|
||
Group {
|
||
if let image {
|
||
content(image)
|
||
} else {
|
||
placeholder(loading)
|
||
}
|
||
}
|
||
// id 变了(TabView 翻到新页 / 行复用换 asset / 同一 path 改目标尺寸)就重新加载;
|
||
// 把 maxPixel 纳入 id(与 FileVault 缓存 key 同构),避免同 path 切尺寸时不刷新显示旧图。
|
||
.task(id: "\(relativePath)@\(Int(maxPixel))") {
|
||
loading = true
|
||
let path = relativePath
|
||
let mp = maxPixel
|
||
let loaded = await Task.detached(priority: .userInitiated) {
|
||
try? FileVault.shared.loadDownsampledImage(relativePath: path, maxPixelSize: mp)
|
||
}.value
|
||
guard !Task.isCancelled else { return }
|
||
image = loaded
|
||
loading = false
|
||
}
|
||
}
|
||
}
|