Message flow problems with NSTableHeaderView and NSTableHeaderCell while trying to produce transparent NSTableView Header - objective-c

Problem: I am trying to create a custom transparent TableView Header and I have created subclasses of NSTableHeaderView and NSTableHeaderCell and overridden -drawWithFrame:inView and -drawInteriorWithFrame:inView in the NSTableHeaderCell subclass. These methods are working as expected, but only when the table column header is first drawn. After the user clicks on the table header, however, it is re-drawn with a white background. To get specific, here are the custom method implementations:
#interface MYTableHeaderCell : NSTableHeaderCell
#end
#implementation MYTableHeaderCell
-(void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView{
//[super drawWithFrame:cellFrame inView:controlView];
NSBezierPath *path = [NSBezierPath bezierPathWithRect:cellFrame];
NSColor *clearColor = [NSColor clearColor];
[clearColor setFill];
[path fill];
[self drawInteriorWithFrame:cellFrame inView:controlView];
}
-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView{
//[super drawInteriorWithFrame:cellFrame inView:controlView];
NSBezierPath *path = [NSBezierPath bezierPathWithRect:cellFrame];
NSColor *clearColor = [NSColor clearColor];
[clearColor setFill];
[path fill];
NSRect titleRect = [self titleRectForBounds:cellFrame];
[self.attributedStringValue drawInRect:titleRect];
}
-(NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView{
return [NSColor clearColor];
}
-(BOOL)isOpaque{
return NO;
}
When the table view header is first drawn, it
has a transparent background as intended.
After clicking on the header, however, it is
redrawn to have a white background.
As far as I can tell, after clicking on the table view header
-drawInteriorWithFrame:inView:
is still called when the header needs to be drawn. However,
-drawWithFrame:inView:
is not. It also appears that another class is drawing a white view underneath the cell text.
I have looked through the NSTableHeaderCell and NSTableHeaderView class descriptions along with all of their superclasses but I can't figure out why the white background is being drawn. I'm obviously missing something fundamental.
Question: What is causing the white view to be drawn?

The
highlight(flag: Bool, withFrame cellFrame: NSRect, inView controlView: NSView)
method is not implemented. Override this method and copy your code from your drawRect method in there and this should be done.

Related

Drawing issues after subclassing NSCollectionView

OK, here's what I have done:
I have an NSCollectionView
I wanted to be able to enable "selecting" items, and drawing a custom border when an items is selected
I subclassed NSCollectionViewItem (to enable selection)
I subclassed NSView for the NSCollectionViewItem view, in order to draw the border
The code
The view item
#implementation MSLibraryCollectionViewItem
- (void)setSelected:(BOOL)flag
{
[super setSelected:flag];
[(MSLibraryCollectionViewView*)[self view] setSelected:flag];
[(MSLibraryCollectionViewView*)[self view] setNeedsDisplay:YES];
}
The custom view
#implementation MSLibraryCollectionViewView
/***************************************
Initialisation
***************************************/
- (MSLibraryCollectionViewView*)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
/***************************************
Drawing
***************************************/
- (void)drawRect:(NSRect)rect
{
if ([self selected]) {
//[[NSColor redColor] setFill];
//NSRectFill(rect);
//[super drawRect:rect];
NSColor* gS = [NSColor colorWithCalibratedRed:0.06 green:0.45 blue:0.86 alpha:1.0];
NSColor* gE = [NSColor colorWithCalibratedRed:0.12 green:0.64 blue:0.94 alpha:1.0];
NSGradient* g = [[NSGradient alloc] initWithStartingColor:gE endingColor:gS];
NSColor *borderColor = [NSColor colorFromGradient:g];
NSRect frameRect = [self bounds];
if(rect.size.height < frameRect.size.height)
return;
NSRect newRect = NSMakeRect(rect.origin.x+5, rect.origin.y+5, rect.size.width-10, rect.size.height-10);
NSBezierPath *textViewSurround = [NSBezierPath bezierPathWithRoundedRect:newRect xRadius:7 yRadius:7];
[textViewSurround setLineWidth:2.0];
[borderColor set];
[textViewSurround stroke];
}
}
However, the seems to be something wrong with drawing. For example:
When resizing the Collection View's container, a weird line appears at the outer box
When an Collection View item is not 100% visible (e.g. because it's been scrolled down), the selection border doesn't appear at all (while I would expect it to draw just the visible portion).
Some Examples
What's going on?
P.S. I'm not a guru with drawing and custom views in Cocoa - so any ideas/help is more than welcome!
You switched from asking about a collection view to talking about an outline view, but I assume that was just a mental hiccup.
When an Outline View item is not 100% visible (e.g. because it's been scrolled down), the selection border doesn't appear at all
(while I would expect it to draw just the visible portion).
That's because of this code in your -drawRect:.
if(rect.size.height < frameRect.size.height)
return;
It's specifically avoiding drawing a partial selection outline.
Regarding the weird line, I doubt that has to do with your collection item view's custom drawing. Does it stop happening if you disable the custom drawing? You could experiment with using an ordinary color rather than using the third-party +colorFromGradient: code you're using.
By the way, this line:
NSRect newRect = NSMakeRect(rect.origin.x+5, rect.origin.y+5, rect.size.width-10, rect.size.height-10);
could be written more simply as:
NSRect newRect = NSInsetRect(rect, 5, 5);

NSView with fill ( pattern image) scrolls when window changes size

I have an NSView with a drawRect
- (void)drawRect:(CGRect)rect {
// Drawing code
NSPoint origin = [self visibleRect].origin;
[[NSGraphicsContext currentContext]
setPatternPhase:NSMakePoint(origin.x, origin.y)];
[[NSColor colorWithPatternImage: self.image] set];
[NSBezierPath fillRect: [self bounds]];
}
It draws my pattern perfectly, but i can see the pattern scroll when i change the the size of my window.
i have tried to set the view isFlipped to YES but that doesn't change anything.
You need to do some off-screen drawing first and then draw that result onto the view. For example you can use a blank NSImage of the exact same size as the view, draw the pattern on that image and then draw that image on the view.
Your code may look something like that:
- (void)drawRect:(NSRect)dirtyRect
{
// call super
[super drawRect:dirtyRect];
// create blank image and lock drawing on it
NSImage* bigImage = [[[NSImage alloc] initWithSize:self.bounds.size] autorelease];
[bigImage lockFocus];
// draw your image patter on the new blank image
NSColor* backgroundColor = [NSColor colorWithPatternImage:bgImage];
[backgroundColor set];
NSRectFill(self.bounds);
[bigImage unlockFocus];
// draw your new image
[bigImage drawInRect:self.bounds
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0f];
}
// I think you may also need to flip your view
- (BOOL)isFlipped
{
return YES;
}
Swift
A lot has changed, now things are easier, unfortunately part of objective-C's patrimony is lost and when it comes to Cocoa, Swift is like an orphan child. Anyways, based on Neovibrant's we can deduct the solution.
Subclass NSView
Override draw method
Call parent method (this is important)
Set a fill on buffer within the bounds of the view
Draw fill on buffer
code
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let bgimage : NSImage = /* Set the image you want */
let background = NSColor.init(patternImage: bgimage)
background.setFill()
bgimage.draw(in: self.bounds, from: NSZeroRect, operation: .sourceOver, fraction: 1.0)
}

NSTextField - Subclassing & drawRect cause text not to load

Trying to round out the border of an NSTextField (the little black box in the upper-left corner): http://cl.ly/image/2V2L1u3b3u0G
So I subclassed NSTextField:
MYTextField.h
#import <Cocoa/Cocoa.h>
#interface HATrackCounterField : NSTextField
#end
MYTextField.m
#import "HATrackCounterField.h"
#implementation HATrackCounterField
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor blackColor] setFill];
[[NSBezierPath bezierPathWithRoundedRect:dirtyRect xRadius:3.0 yRadius:3.0] fill];
}
#end
Now its not showing the text-field text: http://cl.ly/image/1J2W3K431C04
I'm new at objective-c, it seems like this should be easy, so I'm probably just doing something wrong...
Thanks!
Note: I'm setting the text through a collection view, and I've tried setStringValue: at different points also to no avail.
Your text-field's text isn't showing because you overwrite -drawRect and don't call [super drawRect:dirtyRect] in it.
In your case, I think the easiest way to do what you want is using clip mask: just let NSTextField perform drawing ant then clip the region:
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
[[NSBezierPath bezierPathWithRoundedRect:dirtyRect xRadius:3.0 yRadius:3.0] setClip];
[super drawRect:dirtyRect];
[NSGraphicsContext restoreGraphicsState];
}
In general it is better to subclass NSTextFieldCell instead to make custom drawing, because cells are responsible for drawing.
For reference for future readers, this is probably how you should do it, by subclassing NSTextFieldCell:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSBezierPath *betterBounds = [NSBezierPath bezierPathWithRoundedRect:cellFrame xRadius:CORNER_RADIUS yRadius:CORNER_RADIUS];
[betterBounds addClip];
[super drawWithFrame:cellFrame inView:controlView];
if (self.isBezeled) { // optional, but provides an example of drawing a prettier border
[betterBounds setLineWidth:2];
[[NSColor colorWithCalibratedRed:0.510 green:0.643 blue:0.804 alpha:1] setStroke];
[betterBounds stroke];
}
}
I draw an additional bluish border here (though that seems to be unnecessary for your black box)

Highlighting an Edited NSBrowserCell & Draw A Focus Ring on its NSText?

What I'm trying to do
I'm trying to add edit-in-place functionality to the Connection Kit's NSBrowser. I'd like this behaviour to be functionally and visually similar to Finder's implementation.
The visual effect I'm aiming for
What I've got so far
The arrows indicate focus ring & cell highlighting in Finder's implementation, and the lack of it in mine.
I have tried
Setting the background colour of the cell in the controller, in it's drawInteriorWithFrame method
The same for the field editor
setFocusRingType:NSFocusRingTypeDefault for the field editor & cell both in the controller & the draw method
Manually drawing the highlight color in the draw method
Various combinations of the above, and undoubtedly some I've forgotten.
The best I've managed was getting the area surrounding the cell's image coloured with the highlight colour.
Is there some fundamental that I'm missing here? Could someone please suggest a starting point for approaching this? Is drawInteriorWithFrame the place to be doing this?
I've got editing working fine - I'm just having trouble with the visual aspects.
Code to allow editing:
// In the main controller
int selectedColumn = [browser selectedColumn];
int selectedRow = [browser selectedRowInColumn:selectedColumn];
NSMatrix *theMatrix = [browser matrixInColumn:selectedColumn];
NSRect cellFrame = [theMatrix cellFrameAtRow:selectedRow column:0];
NSText *fieldEditor = [[browser window] fieldEditor:YES
forObject:editingCell];
[cell editWithFrame:cellFrame
inView:theMatrix
editor:fieldEditor
delegate:self
event:nil];
And in my subclass of NSBrowserCell:
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
image = [[self representedObject] iconWithSize:[self imageSize]];
[self setImage:image];
NSRect imageFrame, highlightRect, textFrame;
// Divide the cell into 2 parts, the image part (on the left) and the text part.
NSDivideRect(cellFrame, &imageFrame, &textFrame, ICON_INSET_HORIZ + ICON_TEXT_SPACING + [self imageSize].width, NSMinXEdge);
imageFrame.origin.x += ICON_INSET_HORIZ;
imageFrame.size = [self imageSize];
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent {
NSRect imageRect, textRect;
NSDivideRect(aRect , &imageRect, &textRect, 20, NSMinXEdge);
self.editing = YES;
[super editWithFrame: textRect inView: controlView editor:textObj delegate:anObject event:theEvent];
}
You have to draw the focus ring yourself.
Add the following in drawWithFrame in your NSBrowserCell subclass
[NSGraphicsContext saveGraphicsState];
[[controlView superview] lockFocus];
NSSetFocusRingStyle(NSFocusRingAbove);
[[NSBezierPath bezierPathWithRect:NSInsetRect(frame,-1,-1)] fill];
[[controlView superview] unlockFocus];
[NSGraphicsContext restoreGraphicsState];
Try subclassing field editor object and override drawRect function like this :
- (void)drawRect:(NSRect)rect {
[super drawRect:rect];
NSSetFocusRingStyle(NSFocusRingOnly);
NSRectFill([self bounds]);
}
Hope this helps.

Overlay NSScroller over content

Is there any way to overlay the NSScroller over the content of the scroll view (like in iOS)? I've already tried several approaches:
a) setting the frame of the scroll view content view (NSClipView) to extend into the bounds of the scroller
b) adding an NSScroller object as a subview of the scroll view (positioned where I want)
c) creating an entirely custom scroller view and placing it as a subview (this worked, but that would mean that I need to rewrite all the functionality of NSScroller)
Sparrow seems to successfully do this, and it seems to do it through a regular NSScroller subclass (seeing as it responds to the scroll settings set in System Preferences >> Appearance). It's not really drawing the scroller that's the issue, its just making it overlay the content.
Any advice is appreciated :-)
Here's where you can set the custom class of your scrollbars.
After that, by overriding the -tile method of NSScrollView, you'll get them placed properly.
Here is my solution :
Create a MyScroller class that extends NSScroller
In the MyScroller.m :
#import "MyScroller.h"
#implementation MyScroller
+(CGFloat) scrollerWidth{
return 10;
}
+(CGFloat) scrollerWidthForControlSize:(NSControlSize)controlSize{
return 10;
}
- (void) drawBackground:(NSRect) rect{
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:0 yRadius:0];
[[NSColor whiteColor] set];
[path fill];
}
- (void)drawKnob{
[self drawBackground:[self rectForPart:0]];
[self drawBackground:[self rectForPart:1]];
[self drawBackground:[self rectForPart:2]];
[self drawBackground:[self rectForPart:4]];
[self drawBackground:[self rectForPart:5]];
[self drawBackground:[self rectForPart:6]];
NSRect knobRect = [self rectForPart:NSScrollerKnob];
NSRect newRect = NSMakeRect((knobRect.size.width - [MyScroller scrollerWidth]) / 2, knobRect.origin.y, [MyScroller scrollerWidth], knobRect.size.height);
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:newRect xRadius:5 yRadius:5];
[[NSColor grayColor] set];
[path fill];
}
#end
Then just set the custom class for the Scroller in Interface Builder.
I've recently released RFOverlayScrollView which should solve your problem:
Source: https://github.com/rheinfabrik/RFOverlayScrollView
Blog Post: http://blog.rheinfabrik.de/blog/2013/01/01/introducing-rfoverlayscrollview/
You can change the scrollerStyle over the NSScrollerView control to .overlay to get the desired overlay effect.
This may overrule the system wide setting in System Settings for scroll bar visibility. To 'survive' scroll bar setting changes while your app is running, you must observe the NSScroller.preferredScrollerStyleDidChangeNotification and re-apply the overlay effect.