views:

145

answers:

2

I have an NSView subclass that can be dragged around in its superview. I move the views by calling NSView's setFrameOrigin and setFrameRotation methods in my mouseDragged event handler. The views are both moved and rotated with each call.

I have multiple instances of these views contained by a single superview. The problem I'm having is that, as one view is dragged over another, it leaves artifacts behind on the view it's eclipsing. I recorded a short video of this in action. Unfortunately, due to the video compression the artifacts aren't very visible.

I strongly suspect that this is related to the simultaneous translation and rotation. Quartz Debug reveals that a rectangle of the occluding (or occluded) view is updated as another view is dragged across it (video here); somehow this rectangle is getting miscalculated by the drawing engine, so part of the view that should be redrawn isn't.

The kicker is I have no idea how to fix this. I can't find any way to manually specify the update rect in the docs, nor am I sure that's what needs to happen. Any ideas? Thanks!

A: 

Views aren't really designed to be stacked in an interactive fashion. Can be done, but edge cases abound.

Generally, for this kind of thing you would use a Cell like infrastructure if you want to do in-view dragging (See the Sketch example) and you would use the drag-n-drop infrastructure if you want to drag between views or windows (or apps).

If you really want to drag a transformed view over the top, you'll need to invalidate a rectangle of the view underneath the view being dragged. The rectangle will need to be bigger by a few pixels than the total area (unrotated/untransformed) that is obscured by the view being dragged. The artifacts are, effectively, caused by rounding error; diagonal lines are just an estimate on a raster drawing system.

See the method:

- (void)setNeedsDisplayInRect:(NSRect)invalidRect;
bbum
+1  A: 

You might also consider using CALayers instead of views. Unlike views, layers are intended to be stacked with their siblings.

For a possible least-effort solution, try making the views layer-backed; it may or may not solve this problem, but it's worth a try.

Peter Hosey
This solution appeals to me because I had wanted to use Core Animation to animate certain things these views do (like moving out of the way when another view is dragged onto the same slot). Sadly, simply giving the views a layer doesn't fix the problem (actually drawing to that layer might do the trick). If I were to use a CALayer instead of a view, how would I go about responding to events? CALayers don't inherit from NSRespnder. (Sorry, I'm a Cocoa n00b and basically clueless about Core Animation.)
Ryan Ballantyne
My understanding is that you need at least one view at some level, even if it's a single view hosting and controlling the entire layer hierarchy. In that arrangement, the view would handle events, using messages such as `hitTest:` to determine which layers the user is working with.
Peter Hosey
It turns out I was doing it wrong. Once I called the proper method to give my (programmatically-created) views a CALayer backing, the artifacts went away. Of course, my positioning and rotation got broken, but that's not the question at hand. Thanks for the suggestion!
Ryan Ballantyne