Several months ago there was a flame war amongst many of the better read technical blogs, and from what I read in places such as Daring Fireball and Thought Palace was that Java sucked–and part of the reason why it sucked was that Java Swing sucked.
By extension, of course, the Objective-C Application Framework was a very well thought out framework designed by people who knew how to do user interface design–and beautiful user interface design–as opposed to those neanderthal Java developers at Sun who, in a massive language land grab, built a user interface environment which was so horrific that it doesn’t even entertain being taken seriously as a user interface API.
Okay, I will admit Swing has its places where the designers made some really bad design decisions. The whole Swing pluggable L&F environment, while that may be cool in theory on platforms like Linux (where the idea of a design language is either C or C++), but really blows on Windows or on the Macintosh as it requires extra work to make your application fit in rather than look like a refugee from a 1980’s Motif concentration camp. And the fact that it took Sun until v5.0 of Java to realize that floating windows aren’t a bad thing–a fact that some Window Managers still haven’t figured out, combined with a lack of a reasonable serialization standard–okay, I could bitch about Swing on and on and on. (And please don’t get me started on keyboard shortcuts or the fact that you have to write extra code to make the escape key cancel a modal dialog box.)
But now that I’m starting to crack the mysteries of the Objective-C Application Framework, I’m not exactly seeing the nerdvana of a perfect and well designed UI construction environment created by experienced user interface designers.
To whit, I give you–NSScrollView.
I only have three words for those who think the Objective C Application Framework is superior to Java Swing: What The Frack?
First, NSScrollView really really wants to pin your document to the lower left, according to the Application Framework’s insistence on using a mathematically correct coordinate system with the origin to the lower left. That’s great for the 5 programmers out there who are creating statistics and mathematical plotting packages. But for the rest of us, who are creating text and textually-based packages, flipping the Y axis isn’t just an exercise in mathematically imprecise masturbation. Documents–and by extension computer displays–tend to be read top down. Scrolling tends to focus on top-down reading. Documents should start at the top and go down.
And don’t give me this “just use -isFlipped;” nonsense, as without knowing the magic size settings to fiddle in the Interface Builder, when you have a document that is bigger than the window, on resize you’ll find that your document expands upwards, not downwards like every fracking window out there. (You need to select your custom view, then set its size in the inspector to pin to the lower left instead of the upper left. By pinning it to the lower left, after the coordinate system is flipped, document resizing will fix the upper left as you expand your window. Yes, that made no sense. But it works.)
But that’s not what brings me here today; oh, no. I can live with the quirkiness of some stupid design decision made early on that now everyone winds up living with because it’s too deep in the underlying framework to fix. And God knows there are plenty of these in any framework, where you wish some early on design decision could be taken back, but it’s too late now, and now everyone has to just learn the quirk to get along with your code.
No, what I want to talk about is creating custom horizontal and vertical headers.
In Swing, this is a relatively simple proposition: when building your JScrollPane, you also create a view which manages your custom column header, then add it with jScrollPane.setColumnHeaderView(). This may make creating a table view a little harder–you have to tell the JScrollPane holding your table to use the table view passed to it from your constructed table view item, but it’s all fairly well documented in the Sun “How to use Tables” docs. And for the small percentage of us who want to roll our own tables–because we have a complex drawing task we need to accomplish or because we need to draw something that is almost, but not entirely like, a table–well, we can just build a document view and a header view, and pass them to JScrollPane.
So I went to figure out how to create my own custom header view for my NSScrollView. Okay, perhaps I was going about it the wrong way: perhaps I should have just constructed a table view then used a very large hammer to bang the damn thing into the precise shape I wanted. However, that’s not what brings me here either.
What brings me here is the following snippet of code which I ganked from GNUStep. (As a footnote I find that, for understanding the fringe cases and the assumed design models of an application framework–and every application framework has underlying assumptions as to how the pieces will be strung together which seem obvious to the initiated, but are often completely undocumented by the developers.) What’s great about GNUStep is that, at least for the core elements, at least you can look at the code and learn how things are actually built under the hood, so when gaps show up in Apple’s documentation (and Apple’s documentation has gaps that you can drive a truck through–which disappoints me, given how great pre-NeXT Apple docs were), you at least have a fighting chance to figure out what’s going on without wasting a day.
And here is what I found:
- (void) _synchronizeHeaderAndCornerView { BOOL hadHeaderView = _hasHeaderView; BOOL hadCornerView = _hasCornerView; NSView *aView = nil; _hasHeaderView = ([[self documentView] respondsToSelector: @selector(headerView)] && (aView=[(NSTableView *)[self documentView] headerView])); if (_hasHeaderView == YES) { if (hadHeaderView == NO) { ...
In other words, deep in the bowels of NSScrollView, in order to allow an NSTableView to build a custom table column header, NSScrollView gets the current document being scrolled–and if that document responds to headerView, uses that view as the header.
And notice that unnecessary cast to NSTableView. Not to belabor the point here, but what the hell is this? In any case where you have an undocumented (at least in NSScrollView) hook, mentioned in passing in NSTableView, which provides a hook for giving a header to your custom view–to me, it’s a sure sign of a hack. A kludge. A piece of code tossed in (and this is not GNU’s fault–GNUStep is simply implementing to the best of their ability the OpenStep specification on which Cocoa is based) in order to support a specific case behind the scenes–the need to build a custom table view–rather than creating a general solution then implementing the specific instances for specific cases.
WTF?
Any time in your class hierarchy where you use “supernatural knowledge” of another class–and one of the things that irritates me about Objective-C is how easily someone can create hooks which creates this “supernatural knowledge” through querying an object to see if it implements a poorly documented hook–its a sure sign of something that congealed, rather than something that was designed.
And while that wouldn’t matter much to me: after all, any software project is about making compromises to get something out the door–it wouldn’t irritate me quite so much if the accusations of poor design weren’t leveled so fiercely by those who think Objective C is the epitome of good UI framework design. And I speak as someone who once built my own framework.
Feh!
Wait – you are blaming Apple, because the GNU implementation is lousy?
You think that to level a true accusation of poor design, the accuser must be perfect? [1]
If you’d looked at the docs for NSScrollView. You’d see that there is no such thing as a left header. There is a left and top rulerView though, which do exactly what you want.
Now, I’m not a rabid Apple/NeXT fanboy. God knows, there are warts aplenty in AppKit. The one I’m bumping up against: Set a NSSplitView so the left column has a minSize of 100 pixels. Show the window. Grow the window. Manually move the column divider so the left column is 100 pixels. Now grab the window by the grow corner and shrink the window. Note the left column shrinks proportionately, so it is below the minimum.
———–
1. Fighting proverbs: “Let he who is without sin cast the first stone.[2]” versus “It takes one to know one.”
2. A crowd had gathered to stone the adulterer. Suddenly, Jesus spoke: “Let he who is without sin cast the first stone.” From out of the crowd, a stone flew, hitting the adulterer. Jesus said, “Mother, Please!”
LikeLike
It seems like the whole goal of a UI toolkit is to enable developers to produce applications with a great user experience. Cocoa does this, swing does not.
“By extension, of course, the Objective-C Application Framework was a very well thought out framework designed by people who knew how to do user interface design–and beautiful user interface design–as opposed to those neanderthal Java developers at Sun who, in a massive language land grab, built a user interface environment which was so horrific that it doesn’t even entertain being taken seriously as a user interface API.”
This is basically a truism; it’s not that the design of the apple classes is better, but it’s native. Native trumps non-native every single time. Unlike other non-native toolkits, though, sun did not even make an attempt at emulating native widgets (a la qt).
Personally, i’d rather use swt or jambi, even if the structure of the toolkit was half as good as swing (and I think they are basically on par).
LikeLike
I have been using the GNU source kit as a reference to some of the weirder corners of AppKit–but I’m not flaming Apple because GNU sucks. Rather, I’m flaming Apple because it turns out that the GNU source kit illustrates an odd corner of NSScrollView–that it does query the current NSView object and if it implements -headerView, it grabs the view and constructs an NSClipView and sets up the headerView at the top.
In other words, there is this bit of hidden functionality for NSTableView support buried inside NSScrollView, that is not documented anywhere in NSScrollView–but it is documented, albeit poorly, in NSTableView. (Specifically the second and third paragraphs allude to the fact that if you implement -headerView and -cornerView in your document and put it in an NSScrollView, that NSScrollView will use them for a table header and corner. It’s unclear if NSScrollView then limits it’s search to just using an NSTableView–that is, if NSScrollView checks not only if -headerView and -cornerView exists but that the document is an instance of NSTableView, but as it turns out there is no class checking going on within the implementation of NSScrollView.)
My beef is the fact that NSScrollView has special case code for handling the table header of an NSTableView–and it’s right there, in Apple’s “The Parts of a Table” documentation. (In practice it works: if your document view within an NSScrollView implements -headerView, then you get a header. My code for doing this–and it appears to work on 10.3, 10.4 and 10.5:
(And in my case, I’m constructing something that sort of looks like a table view but doesn’t behave like one. I intend to use a design language which sets my view apart from a table view, so it cannot be confused for one. I originally thought of using an NSRulerView, but what I’m building is not a ruler–no ruler, no markers. And it seems silly to force override every method within NSRulerView just so I can hoist a non-ruler custom view on top of an NSRulerView object.)
Oh, to answer the left header verses top header thing: I know there is no such thing as a left header. And elsewhere on the innartubes I saw a suggestion to someone who wanted to create something that looked like a left header (think the leftmost row marker a spreadsheet which numbers the rows) that he should create two tables–and use Apple’s synchronized scrolling hint to make the left table look like a left header.
Um, ick!
As to the problem you’re talking about–not being able to prevent an NSSplitView left column from shrinking below 100 pixels: the only thing I can think of is overriding the custom view representing the left column and updating the setFrameXXX calls to refuse to shrink below 100 pixels. But I’ll be darned if that would work by itself without overriding NSSplitView as well.
LikeLike
Bem:
“It seems like the whole goal of a UI toolkit is to enable developers to produce applications with a great user experience. Cocoa does this, swing does not.”
To me, the whole goal of a UI toolkit is to make developing good user interfaces easier. However, even a great UI toolkit isn’t going to automatically guarantee a great user experience. Nor will a poor UI toolkit automatically doom any application written in it to providing a poor user experience.
You can create native looking applications in Swing if you are willing to do a little additional work–as I have done elsewhere on this site with the UI Tools framework. (Poke around that source kit and you’ll find places where I had to set various options or hook into Apple EAWT calls to make Java Swing applications look native on the Macintosh.)
The problem with Swing on the Macintosh is not that it’s non-native, but that its default behavior is to do everything completely wrong. But alter a few system settings, hook into a few Apple-specific Java libraries when available, and take some extra care to pay attention to the details–and you can produce an Swing-based Java application that is nearly indistinguishable from any other Macintosh application.
LikeLike
While a lot of the AppKit is well designed, it’s true a lot of it has also congealed over it’s relatively long lifetime. The headerView/cornerView stuff in NSTableView/NSScrollView definitely started life as a *ahem* hack long ago (pre-OSX) and eventually become public API. The AppKit does a lot for you and in doing that has a lot of cross-communication which is typically, but not always, available in public API. If you think either the documentation or API sucks and you want to see it improved, file a bug report with Apple. They are pretty responsive these days.
If you’re interested in a different Cocoa source base for enlightenment, check out Cocotron, http://www.cocotron.org .
Have fun, sounds like you’re just getting started!
– Christopher Lloyd
Cocotron project
LikeLike