Draw NSView background partially, with a gradient - xcode

I have a NSView, subclassed, with custom background drawing, filling it with a gradient.
In IB, I've put a checkbox on it, somewhere in the middle.
This is the drawRect method.
-(void) drawRect:(NSRect)dirtyRect {
CGFloat sc = 0.9f;
CGFloat ec = 0.6f;
NSColor* startingColor = [NSColor colorWithDeviceRed:sc green:sc blue:sc alpha:1];
NSColor* endingColor = [NSColor colorWithDeviceRed:ec green:ec blue:ec alpha:1];
NSGradient *grad = [[NSGradient alloc] initWithStartingColor:startingColor endingColor:endingColor];
[grad drawInRect:dirtyRect angle:270];
}
What happens is, this same method gets called to draw the whole view area first and then for the part, where NSButton (checkbox) lies on top of it. OF course the checkbox background is drawn with a complete gradient and it is not right, since the portion is much smaller. The same happens with other controls I put on the said NSView.
What is the suggested approach on such thing?
One option is to make controls height the same as the views' but this will result in problems in the future.

The answer is, always draw the WHOLE area of the view, not just the dirtyRect
[grad drawInRect:[self bounds] angle:270];

Related

drawWithFrame: draws NSTextFieldCell background out of registration with frame

I'm failing to have a NSTextFieldCell created and drawn programmatically recreate the same visual appearance equivalently-configured NSTextFieldCells have when built in Interface Builder and drawn automatically by Cocoa.
Context is a custom NSView that displays content that user can select, delete or replace. Though this content is non-textual, I'd like the field to resemble an NSTextField, so I'm trying to press an NSTextFieldCell into service to draw a bezel, background fill, etc., that would match a corresponding NSTextField's.
I setup the cell as follows (during custom control init()):
textFieldCell = [[NSTextFieldCell alloc] init];
[textFieldCell setDrawsBackground: YES];
[textFieldCell setBackgroundColor: NSColor.blueColor];
[textFieldCell setBackgroundStyle: NSBackgroundStyleNormal];
[textFieldCell setBezeled: YES];
[textFieldCell setStringValue: #""];
and then in custom control's drawRect,
- (void) drawRect: (NSRect) rect
{
// Draw an NSTextField-like background
if( isSelectAndDeletable)
{
[textFieldCell drawWithFrame: [self bounds] inView: self];
}
... // more content drawing here
}
This produces on onscreen 1-pixel frame of the appropriate thickness and size, and fills it with blue. But the frame itself is gray, not blue, and the fill touches the frame on top (no white margin) and misses it by two pixels at the bottom (thick white margin). By contrast, when I let Cocoa draw my textFieldCells either as part of an NSTextField or as standalone cells created in Interface Builder, the frame is drawn WITH the cell's background color and the fill is inset with one pixel white margin all around.
This picture demonstrates the problem. It shows three NSTextFieldCells, the second of which is (defectively) drawn by my own call to drawWithFrame:inView:, the other two (correctly) directly by the runtime.
Any ideas what I'm doing wrong? I feel like I am passing the proper rect (the frame is in the correct location), and the single call produces both the correct stroke (frame) and incorrect fill (background). Is this some misconfigured bezel effect? I can hand draw the effect I'm after but would rather stick with NSTextFieldCell if I can fix my approach here.

Drawing correct CALayer colors honoring NSAppearance

I am looking into theming my app by setting window.appearance.
In my app, I draw some stuff inside layers. I also use Core Plot, which renders its charts in layers.
For the default aqua appearance, I just use the system colors (such as NSColor.textColor and NSColor.gridColor) and they are drawn in the correct color in CALayer. But changing the window's appearance to vibrant dark causes colors to be drawn incorrectly.
Is there any way to obtain the correct color for a givenNSAppearance? Private API is acceptable too.
If the question is not clear, here is a very simple example to show the problem.
I set up a CATextLayer that is added as a sublayer of the main view's sublayers and an NSTextFied that is added as a subview:
CATextLayer* textLayer = [CATextLayer new];
textLayer.string = #"Is this colored correctly? (Layer)";
textLayer.foregroundColor = NSColor.textColor.CGColor;
textLayer.contentsScale = 2.0;
textLayer.frame = (CGRect){0,0, [textLayer preferredFrameSize]};
NSTextField* textField = [NSTextField new];
textField.stringValue = #"Is this colored correctly? (View)";
textField.textColor = NSColor.textColor;
textField.font = (__bridge id)textLayer.font;
textField.editable = NO;
textField.selectable = NO;
textField.bezeled = NO;
textField.backgroundColor = nil;
[textField sizeToFit];
textField.frame = (CGRect){0, 60, textField.bounds.size};
[self.view.layer addSublayer:textLayer];
[self.view addSubview:textField];
On an Aqua window, both appear correctly:
However, on a dark vibrant window, the layer does not, while the text field does:
I'd like to know how to get the correct color for a given NSAppearance.
So I had an incorrect approach.
The right way to do it, is to implement -updateLayer in the view and take the colors' CGColor snapshot there. -updateLayer is called when the appearance changes, so the view can update it with the correct color values.

NSScrollView starting at middle of the documentView

I have the following code:
[[ticketsListScrollView documentView] setFrame: NSMakeRect(0, 0, [ticketsListScrollView frame].size.width, 53 * [tickets count])];
[[ticketsListScrollView documentView] setFlipped:YES];
for(int i = 0; i < [tickets count]; i++) {
TicketsListViewController *viewController = [[TicketsListViewController alloc] initWithNibName:#"TicketsListViewController" bundle:nil];
viewController.dateLabelText = tickets[i][#"date"];
viewController.timeLabelText = tickets[i][#"time"];
viewController.subjectLabelText = tickets[i][#"title"];
NSRect frame = [[viewController view] frame];
frame.origin.y = frame.size.height * i;
[viewController view].frame = frame;
[[ticketsListScrollView documentView] addSubview:[viewController view]];
}
if the list is large enough (many views), the NSScrollView starts at top-left, which is great. For less views (the views do not take the whole documentView, then NSScrollView starts at the middle.
Any idea why?
Thank you!
Views are not flipped by default, which means your document view is being pinned to the lower-left corner (the default, non-flipped view origin) of the scroll view. What you're seeing is a view not tall enough to push the "top" subview to the top of the scroll view. I see you tried flipping this view, so you already know about this, but you're not doing it correctly.
I'm not sure why you're not getting an error or a warning when calling -setFlipped: since the isFlipped property is read-only. In your document view (the view that's scrolled, and in which you're placing all those subviews), you can override it:
- (BOOL)isFlipped {
return YES;
}
Of course you'll have to put this in a custom NSView subclass and set that as your scroll view's document view's class in IB if you're not creating it at runtime. You'll also need to adjust the frames you use for layout, since you're currently expressing them in the coordinate system of the scroll view's frame. You should be expressing them in your container/layout view's bounds coordinates, which will also be flipped, and so, likely different from your scroll view's coordinates. You'll also need to implement -intrinsicContentSize (and call -invalidateIntrinsicContentSize when adding/removing subviews) so auto-layout can size the container appropriately.

subview of layer backed nsview clipped

I have a subview of an layer backed nsview (set through storyboard) which exceed the bounds. For some reason it is getting clipped. Any idea why this is happening ?
By default NSView will clip its subviews to its bounds.
If you have a layer backed NSView, alignmentRectInsets: might provide your solution. It returns an NSEdgeInsets that adds a margin to the layer's clipping bounds. Override the read-only property in your subview and return the desired insets.
If you need a more sophisticated way to inset the clipping, take a look at alignmentRectForFrame: and frameForAlignmentRect:.
See: developer.apple.com/…/Cocoa/Reference/ApplicationKit/Classes/NSView_Class
I just solved the problem.
It wasted me some time although the method is very simple.
There are two things need to pay attention to.
First of all (THE MOST IMPORTANT!!),you should set your winow's contentview.wantsLayer YES;
And then , set the parent view's layer.maskesToBounds NO;
What I did:
self.window.contentView.wantsLayer = YES;
self.testview.layer.backgroundColor = [NSColor clearColor].CGColor;
self.testview.layer.borderWidth = 3;
self.testview.layer.borderColor = [NSColor blueColor].CGColor;
self.testview.layer.masksToBounds = NO;
NSView *aView = [[NSView alloc]initWithFrame:NSMakeRect(-50, -20, 100, 100)];
aView.wantsLayer = YES;
aView.layer.backgroundColor = [NSColor redColor].CGColor;
[self.testview addSubview:aView];
The effect
GOOD LUCK

How do I get the inner/client size of a NSView subclass?

I am doing manual layouting for my Cocoa application and at some point I need to figure out what the inner size of a NSView subclass is. (E.g. What is the height available for my child view inside of a NSBox?)
One of the reasons is that I am using a coordinate system with origin at the top-left and need to perform coordinate transformations.
I could not figure out a way to get this size so far and would be glad if somebody can give me a hint.
Another very interesting property I would like to know is the minimum size of a view.
-bounds is the one you're looking for in most views. NSBox is a bit of a special case, however, since you want to look at the bounds of the box's content view, not the bounds of the box view itself (the box view includes the title, edges, etc.). Also, the bounds rect is always the real size of the box, while the frame rect can be modified relative to the bounds to apply transformations to the view's contents (such as squashing a 200x200 image into a 200x100 frame).
So, for most views you just use [parentView bounds], and for NSBox you'll use [[theBox contentView] bounds], and you'll use [[theBox contentView] addSubview: myView] rather than [parentView addSubview: myView] to add your content.
Unfortunately, there is no standard way to do this for all NSView subclasses. In your specific example, the position and size of a child view within an NSBox can be computed as follows:
NSRect availableRect = [someNSBox bounds];
NSSize boxMargins = [someBox contentViewMargins];
availableRect = NSInsetRect(availableRect, boxMargins.width, boxMargins.height);
If you find yourself using this often, you could create a category on NSBox as follows:
// MyNSBoxCategories.h
#interface NSBox (MyCategories)
- (NSRect)contentFrame;
#end
// MyNSBoxCategories.m
#implementation NSBox (MyCategories)
- (NSRect)contentFrame
{
NSRect frameRect = [self bounds];
NSSize margins = [self contentViewMargins];
return NSInsetRect(frameRect, margins.width, margins.height);
}
#end
And you would use it like so:
#import "MyNSBoxCategories.h"
//...
NSRect frameRect = [someNSBox contentFrame];
[myContentView setFrame:frameRect];
[someNSBox addSubview:myContentView];
The bounds property of NSView returns an NSRect with the origin (usually (0,0)) and the size of an NSView. See this Apple Developer documentation page.
I'm not sure (I never had to go too deep in that stuff), but isn't it [NSView bounds]?
http://www.cocoadev.com/index.pl?DifferenceBetweenFrameAndBounds

Resources