How to open a window with filepath given

I'm trying to do the fairly ordinary task of getting a file from NSOpenPanel (from File > Open) and then create a window, passing the filepath to the window so that it can run an AVPlayer.

My AppDelegate is:


@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBAction func browseFile(sender: AnyObject) {
  
        let dialog = NSOpenPanel();
        if (dialog.runModal() == NSModalResponseOK) {
            let result = dialog.url
      
            if (result != nil) {
          
            // Pass the URL to the ViewController
        } else {
            return
        }
        }
    }


My ViewController is:


class ViewController: NSViewController {
    @IBOutlet weak var playerView: AVPlayerView!
        override func viewDidLoad() {
            super.viewDidLoad()
          // Get the URL somehow
            let player = AVPlayer(url: url)
            playerView.player = player
        }


How do I join the two? Ideally, in such as way that I can create multiple windows.
Am I missing some source of basic templates for these kind of very basic operations?

You have to declare a controller for each window you want to open (they should be global var visible from the whole app); it will be of the class MyWindowController that you have to create.

myFirstWindow : MyWindowController

mySecondWindow: MyWindowController


You create a xib (MyWindowXib) for the window and give its fileowner the class MyWindowController


In MyWindowController class definition,

- you can define the windowNibName

    override var windowNibName: String! {
        return "MyWindowXib"
    }


- So, init(window) will call the appropriate xib.

   override init(window: NSWindow!) {
        super.init(window: window)
    }


You can define a convenience init to open with the appropriate content read from URL.


Then, in browseFile, you can call

myFirstWindow = MyWindowController(data : ....)

myFirstWindow!.showWindow(self)

1. I have to declare a controller for every window that the app opens? So how do I do that programmatically to allow as many windows as I've got memory for?

2. How does the xib differ from the window I've already set up in Interface Builder?

3. Is your "MyWindowController" the same as my "ViewController"?

I have to declare a controller for every window that the app opens? So how do I do that programmatically to allow as many windows as I've got memory for?

Effectively, one controller for each window or view. You can create an array of controllers and add a new instance each time you open a new window.

Interesting discussion here

h ttp://stackoverflow.com/questions/11060935/difference-between-nswindowcontroller-vs-nsviewcontroller

and more uptodate

h ttps://www.raywenderlich.com/153844/macos-view-controllers-tutorial


How does the xib differ from the window I've already set up in Interface Builder?

That's the same ; this window must be in a xib.

Is your "MyWindowController" the same as my "ViewController"?

I personnaly prefer working with windowControllers (in OSX Apps, which is the case here I understant), to be able to manage all the window's attributes.

But you can get most of it with views if you prefer (see www.raywenderlich.com thread)

Is there a way to duplicate what I've already done in IB ? (I guess I'm asking - why do it in the storyboard if you've got to repeat it elsewhere?)


And isn't this something that pretty much everyone writing a Swift app that opens windows from files has to deal with, so aren't there templates or sample code somewhere?


I'm still not sure how what you've written slots in with my code, but I'll play with it and see if it'll work. Thanks.

Sure, anyone writing muti windows app for MacOS has to deal with it ; but I didn't find the template for it.


But look, it is in fact pretty straightforward :

- create a windowController class for all the windows, where you will manage all actions (even drag and drop if you ever need) ; the init will refer to the window xib name (overloading windowNibName is very convenient for this)

- create a xib for the window ; you will have different views in the window

- make the fileowner of the xib belong to the windowController class


Now, you're ready to fly, creating an instance of windowController for each window you want to manage.

I think it's a mistake to start using XIBs in this case, when the OP is already using storyboards. The solution is parallel to what you're suggesting, but doesn't need to use XIBs.


The OP's problem is really two problems. First, he needs to open a window at need. Second, he needs to communicate the file to the window (or more likely when using storyboards, to the root view controller). The first one is harder, or at least requires more familiarity with Cocoa.


Standard app templates provide for (basically) two scenarios: a single-window app, whose window is the initial controller of the main storyboard; and a document-based app, whose prototypical window is in the main storyboard, and is instantiated via NSDocument behavior.


This case is similar to a document-based app, but doesn't need the NSDocument machinery. To create a new window, where the window is part of the main storyboard, you can use the same technique NSDocument does:


let storyboard = NSStoryboard (name: "Main", bundle: nil)
let windowController = storyboard.instantiateController (withIdentifier: "Document Window Controller") as! NSWindowController


but you would do this in the app delegate, once you get to the point of knowing you want a new window. Note that this assumes that the window in the storyboard is not marked as the initial controller (uncheck the checkbox in the attribute inspector), is set for multiple instantiation (again in the inspector), has the correct class (if you use a custom subclass of NSWindowController), and that the code specifies the storyboard identifier that's actually set for the window in the storyboard.


However, regardless of how you cause your windows to be created (storyboard or XIB), you still have a bit of housekeeping work to do. For example, you will need to make sure you maintain the Window menu's list of open windows correctly, that you maintain the list of recent files on the File menu correctly, that you implement window state restoration, and other such things that users expect from non-crappy apps.


For that reason, it might be easier to actually make a document-based app after all, and just remove anything that relates to modifying or saving documents. That way, you get a lot of window-related behavior for free.


I don't know what the best approach is in this case. I'd be inclined to try the document-based approach first. Either way, there's going to be a learning curve, and pitfalls, before getting the windows open successfully.

In my case, where I have a lot of windows, I decided not to go to document based nor to single window and storyboard (I still find storyboards not very well suited to large Mac apps).


I did prefer to keep the flexibility of creating windowControllers as needed, even if that requires more code and efforts. That choice was made a year ago for the app, may be it could be different today ?


Edited

As for housekeeping, some is made "automatically" : for instance, windows menu is updated when I set

windowController!.window?.title = "the title"

Nothing has really changed, but XIBs vs. storyboard is a matter of preference, and gaining a core competency in XIBs is just more homework. Note that you can put windows in separate storyboard files, and load them on demand. It's the same principle as separate XIB files, but uses the storyboard metaphor and APIs.


I didn't recommend a multi-storyboard approach here because there is (apparently) only one kind of window. In that case, it may as well live in the main storyboard.

Yes, a Document-based app is the way. Seems obvious now. 😊

For free, I automatically get a file open dialog linked up to File > Open, and File > New works as well, and the windows are listed in the Windows menu.


However, I can't see how to set the filetypes that it can open (as I don't have any NSOpenPanel code!), and I don't know how to pass that into to the ViewController. Presumably I'll have to override something.


I foolishly set the document file extension as "av" in the Setup Wizard, but that doesn't seem to appear anywhere in Xcode, and in any case, I want the app to handle a range of different file extensions.

Accepted Answer

>> to set the filetypes that it can open

Select the project entry at the top of the navigator pane in Xcode, then select the Info tab in the editor pane and expand the "Document Types" section. What you should see is the "Extensions" field showing your "av" extension, and the "Identifier" field blank. Make the following changes:


— Change the Identifier field to "public.data". This is a UTI (uniform type identifier) that basically means "any file".


— Change the "Role" popup to None. This should prevent your app from ever being chosen when a file icon is double-clicked in the Finder, and restricts your app to getting files via the Open menu item, or by explicitly dragging Finder icons onto your app icon.


— Remove the "av" from the Extensions field, since that field is ignored when you specify an identifier.


— Check that there are no entries in the Exported or Imported UTI sections, below.


Once you've done that, your Open panel should show all files by default. In the rest of your code, NSDocument doesn't really do anything with the file type, except when you're saving a file, which you're not going to do. In overrides and other methods that have a "type" parameter, you can just ignore it.


Getting to the view controller shouldn't be too hard. The easiest way is probably to override NSDocument's windowControllerDidLoadNib(_:) method. From the window controller parameter, you can reference the "contentViewController" property to get the root view controller, cast it to your subtype, and pass it any needed parameters by setting a property, or invoking a view controller method.


Or, you can do it the other way around. The trick in that case is to find a way of telling the view controller what its window controller is, since there's no direct reference by default. One way is to subclass NSWindowController and have it tell the view controller in windowDidLoad. Another way is to defer this until the view controller's view has been added to its window view hierarchy (i.e. wait until viewWillAppear is called), where you can find the window controller as self.view.window.windowController, with appropriate "?" for the optionals.


Either way, from the window controller, you can get the document as windowController.document, and the document is normally where the data model is held. (In this case, you'll just need the document's "fileURL" property.)


The other piece of housekeeping you need to take care of is to prevent the accidental creation of "untitled" documents. That means overriding some NSApplicationDelegate methods: "applicationShouldOpenUntitledFile(_:)" and "applicationOpenUntitledFile(_:)" to return false, and perhaps "applicationShouldHandleReopen(_:hasVisibleWindows:)" to do something relevant.

"From the window controller parameter, you can reference the "contentViewController" property to get the root view controller, cast it to your subtype, and pass it any needed parameters by setting a property, or invoking a view controller method."


I can tell this is the crucial bit. But also the bit where my comprehension fails. 😊 I'll play around and see what happens.


Thanks.

How to open a window with filepath given
 
 
Q