Click on NSBezierPath - macos

I'm drawing a NSBezierPath line on my NSImageView. I'm creating NSBezierPath object, setting moveToPoint, setting lineToPoint, setting setLineWidth: and after that in drawRect of my NSImageView subclass I'm calling [myNSBezierPath stroke]. It all works just like I want, but I can't seem to use containsPoint: method... I tried implementing
if([myNSBezierPath containsPoint:[theEvent locationInWindow]]{
//do something
}
in -(void)mouseUp:(NSEvent*)theEvent of my NSImageView subclass but it's never reacting and I'm sure I'm hitting that line... Am I doing something wrong? I just need to detect if NSBezierPath is being clicked.
Cheers.

Make sure to transform the mouse click location into the coordinate system of your image view subclass, not into the one of the window (unless they are the same). Your bezier path doesn't know about the offset it's drawn into, so you have to take that into account when performing a hit test.
Also, from the containsPoint: documentation:
This method checks the point against the path itself and the area it
encloses. When determining hits in the enclosed area, this method uses
the non-zero winding rule (NSNonZeroWindingRule). It does not take
into account the line width used to stroke the path.
Emphasis mine.

Related

Efficient drawing in NSView drawRect

I have an application I'm writing in which I'm drawing into an NSView. In mouseDown I am saving the location in my data model and then drawing a graphic at that location within the drawRect method of the view. It all works fine.
At the end of my mouseDown I was calling [self setNeedsDisplay:YES]; to force a redraw. The only thing is that the dirtyRect is always the full size of the view. I wanted to optimize this as much as possible so that I'm not redrawing the entire window for just a few changed pixels.
So now I'm calling [self drawRect:...] instead and specifying the rectangle.
Now in my drawRect I am comparing every graphic I have to see if it falls in the dirtyRect. It seems like I've traded work of drawing for work of bounds checking everything. I'm not sure I've made it any more or less efficient.
So what is the standard practice? Is it common to just redraw everything in the view and ignore the dirtyRect? Is there a nice function I can use as a test to see whether my object is in the dirtyRect?
You should never call -drawRect: yourself if you're trying to draw to the screen. Let AppKit call it for you. What you should do is call -setNeedsDisplayInRect: at the end of your -mouseDown:.
Then, in -drawRect:, only draw stuff contained in the dirtyRect. You can test to see if a point is inside the dirtyRect with NSPointInRect(). There are lots of other useful functions for working with NSRect. See the documentation for the point functions and the rectangle functions.

Drawing with drawRect method - 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.

How can I change individual cell background colors in an NSForm?

I would like to individually set cell background colors in an NSForm. I thought it would be a matter of subclassing NSForm and overriding -drawCellAtRow:column:. But -drawCellAtRow:column: only gets called when the cell receives focus.
I've experimented with calling -setCellBackgroundColor on the NSForm but that changes the title background, not the value.
Is there an approach I'm not finding?
I think this just isn't possible with NSFormCell.
First thing I tried:
self.form.drawsCellBackground = YES;
self.form.cellBackgroundColor = [NSColor redColor];
This appears to draw the background only behind the label. Some further testing suggested that a single NSFormCell spans both the label and text field, and the background was drawing behind both. When the text field draws on top, its own background covers up the red color.
Next I subclassed NSFormCell. The only method to be called is -drawWithFrame:inView:. The frame, again, spans both the label and text field. The control view is the NSForm. If I draw my own background and then invoke [super drawWithFrame:inView:], it appears just as above, visible behind the label, but not visible behind the text field. If I invoke super and then draw the background, it appears atop both.
NSFormCell's implementation does not invoke -drawInteriorWithFrame:inView: nor does it seem to provide another override point.
I tried setting highlighted, and implementing -highlightColorWithFrame:inView: – same issue.
While I was trying to understand how this all worked I also tried subclassing NSForm. The only drawing-related method called is -drawRect:. These methods are never invoked: -drawCellAtIndex:, -drawCellAtRow:column:, -drawCell:, -drawCellInside:.
Apart from getting Apple to fix this, the only solutions I see are trying to use NSForm's superclass NSMatrix, or just using labels and text fields.

NSBezierPath point animation in NSView

I've built a custom control in Cocoa which is drawn using an NSBezierPath and I'd like it to change shape when it's state has changed (unused state = a pointed 'look here' edge, used state = standard control edge).
It feels like I've looked through every mention of "NSBezierPath" and "Animation" there is on the web but with no luck.
Before I crack out some NSTimers and write my own timing & path point controls, does anyone know if this is possible using Core Animation or similar?
You could use NSAnimation. Create your own NSAnimation subclass, and override setCurrentProgress method.
In that method, you can change your NSBezierPath size, shape or whatever you need, based on current animation progress. After that you can force a redraw in the view (via delegate, for instance) to re-display the path.

NSScrollView - pattern background - how to start from top-left corner?

To fill NSScrollview with pattern image I use - (void)setBackgroundColor:(NSColor *)aColor method where aColor is created with + (NSColor *)colorWithPatternImage:(NSImage *)image method. Despite of what isFlipped returns for NSScrollView and its content view, pattern start repeat from bottom-left corner while I want it to start from top-left corner. How can I achieve that?
I believe the key here may be that -setBackgroundColor: of the NSScrollView:
Sets the color of the content view’s
background to aColor.
The content view of a NSScrollView is a separate view inside of the NSScrollView.
So, what you will probably need to do is be able to change the isFlipped of the content view of the NSScrollView.
I haven't played around with this yet, so I don't know what problems you may run into.
EDIT: However, it appears your answer may be found here:
CocoaDev: NSScrollView
Look near the bottom of the article.
You need to use the method [NSGraphicsContext setPatternPhase:]. This thread provides the details. It worked for me.

Resources