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:].
Related
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.
In IB, put 2 custom views into one window. I don't see any way to give them separate names. In Inspector-Info I had to use the same name, the class name, for both of them in the drop-down menu. I tried
DrawRect: NSRect bounds = [self bounds];
[[NSColor greenColor] set];
[NSBezierPath fillRect:bounds];
which filled both custom views with green. But I would like to fill, draw, etc. independently in each custom view. How can I address each view separately? Multiple custom views, later. Or does Cocoa require only one view per class?
This is probably trivial, but Google and the similar questions list here didn't come up with anything close. There's a lot of about multiple view controllers, but I don't need to switch views.
IB will show you the names of the classes that you can assign to an object. If you have only one custom class (eg. "myCustomClass") then it will only show that one in the drop-down menu.
I think the best solution to your problem, if you want tu use only one class, is to put the drawing code in two separate functions and assign each view an IBOutlet, then call the function from the controller class.
//Add this to your interface
NSNumber *myColor;
//Add/Edit the following functions
- (void)drawRect:(NSRect)aRect
{
//Some code...
if ([myColor intValue]) [self drawGreen];
else [self drawRed];
//Some code...
}
- (void)drawGreen
{
NSRect bounds = [self bounds];
[[NSColor greenColor] set];
[NSBezierPath fillRect:bounds];
}
- (void)drawRed
{
NSRect bounds = [self bounds];
[[NSColor redColor] set];
[NSBezierPath fillRect:bounds];
}
- (void)drawRedOrGreen:(int)aColor
{
myColor = [NSNumber numberWithInt:aColor];
}
You have to add the following two lines to your controller's interface
IBOutlet myCustomClass *customView1;
IBOutlet myCustomClass *customView2;
And you have to set each view's color.
This will set it when it loads for the first time.
- (void)awakeFromNib
{
[customView1 drawRedOrGreen:1]; //Green
[customView2 drawRedOrGreen:0]; //Red
}
This way each view will be coloured differently.
An alternative solution would be to create two separate custom classes (eg. "myCustomClass1" and "myCustomClass2") which would have their own drawing code...
I have 3 subclasses: a Block class, a Row class and a Table class. All are subclasses of NSView.
I have a Table added with IB which programmatically displays 8 rows, each of which displays 8 blocks. I overrode the mouseDown: method in Block to change the background color to red, but it doesn't work. Still if I add a block directly on top of the Table with IB it does work so I can't understand why it won't work in the first case.
Here's the implementation code for Block and Row (Table's implementation works the same way as Row's):
//block.m
- (void)drawRect:(NSRect)dirtyRect
{
[color set];
[NSBezierPath fillRect:dirtyRect];
}
-(void)mouseDown:(NSEvent *)theEvent
{
color = [NSColor redColor];
checked = YES;
[self setNeedsDisplay:YES];
}
//row.m
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor blueColor] set];
[NSBezierPath fillRect:dirtyRect];
int x;
for(x=0; x<8; x++){
int margin = x*2;
NSRect rect = NSMakeRect(0, 50*x+margin, 50, 50);
Block *block = [[Block alloc] initWithFrame:rect];
[self addSubview:block];
}
}
Are you aware that NSTableView will use NSCell objects for it's drawing, and not an NSView? If not, investigate NSCell - using that for custom drawing in an NSTable is the way to go.
I understood the problem... since the mouseDown implementation would cause the block to redraw, and so even its superview, it would call Table's drawRect: method causing it to draw new blocks on top of the old ones, and so it would seem never to change color. So I created a property for Table called isFirstAppearance initially set to YES which if YES makes the table draw the rows and sets itself to NO.
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.