Custom Draw in NSView without erasing the previous background - macos

I am trying to plot few points dynamically on my Custom View using Quartz functions so that i get a complete graph. I handle drawing of lines inside the drawRect method of Custom View where I get the current context and draw the lines. But, they get erased when i try to draw a new line. I want to have those lines also visible along with the new ones drawn. Please let me know how to do this. I can't store all the points together and draw at the end. I want to continously update my view. Thanks in advance.

Add a method to your custom view:
- (BOOL) isOpaque { return YES; }
That will prevent the drawing of any views behind yours including the background.
Note, however, that on resize you'll need to redraw everything either way. A more proper solution would be to use off-screen image to draw into instead.

You could use CALayers: add a new child layer to the root each time you have new data, and draw to that layer. Your drawing code can remain the same: you just need to put in the code for creating and using layers, which is actually pretty easy.
See: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004514

Related

How do I optimise Cocoa drawing to ensure smooth scrolling

I am drawing graphs on NSView using standard Cocoa drawing APIs. See image below. There are multiple graphs on the NSView which is in a scrollView. Each graph has around 1440 data points and scrolling performance struggles a bit because of redrawing that is being done.
Is there any way to ensure the graphics are only drawn once such that the image can be scrolled up and down smoothly ?
This same view is used to generate a PDF output file.
Given the drawing does not actually need to change unless the view is resized, and this does not happen, is there any way to prevent the view from redrawing itself during scrolling. Hopefully there is a simple switch to ensure the view draw itself once and keeps that in memory!?
The basic code is in the NSView subclass draw() function.
override func draw(_ dirtyRect: NSRect) {
drawAxis()
// Only draw the graph axis during live resize
if self.inLiveResize {
return
}
plot1()
plot2()
plot3()
...
}
func plot1(){
for value in plot1Data {
path = NSBezierPath()
if isFirst {
path?.move(to: value)
} else {
path?.line(to: value)
}
}
}
Apple has pretty comprehensive advice in their View Programming Guide: Optimizing View Drawing article.
It doesn't appear that you're taking even the minimal steps to avoid drawing what you don't have to. For example, what code you've shown doesn't check the dirtyRect to see if a given plot falls entirely outside of it and therefore doesn't need to be drawn.
As described in that article, though, you can often do even better using the getRectsBeingDrawn(_:count:) and/or needsToDraw(_:) methods.
The scroll view can, under some circumstances, save what's already been drawn so your view doesn't need to redraw it. See the release notes for Responsive Scrolling. One requirement of this, though, is that your view needs to be opaque. It needs to override isOpaque to return true. It's not enough to just claim to be opaque, though. Your view actually has to be opaque by drawing the entirety of the dirty rect on every call to draw(). You can fill the dirty rect with a background color before doing other drawing to satisfy this requirement.
Be sure the clip view's copiesOnScroll property is set to true, too. This can be done in IB (although it's presented as an attribute of the scroll view, there) or in code. It should be true by default.
Note that the overdraw that's part of Responsive Scrolling will happen incrementally during idle time. That will involve repeated calls to your view's draw() method. If you haven't optimized that to only draw the things that intersect with the dirty rect(s), then those calls are going to be slow/expensive. So, be sure to do that optimization.

Drawing on CATiledLayer or CALayer with pdf in iOS

i have a scrollview which contains a pdfpage rendered with CATiledLayer, i want to draw stuff onto the pdf page so i created a overlay layer, i need the graphic to look vectorized so i decided to use CATiledlayer for the overlay layer. Only problem is that it is very slow to draw (I'm using beizerpath to draw), then i tried to optimize it by creating the overlay layer with the visible height and width when zooming in and out, so i don't need to create the overlay for the whole content bound. But still no luck , i want to try CALayer but the draw path just becomes blurry and pixelated, so i'm not sure how i can improve on this. I also tried drawinrect but for some reason it doesn't seem to work.
I suggest not using bezierpath to draw annotations since it requires you to redraw the whole path every time the pen moves. It would be better if you draw only the current line segment using CGContextAddQuadCurveToPoint.
At touchMoved, get the current point and 2 prev points
Using those points, get the area where the line should be drawn
draw at that area in drawRect using setNeedsDisplayAtRect
inside drawRect, go to the end of your path and add your new line using CGContextAddQuadCurveToPoint

"CoreAnimation: surface is too large"

I'm creating a custom (layer-hosting) document view, which is contained within a scroll view. The root layer has two sub layers of the same size--one for the view's content, and one for anything that needs to hover over the main content. I set the frame to 2500x2500 and added a number of cells to the content layer, which was fine. On adding a translucent clone of one of the cell's layers to the overlay layer, the whole view clears briefly, and I get a log message 'core animation: surface 2502x2502 is too large'. This happens between adding the new layer and the next cycle of the event loop, so I guess when core animation renders the new layer.
I knew that a layer's content size is related to opengl texture size, but didn't think its frame mattered. I'm not drawing anything to these layers, not setting any style properties, and remove offscreen sub layers. All I'm really using them for is to handle the geometry of the document view. Is this an appropriate use of CA layers? If not, are there better ways of handling a large core animation-based document view?
Edit:
I've had this problem again, caused by an implicit animation on adding sublayers to the large parent. So in addition to what is suggested below, that's one to check if you run into this.
I would check to make sure that you're not setting any properties on your 2500x2500 layers which could require offscreen rendering. (This causes the layer to try and create a full-size buffer off-screen and render its contents into that buffer, rather than just rendering the contents to the screen directly.)
For example, setting an opacity, masksToBounds, mask, shouldRasterize, etc, could cause offscreen-rendering. You can see if offscreen-rendering is happening with the Core Animation instrument. (There's a checkbox to highlight offscreen-rendered areas.)

CAOpenGLLayer displaying remote Context

I have a sub-classed CAOpenGLLayer class which overrides drawInCGLContext there I draw a rectangle with OpenGL. The CAOpenGLLayer is added to a CALayer and shown.
So when I would like to draw something I would need to do it in drawInCGLContext with this architecture.
What I would like to have is a sort of context used by an other class to draw, animate or render to but will be displayed every time drawInCGLContext occurs.
So basically the only thing my subclass should do is display a remote (OpenGL)context, what's the best way to achieve this? Or should I consider a different approach?
*Not using a CALayer is not an option.
Have you considered using a frame buffer object (FBO)? You can create one which is backed by a texture. Your "remote" drawing class could draw into the FBO, which will cause the drawing to go to the texture that backs it. You can then use that texture elsewhere, like blitting it to the screen in your CAOpenGLLayer subclass. See this link for details of how to use an FBO.

NSView leaves artifacts on another NSView when the first is moved across the second

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!
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.
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;

Resources