Dear fellow Cocoa programmers,
What I'd like to accomplish:
I have a checkbox, a popUpButton(which is hidden) and a NSView on my canvas.
If myCheckbox is checked -> show the popUpButton and draw a line through bezierPath on the NSView.
if myCheckbox is UNchecked -> Hide the popUpButton again and "undraw" the path
The code:
- (IBAction)isChecked:(id)sender {
//if myChekcbox is checked, show the pop up button
if ([sender state]==NSOnState) {
NSLog(#"Checked");
[myPopUp setHidden:NO];
}
else
{
//if the checkbox is unchecked, hide the popupbutton
[myPopUp setHidden:YES];
NSLog(#"Unchecked");
}
//reload my drawrect method (reload the view)
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect
{
//if the checkedbutton is checked, draw the line
if ([myCheckbox state]==NSOnState)
{
NSBezierPath *myPath = [NSBezierPath bezierPath];
[myPath moveToPoint:NSMakePoint(10, 20)];
[myPath lineToPoint:NSMakePoint(50, 20)];
[myPath setLineWidth:2];
[myPath stroke];
}
}
The problem:
if checked state = NSOnState the popUpButton is visible but the line just won't draw and I wonder why... I personally think it's a connection(s) problem.
I uploaded the project file (it's rather small-35kb) here:Drawing.zip
Globally:
I've read the NSView documentation and it's saying there is only one way to draw to a view and it's through the drawRect method. Is this actually true? Also is this a descent way to draw to a view? (if function in the view and setNeedsDisplay:YES in the method)
thanks in advance,
Ben
You will need to get an NSColor instance and then call setStroke on it to set a current stroke color. It does not know which color to use to stroke the path at the start of drawRect:, so you have to tell it.
Related
I have created a vertical NSStackView that contains two NSView subclasses (they are just NSViews that draw a background color). I have the stack view set to detach hidden views. I have set one of the views to be hidden.
Neither view hides in the stack view.
To make sure I'm not insane, I also set up two of the same NSViews next to each other, hiding one. Sure enough, one does hide.
The stack view's distribution is set to Fill Proportionally (not that that seems to matter).
In IB the behavior seems correct; one of the views hides.
I must be missing something incredibly obvious here, right?
In case it is relevant, the NSView subclass:
#import "ViewWithBackgroundColor.h"
#implementation ViewWithBackgroundColor
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[self.backgroundColor set];
[NSBezierPath fillRect:dirtyRect];
if(self.bottomBorderColor != nil) {
NSBezierPath *linePath = [[NSBezierPath alloc] init];
[self.bottomBorderColor set];
linePath.lineWidth = 2.0;
[linePath moveToPoint:NSMakePoint(0, 0)];
[linePath lineToPoint:NSMakePoint(dirtyRect.size.width, 0)];
[linePath stroke];
}
}
- (NSColor *) backgroundColor {
if (_backgroundColor) {
return _backgroundColor;
} else {
return [NSColor clearColor];
}
}
#end
This looks like an issue with IB and stack view (please file a bug report if you already haven't).
To workaround it you could either:
Don't hide the button in IB, and set it to be hidden at runtime.
or
Uncheck the 'Detaches Hidden Views' stack view property in IB (visible in your screen shot), and set it at runtime with -[NSStackView setDetachesHiddenViews:].
I have an NSTextField in a container:
[textField setFrameOrigin:NSMakePoint(0, -t.frame.size.height)];
content = [[NSView alloc] init];
[content setWantLayer:YES]
content.layer=[CALayer layer];
[content addSubview:textField];
[content scaleUnitSquareToSize:NSMakeSize(1, -1)];
content.frame=textField.frame;
content.layer.backgroundColor=textBGColor.CGColor;
The container itself is located in a view with
[view scaleUnitSquareToSize:NSMakeSize(1, -1)];
This is all for obtaining a top left origin for the TextField and it works great, the only problem consist in the InsertionPoint not drawing (at least not in the visible frame).
I presume the InsertionPoint is either not Scaled or translated with the TextField. Other possibility is that InsertionPoint can't be drawn in a layer backed View.
Is there a way to display the InsertionPoint cursor ?
EDIT
After trying all the possibilities out, it seems the InsertionPoint (and the focusRing) are not drawing because of its frame being positioned out of the superviews bounds and its dirtyDrawRect. Is there a way to remove the clipping of an NSView ? I need to be able to place my TextField on every absolute position possible.
I found a way through: implementing the drawing myself.
1) giving a custom TextView as Editor for the window.
- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)anObject
{
if (!myCustomFieldEditor) {
myCustomFieldEditor = [[TextView alloc] init];
[myCustomFieldEditor setFieldEditor:YES];
}
return myCustomFieldEditor;
}
2) Overiding the drawInsertionPoint method in the custom TextView class.
-(void)drawInsertionPointInRect:(NSRect)rect color:(NSColor *)color turnedOn:(BOOL)flag{
[color set];
NSRectFill(rect);
[super drawInsertionPointInRect:rect color:color turnedOn:flag];
}
For insertion point just make your textfield to first responder.
[myTextField becomeFirstResponder];
I wanted to create a focus ring outside a subclassed NSView to identify selection. My reference comes from here: Link.
I followed the reference, overwrote the -drawRect method as:
#property (nonatomic) BOOL shouldDisplayFocus;
...
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
if (_shouldDisplayFocus)
{
[self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]];
}
[super drawRect:dirtyRect];
[[NSColor blackColor] set];
NSRectFill(dirtyRect);
if (_shouldDisplayFocus)
{
NSSetFocusRingStyle(NSFocusRingTypeExterior);
NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSInsetRect([self bounds], -1.0, -1.0)];
[[NSColor blackColor] set];
[path stroke];
[NSGraphicsContext restoreGraphicsState];
}
}
And its -mouseDown: method also overwritten:
- (void)mouseDown:(NSEvent *)theEvent
{
[super mouseDown:theEvent];
if (_delegate && [_delegate respondsToSelector:#selector(mouseDownAtView:withEvent:)])
{
[_delegate mouseDownAtView:self withEvent:theEvent];
}
}
And after the view is clicked, its delegate would set/un-set the focus ring and which would make its -drawRect: called again.
It worked and generated the focus ring outside the view correctly. However, one problem occurred soon:
I had an image view inside the subclassed view. As the image view rectangle was auto-layout with NSLayoutConstraint objects, I create four NSLayoutConstraint outlets to adjust their values. I do not frequently change the layout constraints. Actually, as the image size remained unchanged, I would not set them.
Here is the situation when the subclassed view not clicked (seemed fine):
Then click on the image (the focus ring generated, but...):
And I tried resize the window, things got even more sadly "FUNNY":
I could not understand why the problem is or how to solve that. Could anyone help me with that? I have uploaded my sample code here: Download
Quite sad that no one answer this question.
I noticed that the subviews also layouted incorrectly when they were add to this view by -addSubview: and -setFrame method.
Really late answer, but here it is anyway: you didn't call [NSGraphicsContext saveGraphicsState] at the start of the if (_shouldDisplayFocus) { block.
You call [NSGraphicsContext restoreGraphicsState] to pop the graphics state off the stack, but you never put anything on the stack. Cocoa is using the graphics state stack to draw everything so you are popping off some unknown state that has something to do with the position of the image. If you want to add the focus ring style and be able to remove the focus ring style you need to first save the graphics state, set the focus ring style to whatever you want, and then restore the graphics state back to what it was.
Please keep in mind that I'm a really newbie Cocoa developer
Scenario: I've a search the when reaches the end of document restarts from begin, a so called "wrap around".
When I do the wrap I want to show a window that flashes on screen for some time (one second??) like and OSD (On Screen Display) control window, TextWrangler and XCode do that when text search restarts from the begin.
How can I implement a similar window?
Implementing a view to do this would be relatively simple. The following code in an NSView subclass would display a partially transparent rounded rect which ignores events when placed in a window.
- (void)drawRect:(NSRect)dirtyRect {
[[NSColor colorWithDeviceWhite:0 alpha:.7] set];
[[NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:10 yRadius:10] fill];
//Additional drawing
}
- (NSView *)hitTest:(NSPoint)aPoint {
return nil;
}
- (BOOL)acceptsFirstResponder {
return NO;
}
- (BOOL)isOpaque {
return NO;
}
If you do want to do this in a window, you will need to create a borderless, non-opaque window and set this as the content view. Also, you will need to have the view fill it's bounds with a clear color at the start of the drawRect: method.
//Create and display window
NSPanel *panel = [[NSPanel alloc] initWithFrame:NSMakeRect(0,0,300,200) styleMask:NSBorderlessWindowMask|NSNonactivatingPanelMask backing:NSBackingStoreBuffered defer:YES];
[panel setOpaque:NO];
MyViewSubview *view = [MyViewSubview new];
[panel setContentView:view];
[view release];
[p setLevel:NSScreenSaverWindowLevel];
[p orderFront:nil];
//Add these two lines to the beginning of the drawRect: method
[[NSColor clearColor set];
NSRectFill(self.bounds);
However, this window will intercept events and I have not been able to disable this using standard methods.
To fade the view, check out NSViewAnimation, or use an NSTimer object and do it manually.
Im working on a drag n' drop view and found some handlers for drag and drop actions on the web. I want to make it so it turns blue when the user drags a file over the drag and drop area and gray again when they exit the drag and drop area. The issues is its not updating when you drag your mouse over it or exit it. Heres some of the code:
- (void)drawRect:(NSRect)rect
{
NSRect bounds = [self bounds];
[[NSColor grayColor] set];
[NSBezierPath fillRect:bounds];
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSRect bounds = [self bounds];
[[NSColor blueColor] set];
[NSBezierPath fillRect:bounds];
return NSDragOperationCopy;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender {
NSRect bounds = [self bounds];
[[NSColor grayColor] set];
[NSBezierPath fillRect:bounds];
}
Thanks for any help.
Are you calling [yourView: setNeedsDisplay] anywhere?
This is how you let the drawing framework know it needs to message your UIView subclass with drawRect:, so you should do it whenever things have changed. In your case, this probably means when the mouse enters or exits the drop area.
Drawing only works when a context (like a canvas for painting) is set up for you to draw into. When the framework calls -drawRect: it has set up a drawing context for you, so drawing commands like -[NSColor set] and -[NSBezierPath fillRect:] work as you expect.
Outside of -drawRect: there is usually no drawing context set up. Using drawing commands outside of -drawRect: is like waving a paintbrush in the air; there's no canvas, so no painting happens.
In 99.99% of cases, all view drawing should be kept within -drawRect: because NSView does a lot of work that you don't want to do to get the drawing context set up correctly and efficiently.
So, how do you change your view's drawing within your -draggingEntered: and -draggingExited: methods? By side effects.
You're doing the same thing in all three cases: 1) Setting a color and 2) Drawing a rectangle. The only difference is the color changes in each method. So, why not control which color you use in -drawRect: with an ivar, like so:
- (void)draggingEntered:(id <NSDraggingInfo>)sender {
drawBlueColorIvar = YES;
// ...
}
Then in -drawRect: you do this:
- (void)drawRect:(NSRect)rect {
NSColor *color = drawBlueColorIvar ? [NSColor blueColor] : [NSColor grayColor];
[color set];
[NSBezierPath fillRect:rect];
}
(Notice I didn't use [self bounds]. It is more efficient to just draw into the "dirty" rect, when possible.)
Finally, you need some way to tell the framework that your view needs to redraw when drawBlueColorIvar changes. The framework won't draw anything unless it's told it needs to. As Chris Cooper said, you do this with [self setNeedsDisplay:YES]. This should go after any place you change drawBlueColorIvar.