Draw Lines Diagonally in a particular NSTableview cell - cocoa

Is there a way to draw lines diagonally in NSTableview cell.Can u please post sample to do this.I am new to the Mac development.Please help me in this issue.
Thanks in advance.......

Yes, easily.
You need to create a subclass of NSTextFieldCell which is actually the type of cell a NSTableView uses to display text.
Subclassing an class creates a new version of that class that does all that the original class did plus more.
This is using Xcode 4. If you are using Xcode 3 let me know.
In Xcode, create a new file by choosing File > New > New File...
In the sheet that pops up choose Objective-C Class and hit Next.
Make it a subclass of NSTextFieldCell, which is what we will be making a modified copy of. Hit Next.
You can save it as anything you want, but for the purposes of this tutorial, save it as MyDiagonalLinedTextFieldCell. Hit Save.
Two new files should pop up.
Click on the .m file. This is the implementation file that tells what the methods in the class do.
Its contents should be similar to below:
//
// MyDiagonalLinedTextFieldCell.m
// CustomCell
//
// Created by spudwaffle on 7/4/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "MyDiagonalLinedTextFieldCell.h"
#implementation MyDiagonalLinedTextFieldCell
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
#end
Below the init method add a drawInteriorWithFrame: inView: method.
The application calls the drawInteriorWithFrame: inView: method each time the cell needs to render on screen.
Your code should now look like this:
#implementation MyDiagonalLinedTextFieldCell
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
}
#end
The first thing you need to do is just draw a standard NSTextFieldCell.
This can be done by calling:
[super drawInteriorWithFrame:cellFrame inView:controlView];
This draws a normal NSTextFieldCell in the exact area the program wants it to.
Now, we need to draw our custom lines. Let's put them 5 pixels apart and make them 1 pixel wide.
This calls for a for loop!
for (int i = 0; i < cellFrame.size.width/5; i ++) {
}
This makes a int that equals 0,adds to that count every time the loop runs, and stops when i reaches the amount of lines that need to be drawn.
Next, put in the drawing code to draw the lines.
for (int i = 0; i < cellFrame.size.width/5; i ++) {
NSBezierPath *path = [NSBezierPath bezierPath];
[path moveToPoint:NSMakePoint(i * 5, cellFrame.origin.y)];
[path lineToPoint:NSMakePoint((i * 5) + 2, cellFrame.origin.y + cellFrame.size.height)];
[[NSColor grayColor]set];
[path setLineWidth:1];
[path stroke];
}
This:
Creates an NSBezierPath, which is used to draw lines and shapes.
Moves the start of the path to the bottom edge of the cell.
Draws a line to the top edge of the cell.
Sets the drawing color to gray.
Sets the drawing line width to 1.
Draws the line.
It does this over and over for each line thanks to the for loop.
Here is the completed MyDiagonalLinedTextFieldCell.m file. You don't need to worry about the .h one for now.
#import "MyDiagonalLinedTextFieldCell.h"
#implementation MyDiagonalLinedTextFieldCell
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
[super drawInteriorWithFrame:cellFrame inView:controlView];
for (int i = 0; i < cellFrame.size.width/5; i ++) {
NSBezierPath *path = [NSBezierPath bezierPath];
[path moveToPoint:NSMakePoint(i * 5, cellFrame.origin.y)];
[path lineToPoint:NSMakePoint((i * 5) + 2, cellFrame.origin.y + cellFrame.size.height)];
[[NSColor grayColor]set];
[path setLineWidth:1];
[path stroke];
}
}
#end
Now, we need to set the cells in the table view to use this class.
Click on your MainMenu.xib file.
Click on the cell in a row of your table view until it turns blue.
Then, hit the button in the right side bar that looks like so:
Change the Class to MyDiagonalLinedTextFieldCell and hit enter.
Now hit run and enjoy the fruits of your labor!
Mess with the drawing code until you get the exact kind of lines you want.
Feel free to contact me with any questions.

This is a beautiful answer, and very well presented. Still, I tried it, and it seems to be incomplete or inaccurate. I have 4 columns in my NSTableView, and apply the custom cell to just the right one - for some reason only the FIRST (left) column gets the special diagonals drawn, no matter what I do.
It seems that the logic in your drawing code is missing some step of "aligning to the positional column" which I really don't know how to do.
you could also improve the sample by only introducing the custom cell .m once - and adding the .h to accompany it - thus demonstrating the inheritance.

Related

How to do batch display using NSTextView

I'd like to be able to show a view that resembles something like a console log, with multiple lines of text that are scrollable and selectable.
The fundamental procedure I have in mind is maintaining an array of strings (call it lines) and appending these to the textStorage of the NSTextView using a new line character as delimiter.
However there are a few factors to consider, such as:
Updating the textStorage on scroll so that it appears seamless to the user
Updating the textStorage on resizing the view height
Maintaining scroll position after the textStorage gets updated
Handling an out of memory possibility
Can someone please provide some guidance or a sample to get me started?
Add a string from your array to the NSTextStorage and animate the NSClipView bounds origin.
- (void)appendText:(NSString*)string {
// Add a newline, if you need to
string = [NSString stringWithFormat:#"%#\n", string];
// Find range
[self.textView.textStorage replaceCharactersInRange:NSMakeRange(self.textView.textStorage.string.length, 0) withString:string];
// Get clip view
NSClipView *clipView = self.textView.enclosingScrollView.contentView;
// Calculate the y position by subtracting
// clip view height from total document height
CGFloat scrollTo = self.textView.frame.size.height - clipView.frame.size.height;
// Animate bounds
[[clipView animator] setBoundsOrigin:NSMakePoint(0, scrollTo)];
}
If you have elasticity set in your NSTextView you need to monitor for its frame changes to get exact results. Add frameDidChange listener to your text view and animate in the handler:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Text view setup
[_textView setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(scrollToBottom) name:NSViewFrameDidChangeNotification object:_textView];
}
- (void)appendText:(NSString*)string {
// Add a newline, if you need to
string = [NSString stringWithFormat:#"%#\n", string];
// Find range
[self.textView.textStorage replaceCharactersInRange:NSMakeRange(self.textView.textStorage.string.length, 0) withString:string];
}
- (void)scrollToBottom {
// Get the clip view and calculate y position
NSClipView *clipView = self.textView.enclosingScrollView.contentView;
// Y position for bottom is document's height - viewport height
CGFloat scrollTo = self.textView.frame.size.height - clipView.frame.size.height;
[[clipView animator] setBoundsOrigin:NSMakePoint(0, scrollTo)];
}
In real-life application you would probably need to set some sort of threshold to see if the user has scrolled away from the end more than the height of a line.

How to move to the right the label Core Plot?

I use the following method to display the labels for my plot:
-(CPTLayer *)dataLabelForPlot:(CPTPlot *)plot recordIndex:(NSUInteger)index{
...
CPTTextLayer *label=[[CPTTextLayer alloc] initWithText:stringValue style:textStyle];
}
which for every index should return the label
I know that it's possible to move label up or down using:
plot.labelOffset=10;
The question is: how can i move the label a bit to the right?
I tried to use
label.paddingLeft=50.0f;
but it doesn't work.
Adding padding as in your example does work, but maybe not in the way you expect. Scatter and bar plots will center the label above each data point (with a positive offset). The padding makes the whole label wider so when centered, the test appears off to the side. It's hard to control, especially if the label texts are different lengths.
There is an outstanding issue to address this (issue 266). No guarantees when it will be fixed, but it is something we're looking at.
I ran into the same problem and came up with a different solution.
What I decided to do was to create the label using the CPTAxisLabel method initWithContentLayer:
CPTTextLayer *textLayer = [[CPTTextLayer alloc] initWithText:labelStr style:axisTextStyle];
CGSize textSize = [textLayer sizeThatFits];
// Calculate the padding needed to center the label under the plot record.
textLayer.paddingLeft = barCenterLeftOffset - textSize.width/2.0;
CPTAxisLabel *label = [[CPTAxisLabel alloc] initWithContentLayer:textLayer];
Here barCenterLeftOffset is the offset of the center of the plot record.
I wrote an article about this:
http://finalize.com/2014/09/18/horizontal-label-positioning-in-core-plot-and-other-miscellaneous-topics/
A demo project I created that uses this solution can be found at:
https://github.com/scottcarter/algorithms
You can subclass CPTTextLayer and include an offset.
#interface WPTextLayer : CPTTextLayer
#property (nonatomic) CGPoint offset;
#end
#implementation WPTextLayer
-(void)setPosition:(CGPoint)position
{
CGPoint p = CGPointMake(position.x + self.offset.x, position.y + self.offset.y);
[super setPosition:p];
}
Then Use:
WPTextLayer *tLayer = [[WPTextLayer alloc] initWithText:#"blah" style:textStyle];
tLayer.offset = CGPointMake(3, -3);
return tLayer;
There may be consequences of this that I'm not aware of, but it seems to be working so far.

View-backed NSTableView: Inserted Column Width/Margin Issues

I have a view-based NSTableView that usually has one column in it. However, at a button press, I want a new column to slide in from the left (very similar to what happens on an iPhone when you click the Edit button in Mail). For now, the view that I want to slide is in a very simple view that draws a solid background: its drawRect: just does
[[NSColor blueColor] set];
[[NSBezierPath bezierPathWithRect:[self bounds]] fill];
In the delegate for my NSTableView, I have the following:
- (IBAction)buttonPressed:(id)sender
{
NSTableColumn *newColumn = [[NSTableColumn alloc] initWithIdentifier:#"InPreviewColumn"];
[newColumn setWidth:40];
[newColumn setMinWidth:[newColumn width]];
/* To make up for there not being an insertColumnAt: method,
hide the column, add it, and move it to the front before showing it. */
[newColumn setHidden:YES];
[availableFontsView beginUpdates];
[availableFontsView addTableColumn:newColumn];
[availableFontsView moveColumn:1 toColumn:0];
[availableFontsView endUpdates];
[newColumn setHidden:NO];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
/* ... code for main column ... */
else if([[tableColumn identifier] isEqualToString:#"InPreviewColumn"])
{
USSolidBackgroundView *v = [tableView makeViewWithIdentifier:[tableColumn identifier] owner:self];
if(!v)
{
v = [[[USSolidBackgroundView alloc] initWithFrame:NSMakeRect(0, 0, [tableColumn width], 0)] autorelease];
[v setIdentifier:[tableColumn identifier]];
[v setAutoresizingMask:NSViewMinXMargin | NSViewWidthSizable | NSViewMaxXMargin];
}
return v;
}
}
Yet, when I do this, I end up with this result (there's a big non-blue margin between the blue part of the new column and the start of the main column):
When the additional column isn't present, there's no margin so I'm pretty sure that the problem isn't with the other view (since there's no margin when it's the only view displayed).
I've used logging statements to verify that solid color view's bounds always has a width of 40 (in its drawRect:) and, at least when the view is created, the table column has a width of 40 as well.
So where does this margin come from? No matter how I size the column, it seem that only roughly half of it is blue. So, the bigger the column, the bigger the margin.
How do I make the entire extra column blue?
The issue was my being stupid: In the view that draws the font names, I was basing the position on [self frame]. Which is wrong.
[self bounds] is the way to go. Which I knew. Can't believe I made this mistake. Amateur hour.
If you wander by this question, feel free to vote to close it or flag it or whatever it is that's supposed to work on Stackoverflow.
My apologies.

Flipped NSView mouse coordinates

I have a subclass of NSView that re-implements a number of the mouse event functions. For instance in mouseDown to get the point from the NSEvent I use:
NSEvent *theEvent; // <- argument to function
NSPoint p = [theEvent locationInWindow];
p = [self convertPoint:p fromView:nil];
However the coordinates seem to be flipped, (0, 0) is in the bottom left of the window?
EDIT: I have already overridden the isFlipped method to return TRUE, but it has only affected drawing. Sorry, can't believe I forgot to put that straight away.
What do you mean by flipped? Mac uses a LLO (lower-left-origin) coordinate system for everything.
EDIT I can't reproduce this with a simple project. I created a single NSView implemented like this:
#implementation FlipView
- (BOOL)isFlipped {
return YES;
}
- (void)mouseDown:(NSEvent *)theEvent {
NSPoint p = [theEvent locationInWindow];
p = [self convertPoint:p fromView:nil];
NSLog(#"%#", NSStringFromPoint(p));
}
#end
I received the coordinates I would expect. Removing the isFlipped switched the orientation as expected. Do you have a simple project that demonstrates your problmem?
I found this so obnoxious - until one day I just sat down and refused to get up until I had something that worked perfectly . Here it is.. called via...
-(void) mouseDown:(NSEvent *)click{
NSPoint mD = [NSScreen wtfIsTheMouse:click
relativeToView:self];
}
invokes a Category on NSScreen....
#implementation NSScreen (FlippingOut)
+ (NSPoint)wtfIsTheMouse:(NSEvent*)anyEevent
relativeToView:(NSView *)view {
NSScreen *now = [NSScreen currentScreenForMouseLocation];
return [now flipPoint:[now convertToScreenFromLocalPoint:event.locationInWindow relativeToView:view]];
}
- (NSPoint)flipPoint:(NSPoint)aPoint {
return (NSPoint) { aPoint.x,
self.frame.size.height - aPoint.y };
}
- (NSPoint)convertToScreenFromLocalPoint:(NSPoint)point
relativeToView:(NSView *)view {
NSPoint winP, scrnP, flipScrnP;
if(self) {
winP = [view convertPoint:point toView:nil];
scrnP = [[view window] convertBaseToScreen:winP];
flipScrnP = [self flipPoint:scrnP];
flipScrnP.y += [self frame].origin.y;
return flipScrnP;
} return NSZeroPoint;
}
#end
Hope this can prevent just one minor freakout.. somewhere, someday. For the children, damnit. I beg of you.. for the children.
This code worked for me:
NSPoint location = [self convertPoint:theEvent.locationInWindow fromView:nil];
location.y = self.frame.size.height - location.y;
This isn't "flipped", necessarily, that's just how Quartz does coordinates. An excerpt from the documentation on Quartz 2D:
A point in user space is represented by a coordinate pair (x,y), where x represents the location along the horizontal axis (left and right) and y represents the vertical axis (up and down). The origin of the user coordinate space is the point (0,0). The origin is located at the lower-left corner of the page, as shown in Figure 1-4. In the default coordinate system for Quartz, the x-axis increases as it moves from the left toward the right of the page. The y-axis increases in value as it moves from the bottom toward the top of the page.
I'm not sure what your question is, though. Are you looking for a way to get the "flipped" coordinates? If so, you can subclass your NSView, overriding the -(BOOL)isFlipped method to return YES.

Is there a "right" way to have NSTextFieldCell draw vertically centered text?

I have an NSTableView with several text columns. By default, the dataCell for these columns is an instance of Apple's NSTextFieldCell class, which does all kinds of wonderful things, but it draws text aligned with the top of the cell, and I want the text to be vertically centered in the cell.
There is an internal flag in NSTextFieldCell that can be used to vertically center the text, and it works beautifully. However, since it is an internal flag, its use is not sanctioned by Apple and it could simply disappear without warning in a future release. I am currently using this internal flag because it is simple and effective. Apple has obviously spent some time implementing the feature, so I dislike the idea of re-implementing it.
So; my question is this: What is the right way to implement something that behaves exactly like Apple's NStextFieldCell, but draws vertically centered text instead of top-aligned?
For the record, here is my current "solution":
#interface NSTextFieldCell (MyCategories)
- (void)setVerticalCentering:(BOOL)centerVertical;
#end
#implementation NSTextFieldCell (MyCategories)
- (void)setVerticalCentering:(BOOL)centerVertical
{
#try { _cFlags.vCentered = centerVertical ? 1 : 0; }
#catch(...) { NSLog(#"*** unable to set vertical centering"); }
}
#end
Used as follows:
[[myTableColumn dataCell] setVerticalCentering:YES];
The other answers didn't work for multiple lines. Therefore I initially continued using the undocumented cFlags.vCentered property, but that caused my app to be rejected from the app store. I ended up using a modified version of Matt Bell's solution that works for multiple lines, word wrapping, and a truncated last line:
-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSAttributedString *attrString = self.attributedStringValue;
/* if your values can be attributed strings, make them white when selected */
if (self.isHighlighted && self.backgroundStyle==NSBackgroundStyleDark) {
NSMutableAttributedString *whiteString = attrString.mutableCopy;
[whiteString addAttribute: NSForegroundColorAttributeName
value: [NSColor whiteColor]
range: NSMakeRange(0, whiteString.length) ];
attrString = whiteString;
}
[attrString drawWithRect: [self titleRectForBounds:cellFrame]
options: NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin];
}
- (NSRect)titleRectForBounds:(NSRect)theRect {
/* get the standard text content rectangle */
NSRect titleFrame = [super titleRectForBounds:theRect];
/* find out how big the rendered text will be */
NSAttributedString *attrString = self.attributedStringValue;
NSRect textRect = [attrString boundingRectWithSize: titleFrame.size
options: NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin ];
/* If the height of the rendered text is less then the available height,
* we modify the titleRect to center the text vertically */
if (textRect.size.height < titleFrame.size.height) {
titleFrame.origin.y = theRect.origin.y + (theRect.size.height - textRect.size.height) / 2.0;
titleFrame.size.height = textRect.size.height;
}
return titleFrame;
}
(This code assumes ARC; add an autorelease after attrString.mutableCopy if you use manual memory management)
Overriding NSCell's -titleRectForBounds: should do it -- that's the method responsible for telling the cell where to draw its text:
- (NSRect)titleRectForBounds:(NSRect)theRect {
NSRect titleFrame = [super titleRectForBounds:theRect];
NSSize titleSize = [[self attributedStringValue] size];
titleFrame.origin.y = theRect.origin.y + (theRect.size.height - titleSize.height) / 2.0;
return titleFrame;
}
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSRect titleRect = [self titleRectForBounds:cellFrame];
[[self attributedStringValue] drawInRect:titleRect];
}
For anyone attempting this using Matt Ball's drawInteriorWithFrame:inView: method, this will no longer draw a background if you have set your cell to draw one. To solve this add something along the lines of
[[NSColor lightGrayColor] set];
NSRectFill(cellFrame);
to the beginning of your drawInteriorWithFrame:inView: method.
FYI, this works well, although I haven't managed to get it to stay centered when you edit the cell... I sometimes have cells with large amounts of text and this code can result in them being misaligned if the text height is greater then the cell it's trying to vertically center it in. Here's my modified method:
- (NSRect)titleRectForBounds:(NSRect)theRect
{
NSRect titleFrame = [super titleRectForBounds:theRect];
NSSize titleSize = [[self attributedStringValue] size];
// test to see if the text height is bigger then the cell, if it is,
// don't try to center it or it will be pushed up out of the cell!
if ( titleSize.height < theRect.size.height ) {
titleFrame.origin.y = theRect.origin.y + (theRect.size.height - titleSize.height) / 2.0;
}
return titleFrame;
}
No. The right way is to put the Field in another view and use auto layout or that parent view's layout to position it.
Though this is pretty old question...
I believe default style of NSTableView implementation is intended strictly for single line text display with all same size & font.
In that case, I recommend,
Set font.
Adjust rowHeight.
Maybe you will get quietly dense rows. And then, give them padding by setting intercellSpacing.
For example,
core_table_view.rowHeight = [NSFont systemFontSizeForControlSize:(NSSmallControlSize)] + 4;
core_table_view.intercellSpacing = CGSizeMake(10, 80);
Here what you'll get with two property adjustment.
This won't work for multi-line text, but very good enough for quick vertical center if you don't need multi-line support.
I had the same problem and here is the solution I did :
1) In Interface Builder, select your NSTableCellView. Make sure it as big as the row height in the Size Inspector. For example, if your row height is 32, make your Cell height 32
2) Make sure your cell is well placed in your row (I mean visible)
3) Select your TextField inside your Cell and go to your size inspector
4) You should see "Arrange" item and select "Center Vertically in Container"
--> The TextField will center itself in the cell

Resources