Adding a photo picker
Closed this issue · 4 comments
Okay, I've got, hopefully, one last hurdle to integrate MarkupEditor: When I insert an image I'd like to select it from the standard Photos picker. In another part of my app I do this with the SwiftUI PhotosPicker. I see how I can override the markupShowImagePopover through my delegate, but since that's outside of the normal View hierarchy, I'm at a loss for how to implement the PhotosPicker. Any tips would be appreciated.
I guess you want to do basically the same thing as the SwiftUIDemo does with the DocumentPicker. AFAIK the PhotosPicker just takes .onChange(of: selectedItems)
at which point you can do what the DemoContentView does:
private func imageSelected(url: URL) {
guard let view = MarkupEditor.selectedWebView else { return }
markupImageToAdd(view, url: url)
}
The markupImageToAdd
is a MarkupDelegate method, the default implementation of which does view.insertLocalImage(url: url)
to insert the image at the selection point. I suppose the trick would be to use the PhotosPickerItem directly and create a version of view.insertLocalImage(photosPickerItem: PhotosPickerItem, handler: ((URL)->Void)? = nil)
that looked somewhat like the url-based one:
public func insertLocalImage(url: URL, handler: ((URL)->Void)? = nil) {
var path = "\(UUID().uuidString).\(url.pathExtension)"
if let resourcesUrl {
path = resourcesUrl.appendingPathComponent(path).relativePath
}
let cachedImageUrl = URL(fileURLWithPath: path, relativeTo: cacheUrl())
do {
try FileManager.default.copyItem(at: url, to: cachedImageUrl)
insertImage(src: path, alt: nil) {
self.markupDelegate?.markupImageAdded(url: cachedImageUrl)
handler?(cachedImageUrl)
}
} catch let error {
Logger.webview.error("Error inserting local image: \(error.localizedDescription)")
handler?(cachedImageUrl)
}
}
What I do is specify a resourcesUrl: <myModelObject>.relativeResourcesUrl()
when I create the MarkupEditorView. This is basically a resources
subdirectory relative to the URL to get to location where I save the HTML from the MarkupEditor. In the end, the MarkupEditor wants a local file in the cacheUrl directory to hold the image so it can be referenced from the <img src=...>
tag while you're editing. Then when it inserts the uniquely named image at that location, it lets your delegate know where it ended up using markupImageAdded
, so you can take care of it properly at save time.
So, maybe a view.insertLocalImage(photosPickerItem: item)
could use the Transferable to put the image data at the URL. It seems worse than copying a file around to me, though, because the photo you pick is in a .png format, gets transformed to an Image, and then has to be retransformed to .png for storage again. I wonder if there is just a way to get the URL from the PhotosPicker. I haven't used it myself.
Thanks Steven, that code was above-and-beyond the call. You've given me a lot to puzzle over.
I hear you about the efficiency of moving all the images around. I'm already loading the image from the PhotosPicker and storing it in a cache directory of my own for display in another web view, and ultimately it all gets converted into data for socking away in a CoreData store, so I might need to figure out how to consolidate some of these caches.
As near as I can tell, there's no simple way to get a URL from the PhotosPIcker. I did find this, though.
Please post your code if you get the PhotoPicker working reasonably well. I'll leave this open, or we could move it to a discussion.
I pretty much just followed your suggestions. You're correct that PhotosPicker fires an onChange when the user taps on an image. There doesn't seem to be a way to get it to deliver a URL – perhaps they don't have the images stored as files anywhere. Instead, it gives you a big chunk of data that is a UIImage. For my app, I need this stored in a CoreData Persistent Container, so in the onChange, I encode the image and sock it away in the CoreData store. Then I write it out to my resourcesURL as a png.
Using the code you posted above, I then call markupImageToAdd and pass it the URL of the file that I just saved.
I had to make one small change to markupImageToAdd. It creates a filename for the image that you pass to it, and I need the image name to match the one that I've already used in my CoreData store so I took out the part of markupImageToAdd that was creating a filename and stuck with the one I already had.
Once you pointed me in the right direction, it was very easy to get working. If you want the user to be able to select multiple images at once, I don't think you can do that with the PhotosPicker, so that simplifies a lot of things on the back end.