Get URL of loaded document?

I'm writing a MacOS app which loads PDFs as documents. I also want to save them. The app builds and works, but flags an error when I try to save or Save As. about not being able to save the file because it doesn't exist.


Debug console gives the URL as something like:


CGDataConsumerCreateWithFilename: failed to open `/Users/Ben/Library/Developer/Xcode/DerivedData/ReView-hitabkxsdoqaskfrelyidvcylvqs/Build/Products/Debug/file:/var/folders/qd/qjbz7jbn52938qrg166yn9l80000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20ReView)/Test.pdf' for writing: No such file or directory.


which looks very similar to what's described in this thread. https://forums.developer.apple.com/thread/120162


My code is fairly minimal to start with:


override func write(to url: URL, ofType typeName: String) throws {

thePDFDocument?.write(toFile: url.absoluteString, withOptions: nil)

}


It was optimistic to think it would be that easy. What am I doing wrong?

Accepted Reply

You're misusing PDFDocument.write(toFile:withOptions:). It takes a path, and you're passing a URL string, which is not the same thing.


You can see this from your logged path:


`/Users/Ben/Library/Developer/Xcode/DerivedData/

ReView-hitabkxsdoqaskfrelyidvcylvqs/Build/Products/Debug/

file:/var/folders/qd/qjbz7jbn52938qrg166yn9l80000gn/T/TemporaryItems/

(A%20Document%20Being%20Saved%20By%20ReView)/Test.pdf'


(I've added some line breaks so we can see it without scrolling.)


Clearly, your absolute URL string was:


'file:/var/folders/qd/qjbz7jbn52938qrg166yn9l80000gn/T/TemporaryItems/

(A%20Document%20Being%20Saved%20By%20ReView)/Test.pdf'


When you tried to use that as a path, it was interpreted as a relative path (because it didn't start with a slash), and so it got appended to the current default directory.


You should use the URL-based "write(to:withOptions:)" method instead:


https://developer.apple.com/documentation/pdfkit/pdfdocument/1436088-write

Replies

I suppose your app is sandboxed ?


So, you need some extra work to authorize manipulating files.

No, it's not sandboxed. Xcode 10.3, Mojave 10.14.6.

Could you show more code, how you defined url ?

As usual, after spending a whole day wrestling with it, the solution is discovered minutes after posting.


The URL property .absoluteString doesn't return a useful string: but it works with .path instead.

You're misusing PDFDocument.write(toFile:withOptions:). It takes a path, and you're passing a URL string, which is not the same thing.


You can see this from your logged path:


`/Users/Ben/Library/Developer/Xcode/DerivedData/

ReView-hitabkxsdoqaskfrelyidvcylvqs/Build/Products/Debug/

file:/var/folders/qd/qjbz7jbn52938qrg166yn9l80000gn/T/TemporaryItems/

(A%20Document%20Being%20Saved%20By%20ReView)/Test.pdf'


(I've added some line breaks so we can see it without scrolling.)


Clearly, your absolute URL string was:


'file:/var/folders/qd/qjbz7jbn52938qrg166yn9l80000gn/T/TemporaryItems/

(A%20Document%20Being%20Saved%20By%20ReView)/Test.pdf'


When you tried to use that as a path, it was interpreted as a relative path (because it didn't start with a slash), and so it got appended to the current default directory.


You should use the URL-based "write(to:withOptions:)" method instead:


https://developer.apple.com/documentation/pdfkit/pdfdocument/1436088-write

So there are two methods to write, one for paths and one for URLs? Both generous and confusing! You'll see I got it to work by supplying the path instead of the URL string. Presumably there's no actual difference in terms of result?

That's all there is. I didn't define it - Xcode did, when I created the override. Anyway, it's fixed now.

Yes, there are two methods. Almost always, they come in pairs, one with a path (older), one with a URL (newer).


Using the one with a URL is almost always the right thing to do. Outside of a sandbox, when referring to a local file, both should produce the same result, but URLs are the more general mechanism.

Thanks, that's very useful information. Without it, I'd just choose the first method in the list!