Drawing with drawRect method - Cocoa - cocoa

I've been looking for solutions but can't figure out if it is possible to do the following:
I have a drawRect method, what i want to do is to add a graphic element (such as rects and lines) to the current view without refreshing it. I used to call setNeedsDisplay but this method is actually deleting myView an re-drawing it from 0.. Any suggestion to keep the old one and add new content ?
Thank

Every time an update is made in a view the entirety of it is redrawn; so -drawRect needs to redraw the entire view. You have to 'refresh' your view - it's the norm - there's nothing wrong with it. Just draw the old content again.
Or, you could call setNeedsDisplayInRect: if you do want to just redraw a specific section of your view.

That's what drawRect: does - it redraws the rect in the view.
If you want lines and rects drawn on top, try doing it on a different layer.

I guess it's better if you work with layers.
You can make CALayer subclasses for your shapes, and then save them in an array.

By default , the drawRect method will clear the whole content , if your want to dynamic draw some new graphics contents to the view ,you should abstract these graphics element's data structure , for example , you add a line ,this line will have
a start point
a end point
line color
line width
is has a shadow
a line join
so you can put all these property into a struct and define a new Data Class named LineStruct and define a method called
-(void)drawLine:(CGContextRef)ctx withLineStruct:(LineStruct*)lineStruct
to your custom UIView , define a
#property (nonatomic) LineStruct *lineStruct ;
and invoke in it's drawRect method
-(void)drawRect:(CGContextRef)ctx{
CGContextRef ctx = UIGraphicsGetCurrentContext() ;
[self drawLine:ctx withLineStruct:self.lineStruct];
}
so if your have other graphic contents , you can do the draw like that . If you have lots of
contents , you must add a buffer to your UIView ,such as add a NSArray , and in the drawRect method ,you add a for(;;)to draw all the graphics elements

I think maybe you need something like an NSBezierPath to store all your shapes and add new shapes. It's easy to add new shapes to an existing NSBezierPath, see the documentation: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSBezierPath_Class/Reference/Reference.html
Then in your drawrect, you will only have to stroke or fill the NSBezierPath.
This will only work if all your shapes have the same fill color and stroke. Otherwise you could keep some kind of list of multiple NSBezierPaths and stroke/fill them in different ways.

You can 'get around' this by making the view you want to draw in separate from your view with the original iboutlets. Then make your main view background transparent (but do not make the iboutlets transparent). So for this example, I will prevent that the IBOutlets you want to keep (not draw over) are a UITextField, a UILabel and a UIButton.
So you Interface Builder will look like this:
UIVIewController
UIView2 (view with drawRect defined)
UIView (main)
UITextField
UILabel
UIButton
So as you see, when you call 'drawRect' it will still blank out your UIView2 completely, but it won't matter because 'drawRect' won't delete any of the UILabel, UIButton, UITextField or whatever else you want to keep in your UIView1. Hope this helps.

Related

Auto-layout and transforms not working together for UIViewController transition

I have a UIViewController being presented with a UIViewControllerAnimatedTransitioning object to do the animation. The animation has a collection view cell expand and fade out as the new view controller's view fades in, expanding in the same way. So it looks like you have an expanding cell that transforms into the new view controller. But weirdly enough, the subviews of the cell get transformed in weird ways when they use auto-layout. When I don't use auto-layout, this doesn't happen. What might be the issue?
Try adding your cell to [[transitionContext containerView] superview] in - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext, instead of [transitionContext containerView]
For some reason this worked for me

Cocoa Programming: Adding a Rectangle to a Custom View (NSView)

Is there a simple way to add a simple rectangle to a Custom View without using a custom NSView subclass for it? Something along the lines of:
Assign an IBOutlet (let's call it colorWheelView) of NSView type to the CustomView
In my NSViewController's initWithNibName use it to change draw the rectangle:
// pseudocode
self.colorWheelView.addRectangle(myRectangle);
self.redraw()
The only way I've seen it done (on this site, and in my book Cocoa Programming for Mac OSX, pp. 241) is by making a custom class for the Custom View and modifying its drawRect method... Is this really the only way to accomplish this?
Edit: not sure why formatting is not being rendered correctly. I'm trying to fix it.
It really isn't all that hard to roll your own..
Just add an NSArray property to your NSView subclass, then in your drawRect method draw them either manually or using one of the NSRectFillList* methods provided by AppKit already.
(Beware: those take a plain C array, not an NSArray).
You wouldn't want to manually trigger the redraw from outside the view as in your sample code, though. To keep things consistent your addRectangle would trigger a redraw of the view itself e.g. by calling setNeedsDisplay:.

Misunderstanding with CALayer in CGContextRef

I have a custom view in which I want to display a CALayer with PDF content. To do so I implemented a delegate NSObject subclass as shown in the first answer to Using CALayer Delegate.
Since I have a document-based app, I have a starting window from which I can open documents. From the custom document I initWithWindowNibName: a custom windowController from the makeWindowControllers method. From the windowController, in windowDidLoad, I set a custom NSView's variable values and initialize the CALayer. In the same place I run this line of code to draw the content:
[[[PDFViewLayerDelegate alloc] initWithUrl:url andPageIndex:currentPageIndex] drawLayer:layer1 inContext:[[NSGraphicsContext currentContext] graphicsPort]];
What happens is: while before running that line the background of the CALayer was set to green and would appear only in the correct window, now the PDF content is drawn to the initial window only while both layers are filled with white (which is also done in the delegate method).
My questions are:
Why is my CALayer being drawn to a view which is not of the custom
NSView subclass which creates it? and furthermore in different windows?
Are both views in each window sharing the same graphicsContext? This might be the cause..
I understood the problem... As I presumed the problem was both windows share the same graphicsContext, as the currentContext is:
"The current graphics context of the current thread."
I simply resolved this problem placing the CALayer drawing function call inside the drawRect: method of it's container NSView since drawRect: is responsible for drawing exclusively inside it's view and probably already handles the graphicsContext stuff in somewhat way.

How to draw a NSView on a NSView?

I am passed a NSView from a class and I need to add on another NSView at a certain point. How do I do that?
Thanks in advance.
You can add a view to another view by sending the addSubview message, like this:
[MyView addSubview:MyOtherView];
Don't forget though, you are responsible for how that view displays. Make sure you set its bounds.
You can position the new view when instantiating it using the initWithFrame: method that'll create the view and position it within the superview (i.e. the one you'll add your view to with the already mentioned addSubview: message).
PS: The View Programming Guide - is your friend.. ;-)

NSTextField over NSOpenGLView

I have made a window with an NSOpenGLView that I am rendering openGL content into.
I want to add some buttons and text fields to the view: I can add NSTextFields and NSButtons using interface builder (or code) but they do not appear.
NSOpenGLView is documented as not being able to have sub views, so I then made my own CustomGLView by deriving directly from NSView and implementing the code to create and use a NSOpenGLContext in it. But the subviews are still not appearing :- the OpenGL context paints over them.
On Windows this problem does not exist:- Windows used to host OpenGL MUST have the WS_CLIPCHILDREN and WS_CHIPSIBLINGS styles set ensuring that any peer, or sub children (views) will not be obscured by the OpenGL surface.
How do I get subviews to display over a NSView thats drawing using OpenGL ?
You have 2 choices:
Create a window just for the text field. Add as a child window of the one hosting the OpenGL view. Major downside is you have to manage positioning it correctly if the Open GL view is moved.
Set up your view hierarchy like so:
Layer-backed view
Layer-hosting view whose layer contains an OpenGL layer
Text field
Simply call -setWantsLayer:YES on the subviews of the NSOpenGLView.
NSOpenGLView cannot have subviews according to the documentation. Even if you subclass the NSOpenGLView, that will change nothing.
What you can do is to create a NSView that will hold both the NSOpenGLView and the NSTextField. You then overlap them in the right order to make one draw atop the other.
I'm not heavily into OpenGL yet, but it's my understanding that you can accomplish the visual effect of subviews with Quartz Extreme using layer-backed views; however, those may be problematic. Since subviews are not supported directly, any solution is liable to be a hack.
Indeed, the solution in that link actually hacks a second window to appear over your OpenGL display, the second window displaying the Cocoa views you desire.
The following code (from the above link) is something I've not tested (again not being an OpenGL guy by nature -- yet), but appears like a fairly clever approach:
// This is the GL Window and view you already have
glWindow = [[GLWindow alloc] initWithContentRect:windowRect];
glView = [[[GLView alloc] initWithFrame:NSMakeRect(0, 0, windowRect.size.width, windowRect.size.height)] autorelease];
[glView translateOriginToPoint:NSMakePoint(glView.bounds.size.width/2, glView.bounds.size.height/2)];
[glWindow setContentView:glView];
// And here's your transparent UI window
uiWindow = [[TransparentWindow alloc] initWithContentRect:windowRect];
uiView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, windowRect.size.width, windowRect.size.height)] autorelease];
[uiView translateOriginToPoint:NSMakePoint(uiView.bounds.size.width/2, uiView.bounds.size.height/2)];
uiView.wantsLayer = YES;
[uiWindow setContentView:uiView];
[glWindow addChildWindow:uiWindow ordered:NSWindowAbove];
Again, I've not tested this, but it looks like it will get you the visual effect you desire.
The text can be rendered into a texture -- I just used this for a project, did a lot of looking for sample code, and ultimately found Apple's GLString demo code, which was an absolute trove of how-to:
http://developer.apple.com/library/mac/#samplecode/CocoaGL/Listings/GLString_m.html
I haven't tried adding buttons, but you can, of course, draw your own and comparing the positions of click events with those of your buttons...
This was my solution:
1) Create a parent NSView (let's call it parentView).
2) Add an NSOpenGLView Child to parentView.
3) Add an additional NSView Child to parentView (make sure this is after the OpenGLView within the hierarchy). You can add additional TextFields, etc. to this view.
4) In the ViewController for the parent make sure you call [parentView setWantsLayer: TRUE]; I did this within -(void) viewWillAppear
1) The NSOpenGLView can have a subview. It can have plenty even.
2) The reason some views, controls and other elements are being bullied by NSOpenGLView is due to the loading process when the Application launches. I.e If you add a slider or textfield above and into the content view of the window where the NSOpenGLView also resides, upon Application-Launch that textfield will most likely wind up beneath the NSOpenGLView.
This is an Apple Bug. And they know about it.
You can solve it quite easily even without adding a subview to NSOpenGLView...
In Interface Builder drag i.e. a CustomView into the canvas (Not the view). And set it the way you want it with sliders, text and what not. Then create an outlet (Call it i.e topView) in your view controller. Then somewhere in your code... Perhaps (applicationDidFinishLaunching) add this line...
[_window.contentView addSubview:_topView];
(Do your positioning & layout)
This will do the exact same thing as if you had dragged it into the contentView yourself inside IB. Only it will draw the darn thing in the correct Z position.
You loose IB's constraints this way and have to it manually
One could also just subclass and CAOpenGLLayer and use that as a backing layer inside of a regular NSView. There too it is drawn correctly...
Here is Apple's way of wanting to do that. CALayers are a Godsend ;)
Enter following String ** NSOpenGLLayer** in search and hit enter to get to where it is...
NSOpenGLLayer
Hope this helps....

Resources