Opening a photo in Apple Photos from Swift on macOS
The GitHub issue I created for myself was Tap a photo to load it in Apple Photos. It was supposed to be a 10 minute thing. One of those tasks you quickly knock off and then feel good about yourself because you’ve been so productive.
I was pulling photos in from Apple Photos, displaying them in my Mac app, and I wanted to let you re-open a photo in Apple Photos when you tap a photo in my app.
Each photo is represented by PHAsset from PhotoKit which I fetched using its PhotoKit localIdentifier that looks something like E1F92593-2A89-44CA-B4F9-5C586A2EEE14/L0/001
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil).firstObject else {
return
}
Many Apple Apps support URL Schemes to let them expose functionality. I was expecting to find a URL scheme like photos://id=E1F92593-2A89-44CA-B4F9-5C586A2EEE14/L0/001 to let me open a photo in Apple Photos.
The first sign of trouble was the Google for open apple photos using phasset … lots of answers, all telling me it wasn’t possible.
This was further confirmed by the ultimate source of all knowledge:
What had started as a simple 10 minute issue, suddenly morphed into a challenge. A day-long challenge: Given a local identifier for a photo from PhotoKit, how can I open that photo in Apple Photos from Swift?
I worked it out in the end, and hunting for the solution was entertaining, taking me back to my 8 bit microcomputer days where breaking the copy-protection (to get infinite lives) on a game I’d bought was more fun than actually playing the game.
I started by opening Apple Photos, then running Activity Monitor, selecting Photos, and tapping the ⓘ at the top, and then tapping Open Files and Ports:
Nothing there caught my eye.
Next I dumped the strings from the Photos app using the strings terminal command:
strings /System/Applications/Photos.app/Contents/MacOS/Photos
ffffff
?333333
?333333
?QQQQQQ
?XXXXXX
...
Searching that file for “://“ (to find URL Schemes) turned up:
photos:albums
photos:albums?folderUuid=
photos:albums?albumUuid=
photos://album?name=favorites
photos://devices?index=0
photos:all-photos
photos:search?searchTerm=
photos:testing/Monkey
Going to Safari and entering photos://album?name=favorites works! I can go to favorites. I’d really like to hear the story behind photos:testing/Monkey …
This looked interesting but still nothing looked like a URL that might open a specific photo.
Next I decided to look into the photo widget that runs on the Mac.
On the right is a photo widget. If I tap it, it opens that photo in Apple Photos …
Once again I went to Activity Monitor, this time I selected PhotosReliveWidget and tapped the (i) at the top, tapped on Open Files and Ports and saw that it included Users/damian/Library/Containers/com.apple.Photos.PhotosReliveWidget/Data
The first entry looked interesting, so I opened that folder in Finder, and digging around I found various …chrono-timeline files…
I opened the one under timelines in Visual Studio code, accepted the warning that it was a binary file, and struck pay dirt:
Here was a string that looks like it opens a photo: photos:albums?albumUuid=C82E5B43-C2D1-479D-93B4-E33E3836A1EC&assetUuid=5860425A-1CFD-45CB-98D1-F862AA40437B&source=widget
How to use it? I could not open it from Safari…
I tried using the open command line command:
open "photos:albums?albumUuid=C82E5B43-C2D1-479D-93B4-E33E3836A1EC&assetUuid=5860425A-1CFD-45CB-98D1-F862AA40437B&source=widget"
That worked!
A little more spelunking and, by getting the album representing your main photo library, I could now generate a URL that I can use in a SwiftUI Link:
// Fetch the PHAsset corresponding to the photo
guard let asset = PHAsset.fetchAssets(
withLocalIdentifiers: [localIdentifier],
options: nil
).firstObject else {
return
}
// Get the id of the photo libary album
let collections = PHAssetCollection.fetchAssetCollections(
with: .smartAlbum,
subtype: .smartAlbumUserLibrary,
options: nil)
if let albumId = collections.firstObject?.localIdentifier {
// Util function to trim unwanted stuff from local identifer
func trim(_ string: String) -> String.SubSequence {
string.prefix(while: {$0 != "/"})
}
// Assign the URL that can be used to open the photo
// in Apple Photos
linkURL = URL(string: "photos:albums?albumUuid=\(trim(albumId))&assetUuid=\(trim(asset.localIdentifier))")
}
I remove the slash and what comes after it from the localIdentifiers (E1F92593-2A89-44CA-B4F9-5C586A2EEE1 /L0/001) for the photo and the library album, which I can then consume in SwiftUI:
if let linkURL = viewModel.linkURL {
Link(destination: linkURL, label: {
Image(nsImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
})
}
Now I can tap my photos to open them in Apple Photos.
Whether Apple approves my app into the App Store, and whether this technique continues to work remains to be seen. It is totally undocumented and likely to break at some point.
But it works for now!
Kind comments and feedback are welcome