FocusedImage: fix layout flashes when the underlying data changes
- ID
584f492- date
2026-06-29 08:00:39+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
e154ddb- message
FocusedImage: fix layout flashes when the underlying data changes- changed files
2 files, 34 additions, 8 deletions
Changed files
Blink/Views/FocusedImage/FocusedImage.swift (959) → Blink/Views/FocusedImage/FocusedImage.swift (2076)
diff --git a/Blink/Views/FocusedImage/FocusedImage.swift b/Blink/Views/FocusedImage/FocusedImage.swift
index ada00c7..1e250f2 100644
--- a/Blink/Views/FocusedImage/FocusedImage.swift
+++ b/Blink/Views/FocusedImage/FocusedImage.swift
@@ -1,26 +1,52 @@
import SwiftUI
import Photos
-/// Render the big image that gets shown in the main view.
+/// FocusedImage is the primary container view for the photo currently under review.
+///
+/// It handles the presentation and layout of the focused asset, including displaying
+/// the image itself, adding metadata overlays, and attaching interactive modifiers.
struct FocusedImage: View, Identifiable {
+ let asset: PHAsset
+ @ObservedObject var photosLibrary: PhotosLibrary
+
var id: String {
asset.localIdentifier
}
- var asset: PHAsset
- @ObservedObject var focusedAssetImage: PHAssetImage
+ var body: some View {
+ FocusedImageContent(
+ asset: asset,
+ assetImage: photosLibrary.getFullSizedImage(for: asset)
+ )
+ // Tell SwiftUI that this instance of the view corresponds to this asset.
+ //
+ // This means that when the underlying data updates in response to changes
+ // in the Photos app, SwiftUI will update the existing view instead of
+ // destroying and recreating it. This prevents layout flashes.
+ .id(asset.localIdentifier)
+ }
+}
+
+/// FocusedImageContent renders the content of a single photo.
+///
+/// This view isolates and observes a `PHAssetImage` to gracefully handle the
+/// two-step progressive loading from the Photos framework (instantly displaying
+/// a low-resolution thumbnail, followed by the high-resolution image).
+private struct FocusedImageContent: View {
+ let asset: PHAsset
+ @ObservedObject var assetImage: PHAssetImage
var body: some View {
- Image(nsImage: focusedAssetImage.image)
+ Image(nsImage: assetImage.image)
.resizable()
- .draggable(Image(nsImage: focusedAssetImage.image))
+ .draggable(Image(nsImage: assetImage.image))
.aspectRatio(contentMode: .fit)
.albumInfo(for: asset)
- .loadingIndicator(isLoading: focusedAssetImage.isDegraded)
+ .loadingIndicator(isLoading: assetImage.isDegraded)
.contextMenu {
Button {
NSPasteboard.general.clearContents()
- NSPasteboard.general.writeObjects([focusedAssetImage.image])
+ NSPasteboard.general.writeObjects([assetImage.image])
} label: {
Label("Copy", systemImage: "doc.on.doc")
.labelStyle(.titleAndIcon)
Blink/Views/PhotoReviewer.swift (14287) → Blink/Views/PhotoReviewer.swift (14247)
diff --git a/Blink/Views/PhotoReviewer.swift b/Blink/Views/PhotoReviewer.swift
index 314936a..77bf5bc 100644
--- a/Blink/Views/PhotoReviewer.swift
+++ b/Blink/Views/PhotoReviewer.swift
@@ -61,7 +61,7 @@ struct PhotoReviewer: View {
FocusedImage(
asset: focusedAsset,
- focusedAssetImage: photosLibrary.getFullSizedImage(for: focusedAsset)
+ photosLibrary: photosLibrary,
)
Spacer()