Add a spinning indicator for images which are loading
- ID
b934093- date
2023-06-09 14:21:42+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
f7203b2- message
Add a spinning indicator for images which are loading- changed files
Changed files
BlinkReviewer/BlinkReviewer/Views/FullSizeImage.swift (900) → BlinkReviewer/BlinkReviewer/Views/FullSizeImage.swift (1864)
diff --git a/BlinkReviewer/BlinkReviewer/Views/FullSizeImage.swift b/BlinkReviewer/BlinkReviewer/Views/FullSizeImage.swift
index fa74b0f..33dbfa0 100644
--- a/BlinkReviewer/BlinkReviewer/Views/FullSizeImage.swift
+++ b/BlinkReviewer/BlinkReviewer/Views/FullSizeImage.swift
@@ -25,6 +25,22 @@ struct FullSizeImage: View {
AlbumInfo(asset)
}
}
+ .overlay(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) {
+
+ // Render a progress indicator if we're waiting for a higher-resolution
+ // image to load; see the comment on `PHAssetImage`.
+ //
+ // `ProgressView` does have a `tint` modifier, but that doesn't seem to
+ // work on macOS 13 -- this uses some code from a Stack Overflow answer
+ // by aheze: https://stackoverflow.com/a/66568704/1558022
+ if (self.image.isDegraded) {
+ ProgressView()
+ .colorInvert()
+ .brightness(1)
+ .padding()
+ }
+
+ }
Spacer()
}
BlinkReviewer/BlinkReviewer/Views/Helpers/DeferredRendering.swift (0) → BlinkReviewer/BlinkReviewer/Views/Helpers/DeferredRendering.swift (919)
diff --git a/BlinkReviewer/BlinkReviewer/Views/Helpers/DeferredRendering.swift b/BlinkReviewer/BlinkReviewer/Views/Helpers/DeferredRendering.swift
new file mode 100644
index 0000000..bb12641
--- /dev/null
+++ b/BlinkReviewer/BlinkReviewer/Views/Helpers/DeferredRendering.swift
@@ -0,0 +1,40 @@
+//
+// DeferredRendering.swift
+// BlinkReviewer
+//
+// Created by Alex Chan on 09/06/2023.
+//
+
+import SwiftUI
+
+/// A ViewModifier that defers its rendering until after the provided threshold surpasses
+private struct DeferredViewModifier: ViewModifier {
+
+ let threshold: Double
+
+ func body(content: Content) -> some View {
+ _content(content)
+ .onAppear {
+ DispatchQueue.main.asyncAfter(deadline: .now() + threshold) {
+ self.shouldRender = true
+ }
+ }
+ }
+
+ @ViewBuilder
+ private func _content(_ content: Content) -> some View {
+ if shouldRender {
+ content
+ } else {
+ content.hidden()
+ }
+ }
+
+ @State private var shouldRender = false
+}
+
+extension View {
+ func deferredRendering(for seconds: Double) -> some View {
+ modifier(DeferredViewModifier(threshold: seconds))
+ }
+}
BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift (2225) → BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift (3245)
diff --git a/BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift b/BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift
index b513415..82fd5e8 100644
--- a/BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift
+++ b/BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift
@@ -1,17 +1,28 @@
-//
-// PHAssetImage.swift
-// BlinkReviewer
-//
-// Created by Alex Chan on 09/06/2023.
-//
-
import SwiftUI
import Photos
+/// This view gets an NSImage for a PHAsset.
+///
+/// When you get a photo from the Photos library, it may not be available
+/// immediately -- for example, if the image has to be downloaded from
+/// iCloud first. Downstream views can create an instance of this object,
+/// and then watch the `image` property -- this will be populated with the
+/// appropriate image as it loads.
+///
+/// You can use this class in two ways:
+///
+/// 1. Create a new instance for every PHAsset you want to render
+/// 2. Create a single instance and update the `asset` property; the `image`
+/// property will be updated shortly after
+///
+/// Note: PhotoKit may return multiple versions of an image, e.g. a low-res
+/// version immediately and a high-res version later. You can inspect the
+/// `isDegraded` property -- this will tell you if Photos has returned a
+/// low quality image now and expects to return a higher quality image later.
class PHAssetImage: NSObject, ObservableObject {
@Published var image = NSImage()
- @Published var isPhotoLibraryAuthorized = false
+ @Published var isDegraded = false
init(_ asset: PHAsset?, size: CGSize) {
self.size = size
@@ -63,6 +74,10 @@ class PHAssetImage: NSObject, ObservableObject {
contentMode: .aspectFill,
options: options,
resultHandler: { (result, info) -> Void in
+ if let isDegraded = info?[PHImageResultIsDegradedKey] as? Bool {
+ self.isDegraded = isDegraded
+ }
+
if let imageResult = result {
self.image = imageResult
} else {