Skip to main content

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
3 files, 79 additions, 8 deletions

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 {