Cocoa: Does there exist an NSView with user resize capability? - cocoa

I want an NSView that can be resized by dragging its the bottom right corner around, just like an NSWindow. I want to be able to embed this NSView into a parent NSView. Is there a component like this in Cocoa or any of its extensions?

If you get more specific with your question, I can get a little more specific with the answer. :-)
There is nothing like this available that I know of, but it's not terribly difficult to create. The decision to make is "who handles drawing the resize grips and resizing / dragging logic?"
Views Handle Their Own
If your user-resizable view handles drawing the grips and responding to the resizing/dragging actions itself, then you have to choose whether you want the grips drawn atop the view's contents or "around the outside." If you want the grips "outside," the "usable area" decreases because your content has to be inset enough to leave room for you to draw the resizing controls, which can complicate drawing and sizing metrics. If you draw the grips "atop" the content, you can avoid this problem.
Container View Handles All Subviews
The alternative is to create a "resizable view container view" that draws the resize grips around any subviews' perimeters and handles the dragging/resizing logic by "bossing the subviews around" when it (the container) receives dragging events on one of its grip areas. Placing the logic here allows any type of subview to be draggable / resizable and gives you the added benefit of only having one instance of the slightly-heavier-weight view (versus many instances of subviews that have the more complicated logic in them).
The Basic Mechanism
Once you've decided that, it's really just a matter of creating your subview, which does the drawing, manages NSTrackingArea instances (for the grip areas), and responds to the appropriate mouse methods (down, moved, etc.). In the case of each subview handling its own, they'll manage their own tracking areas, grip drawing, and mouse moved, setting their own frame in response. In the case of a container view handling all this for its subviews, it will manage all subviews' tracking areas and draw their grips on itself, and set the targeted subview's frame (and the subview is blissfully ignorant of the whole thing).
I hope this helps give you at least a general idea of possible mechanisms. Had I not just gotten up and started my morning coffee, I'd probably be able to write this more succinctly, but there you have it. :-)
EDIT 7 YEARS LATER
Because there wasn't much detail about what the OP wanted, I gave a very generic answer, but I should make a few points:
Always prefer an NSSplitView if it can be made to work for you (ie, if the views align with each other and divide the common container view's space). A split view lets you customize grip areas, etc. and does all of this to your subview for free.
AutoLayout didn't exist when I wrote this answer and it greatly complicates rolling your own solution for the view-handling-multiple-sizable-subviews scenario.
If you really do need a UI element that can be dragged/resized within some container, try your best to get away with using CALayers inside a master view that handles all the layout/sizing logic if you can.
If you can't do the above (ie, the resizable view contains complex controls and layout, has its own NSViewController, etc.), try a hybrid approach (use layers to display cached images of non-selected views and only add a full, interactive sizable subview for the selected item (or subviews for items).
Because of the complexities of AutoLayout, I really can't recommend the real draggable subview approach at all unless it's unavoidable. If you're designing a view that contains movable, sizable things, it's best (and most efficient) to make everything inside it that view's responsibility. Example: a graphics app with lots of shapes should have a Canvas view that represents the shapes (and any GUI decorations like size/drag grips, etc.) using CALayers. This takes advantage of graphics acceleration and is far more efficient than a bunch of (very resource-heavy) NSView subviews. All the move/size/select logic is handled by the "Canvas View" and the only subviews might be overlaid controls (though if your Canvas itself needs to be enclosed in a scroll view, it's best to use NSScrollView machinery to allow stationary overlay views for this purpose).
If designing a view that draws lots of things (for which you should definitely use layers to represent those things) but allows selecting only one thing, the approach of adding a subview is manageable enough even with AutoLayout. If the "selected for editing" thing has lots of complex controls that become visible when editing, an "editor subview" with accompanying view controller makes sense and is a good tradeoff in maintainability (because view controller compartmentalizes all editing functionality/UI handling) vs. container view complexity (because one subview isn't going to break the resource bank and maintaining temporary AutoLayout constraints for keeping its position during container view resizes & editor interactions isn't overly complex).
All of this assumes macOS; if designing for iOS, definitely bend over backwards to use layers and the new (as of this writing) drag and drop machinery, of which I know precious little at present.
In summary, the answer was incomplete as well as somewhat outdated, so I feel my original advice isn't as good as it could be these days.

Instead of using views, you can use windows and set the style mask of the window to NSResizableWindowMask.
Another option is using an NSSplitView, if you have two resizable, contiguous subviews.

Related

Custom NSView in a NSScrollView that takes up the whole visible space

I have a custom NSView subclass, and I put it in a NSScrollView, and the basics are working fine. It always takes up the full width of its available space, so its constraints take up the whole width of the scroll view. How much vertical space it takes up depends on how much data it has to display, so it can scroll vertically, only. That's all great.
The catch is that if my custom view needs less space than the NSScrollView that it's in, it doesn't expand to fill the visible area. In cases where there's less data than fits in the visible area, I want it to expand downwards -- so that space is available as a drag-and-drop target, among other things.
I've tried changing its "hugging priority". I've tried adding a constraint to keep the bottom below the NSClipView's bottom. I can't come up with any constraint-based solution to fix this (though I haven't ruled out the possibility, either).
I've tried catching the notifications when the NSScrollView changes size, and adjusting the custom view's frame if it's too small, but (presumably because I can only change its current frame, while the layout system does all layout later) I can't seem to make this work, either.
Is there a trick for adding a view to an NSScrollView such that it expands to the bottom of the visible area, whenever it would otherwise be too short? It seems like it should be so simple, and I've done it before in cases where I just call -setFrame on everything manually, but once you move to the autolayout world, that approach stops working.
Without knowing what your constraints and content hugging and content compression actually look like...
Content hugging with at least one edge having >= constraint to superview might do it but you might need to adjust priorities.
You might also need to make sure you've implemented intrinsicContentSize in your custom view class this tells the content hugging and compression what they need to knowfirst.

Drawing an NSTableView's background outside of its bounds

I'm having a problem since Lion introduced elastic scrolling (pictured below). When you scroll my table view (cell-based, with alternating row colors) beyond its bounds, the background doesn't draw. I've tried doing my own drawing in -[drawBackgroundInClipRect:], but it seems like you can't exceed the bounds of the table view. How can I extend the background into elastic scrolling territory?
In Answer to Your Question
A view drawing outside its bounds is generally a no-no. When using alternating background colors, the NSTableView draws its background directly. But in a view-based table view, NSTableRowView is used and if it has its own background color, this is poses even more challenge.
The Bad News
The assemblage of NSScrollView (and its various parts), NSTableView, and NSTableHeaderView is complicated on its own. Once you throw view-based functionality into the mix (where each row has a view and each cell has their own view, and each are reused, animated around, etc.), overriding this behavior "is no way to go through life, son!" ;-)
The Good News
The issue of alternating background colors not extending in an elastically-stretched scrolled table view has been resolved (at least on 10.10, that I can tell), so this is no longer an issue unless you have row/cell views with custom backgrounds or just background colors.
A General Solution For Custom Document (Scrolled) Views
For all other scrolled views with ruled backgrounds you wish to extend for elastic scrolling, you'll need a custom NSScrollView subclass that expects a document view (your custom scrolled view) to conform to a protocol you define (like -(NSImage *)backgroundImageForClipViewBounds:). The scroll view would observe its content view (NSClipView) for bounds change notifications and flag itself for display. When the scroll view's -drawRect: is called, it would then ask the scrolled view for its -backgroundImageForClipViewBounds: (via your protocol) and draw it into itself, thereby making the scrolled view's "infinite background" its own.
I haven't tested this theory but I believe it would work. I hope this helps.

How do you animate a scroll and zoom atomically?

I have a custom view in my application, which is layer-backed and embedded in an NSScrollView. I allow the user to zoom in (which is accomplished by increasing the size of my custom view). I'm having trouble zooming in on an arbitrary point, though, since the NSScrollView keeps getting in the way and causing the view to jump around (typically to the view's origin) before I point it to the new scroll point. I would really like to use a CAScrollLayer, since I know I could definitely get the zooming right with it and have it move smoothly, but then I lose all built-in scrolling facilities.
Is there any way to leverage CAScrollLayer within an NSScrollView, possibly backing the NSClipView? If not, what purpose does CAScrollLayer actually serve? Is it possible, with a different approach, to change my view's size and the scroll point atomically and have that animate?
In short, is CAScrollLayer completely useless, or mostly useless?
Update
I've gotten my inner view to jump around less by making a CALayer subclass to display my view's contents. Rather than sizing with layout constraints, I have it sizing in an override of -resizeWithOldSuperlayerSize:. I still can't change the frame size and origin of my view simultaneously and get a smooth animation, though. To get a sense of what I'm looking for, open an image in Preview and zoom in and out. It zooms about the center of the image in a smooth manner.
In the limit, you can use an NSScroller instead; that way you would be able to use CAScrollLayer, if that’s your preferred implementation.
Note that on some (older) versions of Mac OS X, NSScroller has a bug that causes it to invoke an Apple private method on its containing view. You’ll know if this happens because you’ll get an exception about your custom view not responding to a method starting with an ‘_’.

Resizing Nested NSViews

In order to categorize a wide variety of unique views, I have an elaborate setup: main categories are selected via a toolbar, and then specific panes are selected in a category's NSScrollView. This looks like: window -> NSViewController controlling five views -> sub-NSViewController for each view controlling X views -> each view contains a core-plot graph. In short, nested NSViewControllers with a core-plot CPLayerHostingView at the end of nearly every path.
Before I even get to my question, feel free to point out that this is a poor implementation. In terms of user-friendliness, I think it makes sense, but the sheer number of nested objects makes me wonder if there's a better way.
Now then, assuming I've designed it the best possible way, the question itself: suppose I have selected a category and then a sub-item within, and am looking at a rendered graph. I desire the graph to resize appropriately if the window is resized. In Interface Builder I have done everything necessary to make this happen: everything from the CPLayerHostingView to the NSView in the main window have been set to autosize in all directions. Despite this, if I resize at runtime, the graph stays still and does not resize or move. In a design with zero or one NSView tiers this would be much simpler to debug, but I'm out of ideas in this scenario.
What tricks, programmatic or IB-based, can I use to make sure an NSView resizes according to a window resize many, many levels up?
Not only do you need to set the springs and struts, but you also need to make sure "Autoresizes Subviews" is checked.

How to imitate the workflow view of Automator?

I’m starting to develop my first full-blown Cocoa application containing a view which I would like to behave (and look) similar to Automator’s AMWorkflowView.
The basic features I’d like to achieve:
Positioning of subviews
Display of subviews in expanded / collapsed states
Multiple selection
Drag and drop
In order to get accustomed to Cocoa, I started with a custom NSView which mainly served as a container for the custom subviews and handled their positioning and multiple selection.
The subviews are also subclasses of NSView, and contain a variable amount of views themselves, like buttons, labels and popup menus, and therefore can have different heights.
This worked quite well, but before going on, I want to make sure to have everything neat and tidy according to the MVC pattern.
I suspect that there already is a class in Cocoa that facilitates the implementation of a view container, like maybe NSCollectionView.
It seems that there is no (easy) way to display differently sized views in an NSCollectionView, though. Should I continue implementing my custom NSView (probably using an NSArrayController for selection and sorting support), or are there better ways to go?
Any help is much appreciated
Unfortunately the answer is you'll have to roll your own. NSCollectionView does not allow for variable-sized items (which also rules out expanded/collapsed states).
For a limited number of items, you can accomplish this rather easily (you just need a container view that arranges the subviews properly when asked to layout, then you need to make sure you re-layout when things change). For many subviews, however, you'll need to take care to be as efficient as possible. This can start with laying out as little as possible (only those "after" the resized view, for example) and get as complex as caching a visual representation of a prototype view, drawing the cached images (fast!) for all but the view being edited, and only using/positioning a "real" view for the view being edited.
Drag and drop works the same as it always has, but none of the above accounts for the pretty animation NSCollectionView gives you. :-) It's fast and beautifully-animated precisely because all the subviews are uniform (so the layout calculations are fast and simple). Once you add irregular sizes, the problem becomes significantly more complicated.
The bottom line: If you need variably-sized views, NSCollectionView will not work and you'll need to roll your own or find someone else's shared code, but performance and beautiful animation will not be easy.

Resources