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.
Related
My application has a NSTableView configured as a SourceList.
Under the NSTableView are two NSButton two add/remove items in the table.
The table and the buttons are embedded in a NSVisualEffectView (.behindWindow mode).
The result is great and I can see the background through the table and the buttons, BUT, when the app is in background (I give the focus to another app), the tableview and the buttons become black, and the view is not redrawn, as shown in the picture below (taken with an iPhone, because a screenshot does not show the bug!):
And here is the IB structure of the views:
The container view (CustomView) is layer-backed.
Any lead to solve this problem? Thanks in advance !
The problem was that I was trying to have a NSOutlineView translucent (Behind-window blending), but under a TabView.
Apple UI guidelines (here) state the following:
Use an opaque background when a window contains more than one sidebar, and when using a sidebar in a panel or preferences window. All other times, use a translucent background.
Because of the TabView, I was clearly against this rule which had technical side effects (which led to this post :-)).
--> making the NSOutlineView opaque (not as a SourceList) solved the problem.
Are any of your views opaque and implement drawRect:?
I've seen issues like this when a view implements drawRect: but doesn't completely fill the passed-in rect (or all of the rects returned from getRectsBeingDrawn:count:).
My goal
I would like to add a vibrancy effect to a collection view.
The setup
I have the following view hierarchy:
When I use this layout, the background has the vibrancy effect as expected, but the performance is not quite there.
NSCollectionView is optimized to work with layer-backed views, so I enable the CALayer on the collection view's enclosing scroll view.
The problem
If I do this, the visual effect view is no longer visible, and the collection view has a white background.
My question
Is there any way to make a layer-backed view work together with NSVisualEffectView?
So if anyone's wondering, here's the trick:
adding the NSVisualEffectView: correct
making the enclosing scroll view layer-backed: correct
enabling the scroll view to draw a background: wrong
Make sure to either set drawsBackground to false, or disable it in Interface Builder, and your collection view will have the vibrant background, and fast scrolling as well 👍
I have a NSView as the documentView for a NSScrollView. I also have a NSImageView as a subview of the NSView. The image dynamically changes size so the scroll bars become active/inactive at various times. Once the image has changed, I'd like to scroll to a certain point on the image. From within the NSView's drawRect: method, I call
[[myScrollView contentView] scrollToPoint: myPoint];
The scroll bars update and the image appears as I'd like, but as soon as the image is scrolled, a double image appears or parts of the image get cut off. Any help would be appreciated. Thank you.
Sounds like you might want to turn off the "Copy On Scroll" behavior option of the NSScrollView either in Interface Builder or programmatically.
From Scroll View Programming Guide for Mac OS X: How Scrolling Works:
The NSClipView class provides low-level scrolling support through the
scrollToPoint: method. This method translates the origin of the
content view’s bounds rectangle and optimizes redisplay by copying as
much of the rendered document view as remains visible, only asking the
document view to draw newly exposed regions. This usually improves
scrolling performance but may not always be appropriate. You can turn
this behavior off using the NSClipView method setCopiesOnScroll:
passing NO as the parameter. If you do leave copy-on-scroll active, be
sure to scroll the document view programmatically using the NSView
method scrollPoint: method rather than translateOriginToPoint:.
I have an application with an NSTableView in a window. I want to use a CALayer as the background for the entire window, and the table view. In all my my experiments so far, the CALayer always draws over the NSTableView, which is not the effect I'm looking for. Is there a way to make this work, or am I simply out of luck due to the nature of layer-hosting views vs NSViews?
My test setup is a window with the usual NSScrollView/NSTableView combo, and a sibling NSView behind it in the view order. The NSView is set to be layer-hosting with my custom layer within it (just a layer with a backgroundColor set). I've experimented with setting the window's content view to be layer-backed, as well as the table view itself, as well as wrapping the NSScrollView in a layer-backed NSView. The result is always the same.
Thanks for any insight you might be able to provide.
It is simple. all overlapping views or layers should be layer backing or layer hosting for correct ordering.
you can set [tableview setWantsLayer:YES]
or simply check it in the layers tab when editing the interface.
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.