render a full-sized image
- ID
7be2ee1- date
2023-06-08 06:59:03+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
e6c1f9f- message
render a full-sized image- changed files
Changed files
BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj (22748) → BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj (23190)
diff --git a/BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj b/BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj
index 37293c8..0084083 100644
--- a/BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj
+++ b/BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj
@@ -15,6 +15,7 @@
94D7510C2A31A798005859E7 /* BlinkReviewerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D7510B2A31A798005859E7 /* BlinkReviewerUITests.swift */; };
94D7510E2A31A798005859E7 /* BlinkReviewerUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D7510D2A31A798005859E7 /* BlinkReviewerUITestsLaunchTests.swift */; };
94D7511C2A31A7B1005859E7 /* ThumbnailItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D7511B2A31A7B1005859E7 /* ThumbnailItem.swift */; };
+ 94D7511E2A31B243005859E7 /* PreviewImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D7511D2A31B243005859E7 /* PreviewImage.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -47,6 +48,7 @@
94D7510B2A31A798005859E7 /* BlinkReviewerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlinkReviewerUITests.swift; sourceTree = "<group>"; };
94D7510D2A31A798005859E7 /* BlinkReviewerUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlinkReviewerUITestsLaunchTests.swift; sourceTree = "<group>"; };
94D7511B2A31A7B1005859E7 /* ThumbnailItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailItem.swift; sourceTree = "<group>"; };
+ 94D7511D2A31B243005859E7 /* PreviewImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewImage.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -136,6 +138,7 @@
isa = PBXGroup;
children = (
94D7511B2A31A7B1005859E7 /* ThumbnailItem.swift */,
+ 94D7511D2A31B243005859E7 /* PreviewImage.swift */,
);
path = Views;
sourceTree = "<group>";
@@ -272,6 +275,7 @@
files = (
94D7511C2A31A7B1005859E7 /* ThumbnailItem.swift in Sources */,
94D750F22A31A796005859E7 /* ContentView.swift in Sources */,
+ 94D7511E2A31B243005859E7 /* PreviewImage.swift in Sources */,
94D750F02A31A796005859E7 /* BlinkReviewerApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
BlinkReviewer/BlinkReviewer/ContentView.swift (1658) → BlinkReviewer/BlinkReviewer/ContentView.swift (1679)
diff --git a/BlinkReviewer/BlinkReviewer/ContentView.swift b/BlinkReviewer/BlinkReviewer/ContentView.swift
index ee99695..39dcb8c 100644
--- a/BlinkReviewer/BlinkReviewer/ContentView.swift
+++ b/BlinkReviewer/BlinkReviewer/ContentView.swift
@@ -19,23 +19,19 @@ struct AssetData: Codable, Identifiable {
}
struct ContentView: View {
- var allPhotos: [AssetData] {
- var photos: [AssetData] = []
+ var allPhotos: [PHAsset] {
+ var photos: [PHAsset] = []
PHAsset.fetchAssets(with: PHAssetMediaType.image, options: nil)
.enumerateObjects({ (asset, _, _) in
- photos.append(
- AssetData(
- localIdentifier: asset.localIdentifier,
- creationDate: asset.creationDate?.ISO8601Format(),
- isFavorite: asset.isFavorite
- )
- )
- })
+ photos.append(asset)
+ })
return photos
}
+ @State private var selectedAsset: PHAsset? = nil
+
var body: some View {
VStack {
Divider()
@@ -47,16 +43,20 @@ struct ContentView: View {
.padding()
LazyHStack(spacing: 10) {
- ForEach(allPhotos) { photo in
+ ForEach(allPhotos, id: \.localIdentifier) { photo in
ThumbnailItem(label: "\(photo.localIdentifier)")
}
}.padding()
}.frame(height: 100)
}
+ Divider()
+ if let thisSelectedAsset = selectedAsset {
+ PreviewImage(asset: thisSelectedAsset)
+ }
+ }.onAppear {
+ selectedAsset = allPhotos[0]
}
- Divider()
- Spacer()
}
}
BlinkReviewer/BlinkReviewer/Views/PreviewImage.swift (0) → BlinkReviewer/BlinkReviewer/Views/PreviewImage.swift (2633)
diff --git a/BlinkReviewer/BlinkReviewer/Views/PreviewImage.swift b/BlinkReviewer/BlinkReviewer/Views/PreviewImage.swift
new file mode 100644
index 0000000..9eabcf8
--- /dev/null
+++ b/BlinkReviewer/BlinkReviewer/Views/PreviewImage.swift
@@ -0,0 +1,84 @@
+//
+// PreviewImage.swift
+// BlinkReviewer
+//
+// Created by Alex Chan on 08/06/2023.
+//
+
+import Photos
+import SwiftUI
+
+extension PHAsset {
+ /// Create an NSImage at the given size.
+ func getImage(forWidth width: Double, forHeight height: Double) -> NSImage {
+ // This implementation is based on code in a Stack Overflow answer
+ // by Francois Nadeau: https://stackoverflow.com/a/48755517/1558022
+ //
+ // I've added more comments and error-handling logic.
+
+ let options = PHImageRequestOptions()
+ options.isSynchronous = true
+
+ // If i don't set this value, then sometimes I get an error like
+ // this in the `info` variable:
+ //
+ // Error Domain=PHPhotosErrorDomain Code=3164 "(null)"
+ //
+ // This means that the asset is in the cloud, and by default Photos
+ // isn't allowed to download assets here. Apple's documentation
+ // suggests adding this option as the fix.
+ //
+ // See https://developer.apple.com/documentation/photokit/phphotoserror/phphotoserrornetworkaccessrequired
+ options.isNetworkAccessAllowed = true
+
+ var image = NSImage()
+
+ PHImageManager.default()
+ .requestImage(
+ for: self,
+ targetSize: CGSize(width: width, height: height),
+ contentMode: .aspectFit,
+ options: options,
+ resultHandler: { (result, info) -> Void in
+
+ // If we fail to get a result, print a message to the user that
+ // includes the value of `info`. For information about interpreting
+ // these keys, see Apple's documentation:
+ // https://developer.apple.com/documentation/photokit/phimagemanager/image_result_info_keys
+ switch (result, info) {
+ case let (result?, _):
+ image = result
+ case let (.none, info?):
+ fputs("Unable to create image:\n", stderr)
+ fputs("\(info)\n", stderr)
+ exit(1)
+ case (.none, .none):
+ fputs("Unable to create image:\n", stderr)
+ fputs("(unknown error)\n", stderr)
+ exit(1)
+ }
+ })
+
+ return image
+ }
+}
+
+struct PreviewImage: View {
+ var asset: PHAsset
+
+ var body: some View {
+ VStack {
+ GeometryReader { geometry in
+ HStack {
+ Spacer()
+
+ Image(nsImage: asset.getImage(forWidth: geometry.size.width, forHeight: geometry.size.height))
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+
+ Spacer()
+ }
+ }
+ }.padding()
+ }
+}