Double click not opening file correctly on document based app

Hi All!


Kind of new to macOS development long time iOS developer.


I am wokring on a document based app and I can open files with the Open Dialog just fine however when I double click on a file it launches my app just fine however there is nothing in the window and the viewDidLoad() on my document view controller is never called thus no content. I am presented with an empty window/VC. Also, it never hits my breakpoints in the debugger.


About the app: Document based video player. The view in the ViewController scene contains an AVPlayerView and I setup the URL to the video to be played. If I use the Open command on the Menu bar my read(from url: is called in my Document class and that is where I get the URL to play. Seems to work just fine when using the Menu Bar however when I double click those methods appear to not be called (can't debug, nothing breaks in debugger).


Any help appreciated!

Thanks,

Michael

Replies

Did you define Document types and exported UTI ?


You find them by:

- selecting the product name in the Navigator panel (files browser in the very left panel) of XCode

- select the target in Targets

- select Info tab

You will see the Document types and exported UTI areas.

Thank you for the quick reply Claude31!


I believe the document type and UTI are correct as the App launches however I end up with a blank document and I am unable to debug it.


I have set my scheme to "wait for executable to be launched" however no breakpoints are ever hit, even applicationDidFinishLaunching.


Here are my Document settings:


Name: Movie File

Class: $product.Document

Extensions: mp4, mpeg, mov

Idenitifer: none

Role: Editor

Mime Types: public.video, public.movie, com.apple.quicktime-movie

Bundle: unchecked

well still have not figure this one out.


When I run the app in the debugger it works as expected. Opens the document video files and plays them just fine. Opens one, multiple, etc.


When I run outside of the debugger by righ clicking on a video file and selecting "Open with myApp" it launches myApp however the document window is blank. Furthermore, if I use the Open dialogn while the app is running any other file I try to open also opens blank.


The document type / Mime is a regular video file so I am not sure if I need to "register" anything with the system?


Thanks in advance.

Michael

It sounds like you are coming from a Swift/iOS background. The NSDocument architecture is old-school Mac, with lots of funky new stuff thrown in with no documentation. It is best to start with the template and follow those defaults. If you deviate from those defaults, by even the slightest amounts, you can expect problems.


It is difficult to say where the problem is. In my apps I am typically doing something funky, so I typically have these problems. But there are many different ways for it to break and only one, completely undocumented, way for it to work properly. That's why I suggest starting with the template. Maybe make a dummy app with dummy document type just so you can figure it out.


Most likely, the problem is related to the various way that an app initializes itself. You may be making certain incorrect assumptions about how that takes place. I know one issue is that when you double-click a document, those document opening notifications (like windowControllerDidLoadNib) and, I assume, document object instantiation, are sent to your app before things like applicationDidFinishLaunching.


Trying to figure out the exact sequence is a daunting task. At once point, I think I overrode every NSDocument method to figure it out. But that was 3 or 4 years ago and it is all different now.

The fact that it's so poorly documented is really incredible. There should be TN for such basic tasks.


Michalg, you should probably file a bug report against documentation, or if really important, burn a DTS ticket.

john daniel and Claude31 I appreictate you taking the time to help me. Looks like its just not the easy and straightforward. Oh well.


I downloaded Apple's "DevelopingADocumentBasedApp" (https://developer.apple.com/documentation/appkit/documents_files_and_icloud/developing_a_document-based_app)


and played with it extensively and compared to my app and tried a lot of different variation without success. I'm thinking it might have something to do with the "representedObject" however I am not certain what that is. I need to read about the "Bindings" to understand more what they are and how that relates to the representedObject. At least that's what I'm thinking is the only piece thats different. I tried to modify the sample app to do what I wanted to and its just not working. It is what it is.


Again thanks for all the help and advice and if I get things figured out I'll post my answer.


Best wishes,

Michael

Just some random thoughts:


I would put a bunch of NSLog statements in your Document class and in the view controller so you can get a rough picture of what happens when you open a file and when you drop a file and what is different between the two scenarios. Maybe this can give you some clues. As an example, one time when I was trying to get the Revert document thing to work, it did everything as expected except the document was not showing up in my window controller. It turned out that opening a file and reverting a file had a different order of calling things. Since you app launches, I suspect something similar is happening. Anyway, if it was me trying to figure it out, I would put NSLog statements in the code to see where the file date gets to and where it doesn't. I'm sort of a simplton in that I rely a lot on NSLog for debugging. 🙂

The "representedObject" is part of that new code that Apple has bolted onto NSDocument to make it more appealing to iOS developers. Or perhaps, it is UIDocument that Apple has hacked up to work with legacy NSDocument app. I don't know which.


But I can tell you that the representedObject is just a convenience. Apparently Apple wants people to have a more strict separation between the document object and the user interface in Interface Builder. This could be Apple's take on MVC, or MVVC, or MVVDCFDJMMMC. Just the fact that Apple created a class called "ViewController" means they had no clue to begin with. Building complex OO architectures was never Apple's strong suit.


Bindings, however, are a nice convenience. They "bind" user interface attributes to properties in an object. Instead of manually setting a text value or manually disabling a control, you can bind them to properties. Then, in your code logic, just set properties and the UI gets updated automatically. The only trick is that you can only update the UI on the main thread. That means that you can only upate a bound property on the main thread. Otherwise, bindings are pretty slick.


I do think that your biggest problem is related to state. You are making too many assumptions. You can't assume that your user interface is is ready and available to display your data when you load the data. You also can't assume that you have any data when your are displaying your user interface. You can't assume that your app has been initialized when you load documents. You can't assume that all documents are available when you initialize the app. Make sense?


The app lifecycle does not necessarily run in conjunction with document object lifecycle. The creation, initialization, and initial display of documents is mainly handled by the framework on its own schedule. That makes it a challenge when the framework dips into your code and you have assumptions about the UI and the document object. It gets really tricky when you start looking at the end of a document's lifecycle. Document objects are intimately tied to their windows and may never be directly released. This is where the "representedObject" could be useful. You could control this object very precisely and then let the framework do whatever it is that it does or does not do with the object.


See janabanana's excellent advice. The only part I would disagree with is the idea that using NSLog makes one a "simpleton".

I truly appreciate all the replies and help. I use NSLog statements a lot. A lot. For debugging purposes as well as understanding the flow. Here is my latest:


Uploaded to the App store then realized I could distribute outside the App store. Got the App notarized by Apple and tested with the $ spctl command line tool. Everything looks good. Emailed it to another non-developer Mac and ran it. Got the "App downloaded from Mail are you sure?" message and ran the App. App appears to run normal however when I open a video file I get a blank window with the correct window title (name of the file).


Sooooo, it runs just like if I double clicked on the video file.


Here is how I have the code organized, using the DevelopingADocumentBasedApp as my stating point:


override func read(from url: URL in my Document class gets called with the URL of the video file to open. Document class saves the video file name.


override func makeWindowControllers() in my Document class gets called and makes a new Window Controller, adds it to the list. This method also gets the windowConroller.contentViewController and attaches the video file name to an instance vairable/property of the view controller.


ViewController class loads the video file in viewWillAppear. If the video file is NIL I call the "Open" dialog to ask the user for a file to open.


I'll play aorund a little more however I'm pretty tired working on this issue. maybe give it a few days rest...


Michael

Is the app sandboxed? Your comment about saving the video file name is curious. The read function should read the file, not save the file name for some time later. And are you just saving the file or the entire URL? A sandboxed app may still have access to the URL after the read method completes, but I'm not sure. That is the problem with doing things strangely. If it doesn't work, then you are on your own to debug the system frameworks and figure out why.


I haven't used the AVPlayerView. But it looks like you have to give it an AVPlayer instance. You can create that instance in your read method and save it in the data model. Then, when your view is being constructed, you can give that player to the AVPlayerView.