As of 2018, what is the correct way to create a custom control with AppKit/Cocoa?
The traditional technique has been to subclass NSControl and/or NSCell for the type of custom control you're implementing. (Such as an NSButton that does custom drawing.)
However, in WWDC 2014-204 Apple stated that NSCell is on its way to "Formal Deprecation". In WWDC 2012-217, Apple suggests subclassing NSView and taking advantage of layer-backing and layer properties to draw a simple button.
If you subclass NSView to create a custom control (such as a "better button") then you lose a lot of functionality that NSControl offers, such as pre-wired action and target properties, mouse-tracking, keyboard activation and probably a bunch of other things I'm not even aware of.
If you subclass NSControl, or even NSButton, what is the correct way to take over all of the drawing? Apple's Programming Guide has not been updated regarding this. In particular, can an NSButton subclass just override all the draw... methods from itself and NSCell and then just do what it wants to do in updateLayer? Is there any guarantee that the existing NSButton/NSButtonCell drawing code won't still do any drawing?
What if you wanted to build a button with a custom background but still uses a string-based title or attributedTitle? NSButton offers this but how does the title drawing interact with updateLayer?
My use-case is to create a custom button that offer more visual states and more visual designs than a traditional NSButton. Using a layer-backed NSView and updateLayer makes implementing all of the different states a breeze, but I'd prefer to subclass NSControl or even NSButton so that I also retain all of the functionality that those classes already offer.
Edit 1: Changed wording based on comments below to more accurately reflect the current deprecation state of NSCell.
Related
I have managed to get the IBDesignable/IBInspectable attributes working with direct subclasses of NSView but not with a direct subclass of NSButton. This is causing me to question if in fact the Cocoa implementation is somehow limited to NSView only.
Almost every example on the web (and Apple WWDC 2014 Xcode video) use NSView and then drag a custom view component from the library onto the canvas (and then change its class).
Is it possible to use IBDesignable with subclasses of NSControl and NSButton etc...? I have seen many examples on the web using UIButton.
If it is possible, then what are you supposed to drag from the library onto the canvas? It doesn't make sense for it to be a "custom view". On the other hand, there is no "custom control" available.
To be clear, I can get the IBInspectable attribute to show up at design time; but any changes don't seem to live render at design time.
The workaround is to wrap any custom NSButton I want to create within an NSView (via composition) but this seems like a bit of a hack...
I started playing around with a custom NSButton and NSButtonCell.
Dragging a button from the library onto the canvas and changing its class and the cell class doesn't live render. I think this is because Interface Builder still does a lot of custom things to setup NSButtonCell.
What works fine for me is dragging a custom view from the library onto the canvas and set its class. For this to work you need to setup the cell inside NSButtons -initWithCoder:.
Also I found a sample from Apple with a layer-backed custom Checkbox.
You need to drag an NSButton onto the view, then set the Custom Class to your specific NSButton descendant. Not sure why it doesn't work when you start with an NSView.
What can give you a hint is that the NSButton specific attributes aren't in the "Attributes Inspector". Hence there must be some setup at the time you drag the control onto the view.
What would be the best way to implement (using Cocoa) a DataView as presented in this example:
1. IKImageBrowserView
The image below is taken directly from Apple's Image Kit documentation and does exactly what you need.
2. NSCollectionView
NSCollectionView is more general because you are not restricted to just presenting images; you can present a collection of any NSView subclass you like. Try this if you need more customisation. For example, you could have a NSImageView, NSTextField and NSButton in your subclass if you needed to have a more complex user interface.
I've implemented a browser that shares NSTreeController with NSOutlineView so I can easily switch between them and keep the states in sync. Implementing drag support for NSOutlineView is simple, but it appears that NSBrowser cannot support dragging while using bindings. Has anyone been able to support dragging from NSBrowser while also using bindings?
I solved this by creating custom subclasses of NSBrowser, NSMatrix, and NSBrowserCell. I had to force NSBrowser to use NSRadioModeMatrix mode (single cell) by setting this mode in NSBrowser subclass -mouseDown method because it was constantly being reset.
In the NSBrowserCell subclass, I implemented trackMouse:inRect:ofView:untilMouseUp: by simply having superclass do it's thing, but returned NO so tracking would occur, which allowed mouseDragged: to be called in my NSMatrix subclass. In mouseDragged: I then did all the stuff to get the mouse location, construct a mouse image, and used [self dragImage:at:offset:pasteboard:source:slideBack:].
I'm not sure if this is the best approach, but it worked. It only works when there is a single selection allowed in NSBrowser.
I've been reading through the Apple documentation about NSButtons and cells and I really can't seem to understand the distinction between the two. Adding to this complexity, it looks like both of them have a large overlap of methods like setTitle: etc. and I'm not sure which ones I should use.
Can anyone explain what the basic differences are?
Thanks,
Teja
You can think of a control as a cell's representative, or "agent".1 The control is an NSView, which means two important things in these circumstances. First, that it represents an area of a window to be drawn in. Second, that it accepts user interaction.2
The control doesn't do very much itself, though. The cell is the thing that does all the work -- notice, for example, that cells can draw themselves into a given frame. This is the responsibility of a view, but the control defers that to the cell, simply providing an area for drawing.
Likewise, when a user clicks on a control, the control receives the event and figures out what it means, but the work of performing an action in response to the event is passed on to the cell.
If you look at the docs for various control/cell pairs (NSButton and NSButtonCell being one such pair), you will see mention of "cover" methods. This means that the control has methods with the same names as its counterpart cell, which simply call through to the cell's. That's the source of the duplication that you mentioned. When you want to use one of these methods, call it on the control -- as the public face of the pair, it will most likely simply ask the cell anyways.
The best Apple-provided description of the interaction is "How Controls and Cells Interact" in the Control and Cell Programming Topics guide.
1In the sense of a actor having an agent who procures gigs.
2This is not strictly true of all views; it's actually a feature of NSResponder from which NSView inherits.
Excerpted from Cocoa Programming for OS X: The Big Nerd Ranch Guide
“A cell handles the implementation details of the control. For the
most part, the cell’s properties and methods are “covered” by
identical properties and methods in the corresponding control. For
instance, when you change the title on a label, the label’s cell is
doing the work, but you interact with the label’s stringValue
property.
Cells have a long history in Cocoa. They were originally
added to address performance issues: many of the basic tasks of a
control were handed off to the cell, which could do them more
efficiently than the control.
Mac computers are much more powerful
than they were in those days, and cells have become an encumbrance.
Apple has stated that it is in the process of deprecating cells, but
you will still see them in your document outline and in older code.”
NSButtonCell is a subclass of NSActionCell used to implement the user interfaces of push buttons, switches, and radio buttons. It can also be used for any other region of a view that's designed to send a message to a target when clicked.
The NSButton subclass of NSControl uses a single NSButtonCell. To create groups of switches or radio buttons, use an NSMatrix holding a set of NSButtonCells.
NSButton Cell has alot more cutomizable options for drawing and behaviour. The differences are better seen in the classes they inherit from (NSButtonCell inherits from the ActionCell class, while the NSButton inherits from the NSControl class.
Take a look at the documentation better:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSButton_Class/Reference/Reference.html
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSButtonCell_Class/Reference/Reference.html
You should choose based on how you want the buttons to be drawn and behave. If its a simple button you want, use NSButton.
Will the individual UML diagram shapes be NSView subclasses or NSBezierPaths? How are the diagrams created and managed?
One way to do this is to:
Create a document-based app
Design model classes for the different objects the end-user will be able to draw in your canvas, all sharing one abstract superclass
In your CanvasView class, implement drawRect and have it call the NSDocument subclass, or for more granular classes it's viewcontroller, to get all the objects that should be drawn in the right order to draw them.
For each of these objects, call a drawInteriorInView:rect: method or something similar that they all have implemented, from within your CanvasView's drawRect: implementation.
The advantage of such a granular design is that you can decide to replace NSBezierPath drawing with straight CoreGraphics calls if you find a need to do so, without having to completely re-architect the app.
Typical Cocoa controls, like for instance a tableView, implement a bunch of different drawing methods, one for the background, one for the gridlines, etc. etc. all of them called (when applicable) from the view's drawRect:.
Or you could of course look at GCDrawKit, which seems to have a pretty functional implementation. Especially check out the sample app that comes with it.
Have you looked at OmniGraffle? It may do what you need.
[non-programming-related answer...]
Have you looked at the Sketch example project, found in /Developer/Examples/AppKit? It should get you at least halfway to where you're going.
You would typically start with an NSView subclass to represent your "canvas" and handle drawing and mouse/keyboard events. You would probably use NSBezierPath inside your drawing methods to fill and outline the shapes. Depending on how complex the drawing code is, you might put everything in your NSView subclass, or make an NSCell subclass that would take some work out of the NSView. In either case you would want to define a data source protocol (or create bindings) to provide data to the NSView from the objects in your data model which represent UML items.
Core Animation would be worth considering too, although I would start with NSView at the beginning, at least for a simple prototype.