I need to downsample the image stored in CoreData to show the image at a specific size.
Currently, I've implemented it synchronously, but the image animation is not smooth when the sheetView comes up from the bottom.
Should I animate it, or is downsampling such a large task that I should do it asynchronously? I'm not sure when to do it synchronously and when to do it asynchronously, please help.
import SwiftUI
struct DetailView: View {
@Environment(\.managedObjectContext) private var moc
@ObservedObject var item: ItemEntity
@State private var uiImage: UIImage? = nil
@Environment(\.dismiss) private var dismiss
var safeArea: EdgeInsets
var size: CGSize
private let spacing: CGFloat = 3
var body: some View {
ScrollView(showsIndicators: false) {
VStack(spacing: 2) {
imageHeaderView
}
}
.coordinateSpace(name: "scroll")
.task(id: size) {
guard size != .zero else { return }
loadImage(width: size.width, height: size.height * 0.56) //
}
.onChange(of: item.unwrappedImageData) { newImageData in
updateImage(data: newImageData, width: size.width, height: size.height * 0.56)
}
.onDisappear {
uiImage = nil
}
}
@ViewBuilder
private var imageHeaderView: some View {
let imageHeight = size.height * 0.56
GeometryReader { proxy in
let size = proxy.size
let minY = proxy.frame(in: .named("scroll")).minY
let opacityProgress = (minY / imageHeight) * 1.52
Group {
if !item.unwrappedImageData.isEmpty, let image = uiImage {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: size.width, height: size.height + (minY > 0 ? minY : 0), alignment: .top)
.clipped()
} else {
Image(systemName: "tshirt.fill")
.font(.largeTitle)
.imageScale(.large)
.fontWeight(.regular)
.foregroundColor(.secondary.opacity(0.55))
.frame(width: size.width, height: size.height + (minY > 0 ? minY : 0))
.background(Color.customSecondBackgroundColor)
}
}
}
private func loadImage(width: CGFloat, height: CGFloat) {
guard !item.unwrappedImageData.isEmpty else { return }
//
uiImage = downsampleImage(imageData: item.unwrappedImageData,
to: CGSize(width: width, height: height))
}
private func updateImage(data: Data, width: CGFloat, height: CGFloat) {
uiImage = !data.isEmpty ? downsampleImage(imageData: data, to: CGSize(width: width, height: height)) : nil
}
private func downsampleImage(imageData: Data, to pointSize: CGSize, scale: CGFloat = UIScreen.main.scale) -> UIImage {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let imageSource = CGImageSourceCreateWithData(imageData as CFData, imageSourceOptions) else { return UIImage() }
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
] as CFDictionary
guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return UIImage() }
return UIImage(cgImage: downsampledImage)
}
}