Where should document-related actions for a Cocoa app be implemented? - cocoa

I'm writing a document-based Cocoa app that's basically a graphical editing program. I want the user to be able to show/hide non-modal windows (such as an inspector window). Since these windows would be shown/hidden from menu items, where is the "best" place to implement the actions, such as - (IBAction)toggleInspector:(id)sender?
I've seen that in the Sketch example code these are implemented in the app delegate, and the window controller instances are kept there as well, but that feels like more of a convenient place to put it than the most "graceful" place. Additionally, since this inspector would only be relevant when a document is open it feels like it should be associated more with the document's main NSWindowController than the app.

Additionally, since this inspector would only be relevant when a document is open it feels like it should be associated more with the document's main NSWindowController than the app.
No, because the Inspector is shared amongst all documents; there isn't one Inspector per document.
Remember that a single process can have multiple documents open; these are not multiple processes, one per document, as on Windows, but multiple documents within a single process. There is one Inspector per process, shared amongst all of the documents, and it applies to whichever of those documents is frontmost at the time.
I would give the Inspector its own controller, instantiated in the MainMenu nib.

Related

create a program with Xcode-like interface

On Mac it is usual that there is a "hidden" main window.
The usual example is "Text Edit". When you open a file you with you don't see a "main frame". Instead every single file will be opened in its own "Text Edit" instance. This is OSX way of emulating the so-called MDI interface.
However, there is an exception. If you open Xcode and open the project there, you can click on the file and it will be open inside the main Xcode window. And if you double click the file it will be opened in its own independent editor window, keeping the main Xcode window visible.
My question would be: do I need to do anything special in order to make my program behave like an Xcode? Should I use different class for the main frame or maybe react differently on the opening document event?
Any hints/pointers where to look or even to the official Apple documentation would be helpful.
The TextEdit behavior you're describing is much more like “SDI” than “MDI”, and the terms “SDI” and “MDI” weren't even needed until Microsoft invented MDI long after Xerox invented the SDI-type interface of which macOS is a derivative.
Anyway, I think you are misunderstanding Xcode's behavior. You seem to think “its own independent editor window” is a different kind of window than “the main Xcode window”. But in fact the new window is of the same kind as the old window, with some optional parts hidden. You can show those hidden parts and make the new window look exactly like the old window. Demo:
The ability to open multiple windows showing the same document (or, in Xcode's case, project) is a matter of software architecture. If you carefully design your app so that multiple windows can share a single model object graph, and can be notified and redraw themselves when the object graph changes, then you have an app that supports multiple windows showing the same document. If you want multiple kinds of windows showing the same document, nothing about Cocoa stands in your way. As a matter of fact, Xcode does have at least one other kind of window in which it shows some properties of a project:
That project settings sheet is really another window; macOS keeps it attached to the main window, but it is in fact an instance of NSWindow (or a subclass of NSWindow), no doubt with its own custom window controller that references the same project objects as the main window.
If you use the Cocoa NSDocument architecture, then a small amount of multi-window support is built-in: an NSDocument knows about its associated windows (via their window controllers). If you want to use the NSDocument architecture, you should read Document-Based App Programming Guide for Mac.
It is unclear what you are after. The traditional Mac UI has been one window per document - i.e. SDI with a single instance of the app running multiple windows - but there has always been the ability for any app to organise the content of that window as it sees fit, including showing multiple "documents" within one window - i.e MDI type UI.
Apps approach such "MDI" in different ways, e.g. some use panes (views) and others tabs. From macOS Sierra the standard NSWindow supports tabs, this system is (semi)automatic for standard document apps. Read Apple's NSWindow Automatic Window Tabbing section in the Sierra release notes for more details.
If you wish to use multiple panes - e.g. like Xcode - you just use views (NSView) and arrange them how you wish.
HTH

How to choose a specific instance of a custom class added to Interface Builder, or suggest a better organization

I'm writing a single-window, non-document-based app for Mac OSX. I'm coming from an iOS project where we avoided Interface Builder like the plague. I'm forcing myself to use it for the Mac project just for the experience.
My program's menu and its main window are in two different nibs. The window contains a toolbar. Of course many of the toolbar and menu items are representing the same function, so I want to route them through the same code. Initially I put all the handlers for the menu/toolbar in my application controller and wired them to the menu/toolbar using Interface Builder. Then I realized many of the operations affect the window and its contents, not the entire program, so I thought it more appropriate to put those methods in the main window controller.
To support this I added an NSObject to my window nib, changed its class to my window controller class, and wired those up.
Of course what I'm seeing now is that my app controller creates an instance of the window controller when it creates my one-and-only window, AND the nib for the menu creates another instance of the window controller when it builds the interface from the nib. That may or may not prove to be problematic, but it seems wasteful. Seems like everybody should be talking to the one-and-only window controller I create in applicationDidFinishLaunching.
So question 1 is "How do I tell my program to use the existing window controller when building the user interface from the nib?"
If the answer to question 1 is "You don't" then I'm looking for a suggested organizational pattern to follow. Do I put all the menu/toolbar handlers in the app controller and route from there? Or is there some other common way of doing this that is consistent with the zeitgeist of Cocoa?

How to read a global selection using MacRuby?

How can I read a global UI selection within MacRuby? For instance, of selected text in Preview.
Having no experience in Ruby and Cocoa, I've decided to take a plunge and to write a small dictionary app to aid myself with translation. All the pieces are ready, I just need to know how to read selected text on hotkey.
You can't, because there isn't one.
There is not one global selection. There is one text selection per text view (or other selectable-text-containing view). A window may have any number of such views, an application may have any number of such windows open, and the user may have any number of such applications running.
A further problem is that not all applications are Cocoa. Of those that are, most are accessible, but not all; custom views may trip you up (think of the Text tool in a graphics editor, for example). If the user selects text in a non-Cocoa application, chances are you won't be able to read it.
If you want to access the selected text in the focused view in the focused window in the focused application, the best way to do that is to make your application provide a Service, which the user can invoke from nearly any Cocoa application and some of the more enlightened Carbon apps. That's the best you can do.
Apple's own Dictionary gets special treatment in AppKit (including the availability of a floating Dictionary panel in Cocoa and Carbon apps), but otherwise works the same way: It provides a service that shows up in every Services menu (if the user hasn't turned it off).

Remove save feature in cocoa document-based application

I am currently working on a web browser project from Apple's Mac Dev site.
I have completed the project, but have a bit of a problem. I have created the project as a Document-Based Cocoa Application, and now whenever I enter text in any text field on the web, the red traffic light button shows a black dot in the middle that signifies an unsaved document. When I try to close the window or entirely quit the application, a warning pops up like that in TextEdit or Pages where it alerts me to unsaved changes. It's not too much of a problem, but I would like if someone could please tell me how to remove that feature of a Cocoa Document-Based Application.
Why a document based application if your application isn't document based? Document based applications inherently include the concept of open and save; it is a fundamental part of what they are.
In any case, you could "work around" this by configuring NSDocument appropriately; override the proper methods and otherwise muck with the change count & dirty state of the document. But it'll be just that, a workaround. The documentation for NSDocument has all the information you need.
A cleaner overall solution would be to refactor your app to not use NSDocument. Creating multiple windows is quite straightforward in Cocoa (an action method tied to a menu item where the action method loads a nib file; if I remember correctly, you could even use NSWindowController still).
An easier solution would simply be to override the isDocumentEdited method to always return NO.
- (BOOL)isDocumentEdited {
return NO;
}

Getting started with cocoa for os x?

I am familiar with iOS programming, but I don't know where to put my logic when I start a Cocoa project for OS X. Are there any good online resources for transitioning to OS X from iOS?
EDIT:
Thanks for the help so far. My main question is where my code goes in the default template.
My main question is where my code goes in the default template.
Assuming a single-window app, I make a separate custom controller to own that window, and I have the application object's delegate own that controller.
So:
The application object's delegate
Owns the custom controller.
Creates it in applicationWillFinishLaunching: (note: not Did; I change this).
Releases it (and nils out the instance variable) in applicationWillTerminate:.
Does not own the primary window (I remove that ivar and all references to it).
Responds to applicationShouldTerminateAfterLastWindowClosed: with YES, so that the user can quit the application either by specifically quitting or by closing the primary window.
May also respond to application:openFile: or application:openFiles:, typically by passing the file(s) along to the controller to import or something.
The MainMenu nib
Owns the application's delegate.
Does not contain the primary window (I remove the blank one).
The primary-window controller
Owns the primary window.
Owns the model displayed in the window.
Creates initial/empty model in init.
Loads the primary-window nib with itself as owner in init.
Closes and releases window (first) and releases model (second) and subordinate controllers (third) in dealloc.
The primary-window nib
Named similarly to the controller; e.g., “SnippetListController” for the controller and “SnippetList” for the nib.
Contains the primary window.
Primary window has “visible on launch” (really on nib load) turned on and “release when closed” turned off.
The advantage of all of this is that the lifetime of every object but the application's delegate is clear and explicit, as is the area of responsibility. My application's delegate is little more than that; the only thing it does that isn't specifically an app-delegate job is owning the primary-window controller. Likewise, the primary-window controller does nothing but own the primary window and whatever model I have to show in that window. And at quit time, everything but the application's delegate gets deallocked.
For document-based apps, I stick pretty close to the template. I delete any template methods I don't need to customize, and I may switch one or both of the I/O methods to one of the other versions (e.g., from readFromData:ofType:error: to readFromURL:ofType:error:), and maybe delete the write method if I'm writing a viewer only; that's about it.
Dive in.
Seriously just jump in with both feet. Find something that you want to make and just make it.
From what you've said you've already got a fair bit of experience with Objective-C and Cocoa and most things carry straight across.

Resources