Drawing text above the divider in an NSSplitView, the top view will occasionally draw over it - objective-c

Here's a .swf (pardon, the bad website and swf, that was the only way I could capture what was happening)
http://screencast.com/t/rzJ3b5ihSj
What appears to be happening, is that my divider is occasionally drawn first, and then the top NSView in the NSSplitView draws over it. But it seems inconsistent, because sometimes the divider draws on top.
Here is my -drawDividerInRect method, overridden from NSSplitView
-(void) drawDividerInRect:(NSRect)aRect
{
[[NSColor colorWithRed:10.0/255.0 green:10.0/255.0 blue:10.0/255.0 alpha:0.0] set];
NSRectFill(aRect);
id topView = [[self subviews] objectAtIndex:0];
NSRect topViewFrameRect = [topView frame];
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont fontWithName:#"Helvetica" size:26], NSFontAttributeName,[NSColor whiteColor], NSForegroundColorAttributeName, nil];
NSAttributedString * currentText=[[NSAttributedString alloc] initWithString:#"Tool Properties" attributes: attributes];
NSSize stringSize = [currentText size];
CGFloat xOffset = ([topView frame].size.width - stringSize.width)/2;
NSRect textRect = NSMakeRect(topViewFrameRect.origin.x+xOffset, topViewFrameRect.size.height+50, stringSize.width, stringSize.height);
[currentText drawInRect:textRect];
}
How can I make it so my divider & text within it draws on top all the time?

So actually the divider, and the text I wanted to draw, were all being done on the NSSplitView itself. The two views on either side of the divider are subviews, and so there's no way to draw in front of them. What I ended up doing was add some padding at the bottom of the top view so that the divider text is always exposed

Related

Cocoa: How to draw inset text as in Mail.app?

How can I draw this style of text in Cocoa (OS X)? It seems to be used in several Apple apps including Mail (as pictured above) and several places in Xcode sidebars. I've looked around but haven't been able to find any resources suggesting how to reproduce this specific style of text. It looks like an inset shadow and my first guess was to try using an NSShadow with the blur radius set to a negative value but apparently only positive values are allowed. Any ideas?
I have some code that draws an embossed cell (originally written Jonathon Mah, I believe). It might not do exactly what you want but it'll give you a place to start:
#implementation DMEmbossedTextFieldCell
#pragma mark NSCell
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
/* This method copies the three-layer method used by Safari's error page. That's accessible by forcing an
* error (e.g. visiting <foo://>) and opening the web inspector. */
// I tried to use NSBaselineOffsetAttributeName instead of shifting the frame, but that didn't seem to work
const NSRect onePixelUpFrame = NSOffsetRect(cellFrame, 0.0, [NSGraphicsContext currentContext].isFlipped ? -1.0 : 1.0);
const NSRange fullRange = NSMakeRange(0, self.attributedStringValue.length);
NSMutableAttributedString *scratchString = [self.attributedStringValue mutableCopy];
BOOL overDark = (self.backgroundStyle == NSBackgroundStyleDark);
CGFloat (^whenLight)(CGFloat) = ^(CGFloat b) { return overDark ? 1.0 - b : b; };
// Layer 1
[scratchString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithCalibratedWhite:whenLight(1.0) alpha:1.0] range:fullRange];
[scratchString drawInRect:cellFrame];
// Layer 2
BOOL useGradient = NO; // Safari 5.2 preview has switched to a lighter, solid color look for the detail text. Since we use the same class, use bold-ness to decide
if (self.attributedStringValue.length > 0) {
NSFont *font = [self.attributedStringValue attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL];
if ([[NSFontManager sharedFontManager] traitsOfFont:font] & NSBoldFontMask)
useGradient = YES;
}
NSColor *solidShade = [NSColor colorWithCalibratedHue:200/360.0 saturation:0.03 brightness:whenLight(0.41) alpha:1.0];
[scratchString addAttribute:NSForegroundColorAttributeName value:solidShade range:fullRange];
[scratchString drawInRect:onePixelUpFrame];
// Layer 3 (Safari uses alpha of 0.25)
[scratchString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithCalibratedWhite:whenLight(1.0) alpha:0.25] range:fullRange];
[scratchString drawInRect:cellFrame];
}
#end
Please don't pick this as the answer I just implemented the above suggestions for fun and put it here because it will probably be useful to someone in the future!
https://github.com/danieljfarrell/InnerShadowTextFieldCell
Following from the advice of indragie and wil-shipley here is a subclass of NSTextFieldCell that draws the text with an inner shadow.
The header file,
// InnerShadowTextFieldCell.h
#import <Cocoa/Cocoa.h>
#interface InnerShadowTextFieldCell : NSTextFieldCell
#property (strong) NSShadow *innerShadow;
#end
Now the implementation file,
// InnerShadowTextFieldCell.m
#import "InnerShadowTextFieldCell.h"
// This class needs the NSString category -bezierWithFont: from,
// https://developer.apple.com/library/mac/samplecode/SpeedometerView/Listings/SpeedyCategories_m.html
#implementation InnerShadowTextFieldCell
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
//// Shadow Declarations
if (_innerShadow == nil) {
/* Inner shadow has not been set, override here with default shadow.
You may or may not want this behaviour. */
_innerShadow = [[NSShadow alloc] init];
[_innerShadow setShadowColor: [NSColor darkGrayColor]];
[_innerShadow setShadowOffset: NSMakeSize(0.1, 0.1)];
/* Trying to find a default shadow radius which will look good for
a label of any size, let's get a rough estimate based on the
hypotenuse of the cell frame. */
[_innerShadow setShadowBlurRadius: 0.0075 * hypot(NSWidth(cellFrame), NSHeight(cellFrame)) ];
}
/* Because we are using the -bezierWithFont: we get a slightly
different path than had we used the superclass to drawn the
text path. This means that the background colour and text
colour looks odd if we use call the superclass's,
-drawInteriorWithFrame:inView: method let's do that
drawing here. Not making the call to super might cause
problems for general use (?) but for a simple label is seems
to work OK */
[self.backgroundColor setFill];
NSRectFill(cellFrame);
NSBezierPath *bezierPath = [self.title bezierWithFont:self.font];
[self.textColor setFill];
[bezierPath fill];
/* The following is inner shadow drawing method is taken from Paint Code */
////// Bezier Inner Shadow
NSShadow *shadow = _innerShadow;
NSRect bezierBorderRect = NSInsetRect([bezierPath bounds], -shadow.shadowBlurRadius, -shadow.shadowBlurRadius);
bezierBorderRect = NSOffsetRect(bezierBorderRect, -shadow.shadowOffset.width, shadow.shadowOffset.height);
bezierBorderRect = NSInsetRect(NSUnionRect(bezierBorderRect, [bezierPath bounds]), -1, -1);
NSBezierPath* bezierNegativePath = [NSBezierPath bezierPathWithRect: bezierBorderRect];
[bezierNegativePath appendBezierPath: bezierPath];
[bezierNegativePath setWindingRule: NSEvenOddWindingRule];
[NSGraphicsContext saveGraphicsState];
{
NSShadow* shadowWithOffset = [shadow copy];
CGFloat xOffset = shadowWithOffset.shadowOffset.width + round(bezierBorderRect.size.width);
CGFloat yOffset = shadowWithOffset.shadowOffset.height;
shadowWithOffset.shadowOffset = NSMakeSize(xOffset + copysign(0.0, xOffset), yOffset + copysign(0.1, yOffset));
[shadowWithOffset set];
[[NSColor grayColor] setFill];
[bezierPath addClip];
NSAffineTransform* transform = [NSAffineTransform transform];
[transform translateXBy: -round(bezierBorderRect.size.width) yBy: 0];
[[transform transformBezierPath: bezierNegativePath] fill];
}
[NSGraphicsContext restoreGraphicsState];
}
#end
This could probably be made more robust but it seems fine for just drawing static labels.
Make sure your change the text color and text background color properties in Interface Builder otherwise you will not be able to see the shadow.
From your screenshot, that looks like text drawn with an inner shadow. Hence, the standard NSShadow method of using a blur radius of 0 won't work because that only draws the shadow under/above the text.
There are two steps to drawing text with an inner shadow.
1. Get the drawing path of the text
To be able to draw a shadow inside the text glyphs, you need to create a bezier path from the string. The Apple sample code SpeedometerView has a category that adds the method -bezierWithFont: to NSString. Run the project to see how this method is used.
2. Fill the path with an inner shadow
Drawing shadows under bezier paths is easy, but drawing a shadow inside one is not trivial. Fortunately, the NSBezierPath+MCAdditions category adds the -[NSBezierPath fillWithInnerShadow:] method to make this easy.

Adding an image border

OK, this is what I'm trying to do :
Get an NSImage containing, let's say a photo (1000+ x 1000+ dimensions).
Get another NSImage containing just a tranparent background and a simple black border (500x500).
"Combine" the 2 images, so that the resulting image is the photo with a border.
This is what I've achieved so far :
NSImage* resultImage = [[[self drop] image] copy];
[resultImage lockFocus];
NSRect newRect = NSMakeRect(0, 0, [[[self drop] image] size].width, [[[self drop] image] size].height);
[[[self drop2] image] drawInRect:newRect
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0];
[resultImage unlockFocus];
[[self drop] setImage:resultImage];
Where [self drop] is an ImageWell containing the photo, and [self drop2] an ImageWell containing the border.
The thing is that it IS working. However, the resulting image is - quite obviously - showing a somewhat "stretched" border.
How could I resolve that? Given that the original photo should be of ANY dimensions, how could I make it to use a border (of some fixed dimensions) and still avoid stretching?
How about doing the border directly with CALayer, e.g.:
#import <QuartzCore/QuartzCore.h>
CALayer *layer = imageView.layer;
layer.borderColor = [[NSColor blackColor] CGColor];
layer.borderWidth = 10;
I would do this differently. Just size the image as desired and then add the border. You could do this just by having a simple view with black background, or a suitable image (assuming you want to have customizable image borders, like frames), sized to always keep the resulting border constant. Then you can generate a new image from that view, if you need to.

NSWindow Shadow Outline

I am drawing a custom window by setting a custom content view for the window. When I draw the custom view I give it rounded corners and a nice outline to mimic a proper window.
However, I see another 1 px outline around the window which strays from the edge at the corners. I have found that if I turn off the shadow it goes away, but obviously as this wants to act like a window I need the shadow. Here's what I mean about the 1px outline:
How can I prevent this?
EDIT
Code for drawing the custom window's content view:
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] cornerRadius:5];
NSGradient* aGradient = [[[NSGradient alloc] initWithColorsAndLocations:
[NSColor colorWithDeviceRed:0.5569 green:0.5137 blue:0.4588 alpha:1.0000], 0.0,
[NSColor colorWithDeviceRed:0.5569 green:0.5137 blue:0.4588 alpha:1.0000], 1.0,
nil] autorelease];
[aGradient drawInBezierPath:path angle:90];
[path setLineWidth:4];
[[NSColor colorWithDeviceRed:0.4235 green:0.3922 blue:0.3451 alpha:0.9000] setStroke];
[path strokeInside];
[path setLineWidth:3];
[[NSColor colorWithDeviceRed:0.8431 green:0.8314 blue:0.8078 alpha:1.0000] setStroke];
[path strokeInside];
[path setLineWidth:1];
[[NSColor colorWithDeviceRed:0.4235 green:0.3922 blue:0.3451 alpha:0.9000] setStroke];
[path strokeInside];
Don't ask me how I got this, but this will solve your problem.
Define a category for NSWindow with the following content:
#implementation NSWindow(NoShadowRim)
- (id)_shadowRimInfo {
return #{
#"kCUIMeasureWindowFrameRimDensity": [NSNumber numberWithInt:0]
};
}
#end
DISCLAIMER: This overrides the internal method of NSWindow, so use it at your own risk. It may break with any OS X update.
You need to tell the window to recompute its shadow by sending it -invalidateShadow.
Try:
[[self window] display];
[[self window] setHasShadow:NO];
[[self window] setHasShadow:YES];
This line contouring the window area is drawn automatically. I have a window which has this line running accurately around bottom rounded corners. You have to setup the window as non-opaque and the background color to transparent:
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
The somewhere in the contentView -drawRect: you do
[NSGraphicsContext saveGraphicsState];
[pathWithBottomRoundedCorner addClip];
// your drawing here...
[NSGraphicsContext restoreGraphicsState];
That should work.
As I understand correctly, shadows are drawn by windows server. When you draw custom NSWindow with rounded corners or other not rectangular shapes, window server don't count those transparent pixels and dont drop shadow under them.
I developed some hack to avoid such behavior. Just drop additional shadow under your path, something like this:
NSShadow *headShadow = [[NSShadow alloc] init];
[headShadow setShadowColor:[NSColor colorWithSRGBRed:0.0
green:0.0
blue:0.0
alpha:0.16]];
[headShadow setShadowBlurRadius:0.0f];
[headShadow setShadowOffset:NSMakeSize(0.0f, 0.0f)];
[headShadow set];
Ideally for perfect result i fink shadow must be equal to window servers.

How to programmatically add text to a UIView

I have a UIView that I'd like to add several bits of text to. I have used a UITextView but I think that's overkill as it doesn't need to be editable. I thought about using a UILabel or a UITextField, but I don't see how you tell the superview where to position the UILabel or UITextField within itself. I want the lowest footprint object that will let me put text of a font/color/size of my choosing in my UIView where I want it. Not too much to ask, eh?
The simplest approach for you would be:
UILabel *yourLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 300, 20)];
[yourLabel setTextColor:[UIColor blackColor]];
[yourLabel setBackgroundColor:[UIColor clearColor]];
[yourLabel setFont:[UIFont fontWithName: #"Trebuchet MS" size: 14.0f]];
[yourSuperView addSubview:yourLabel];
Building or populating Views in your code will probably require you to use CGRectMake a lot.
As its name says, it creates a rectangle that you can use to set the relative position (relative to the borders of your superview) and size of your UIView-Subclass (in this case a UILabel).
It works like this:
yourLabel.Frame = CGRectMake(x, y, width, height); //x,y,width,height are float values.
x defines the spacing between the left hand border of the superview and the beginning of the subview your about to add, same applies to y but relating to the spacing between top-border of your superview.
then width and height are self-explanatory i think.
Hope this gets you on the track.
Instead of finding a way to tell the view where to position the UILabel, you can tell the UILabel where to position itself in the view by using "center".
E.g.
myLabel.center = CGPointMake(0.0, 0.0);
Hope you'll be able to use UILabel, for me it's the basic form of a flexible non editable text.
For Swift:
let yourLabel = UILabel(frame: CGRectMake(100, 100, 100, 100))
yourLabel.textColor = UIColor.whiteColor()
yourLabel.backgroundColor = UIColor.blackColor()
yourLabel.text = "mylabel text"
yoursuperview.addSubview(yourLabel)
This question is old, but for a pure UIView text option without using UILabel or UITextField (as all the other answers describe, but the question is how to do it without them), drawRect in a subclassed UIView works for me. Like so:
- (void)drawRect:(CGRect)rect{
NSString *string = #"Hello World!";
[string drawAtPoint:CGPointMake(100, 100) withFont:[UIFont boldSystemFontOfSize:16.0]];
}
This routine displays a text at a X-Y position
-(void)placeText:(NSString *)theText:(int)theX:(int)theY {
UILabel *textLabel;
// Set font and calculate used space
UIFont *textFont = [UIFont fontWithName:#"Helvetica" size:14];
CGSize textStringSize = [theText sizeWithFont:textFont constrainedToSize:CGSizeMake(300,50) lineBreakMode:NSLineBreakByTruncatingTail];
// Position of the text
textLabel = [[UILabel alloc] initWithFrame:CGRectMake(theX+OFFSETIMAGEX-(textStringSize.width/2), theY+OFFSETIMAGEY-(textStringSize.height/2), textStringSize.width,textStringSize.height)];
// Set text attributes
textLabel.textColor = [UIColor blackColor];
textLabel.backgroundColor = [UIColor orangeColor];
textLabel.font = textFont;
textLabel.text = theText;
// Display text
[self.view addSubview:textLabel];
}
It might be late but here is what I use:-
CGRect labelFrame = CGRectMake(120,300, 530, 100);
UILabel *myLabel = [[UILabel alloc] initWithFrame:labelFrame];
//If you need to change the color
[myLabel setTextColor:[UIColor whiteColor]];
//If you need to change the system font
[myLabel setFont:[UIFont fontWithName:NULL size:23]];
//If you need alignment
[myLabel setTextAlignment:NSTextAlignmentCenter];
// The label will use an unlimited number of lines
[myLabel setNumberOfLines:0];
//Add label view to current view
[self.view addSubview:myLabel];
NSString *someString = #"Sample String, Yarp!";
myLabel.text = someString;
add a UILabel to your View. then override the View's layoutSubviews method.

Drawing text in a NSButton subclass - positioning

I Have a custom button:
My Button http://cld.ly/29ubq
And I need the text to be centered, here's my code:
NSMutableParagraphStyle *style =
[[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setLineBreakMode:NSLineBreakByWordWrapping];
[style setAlignment:NSLeftTextAlignment];
att = [[NSDictionary alloc] initWithObjectsAndKeys:
style, NSParagraphStyleAttributeName,
[NSColor blackColor],
NSForegroundColorAttributeName, nil];
[style release];
// other Drawing code here
[[self title] drawInRect:[self bounds] withAttributes:att];
How do I center the text in the center of my button (not center of the bounds)?
You want to get the size of the title first using NSString -sizeWithAttributes:. Then you can use that size to adjust the drawing of title within your bounds.
There is a trick to this, since you will almost always wind up dividing by two at some point in this process. When you do that, you may wind up with a half-pixel result, which Cocoa will happily use. However, it will cause your text to be blurry. You almost always want to call floor() on your coordinates before drawing on them to get yourself back onto integral pixels.
Edit: A basic example of centering (this should compile correctly, I haven't compiled it and my recent work has been over on iPhone which is upside-down):
NSRect rect;
rect.size = [[self title] sizeWithAttributes:att];
rect.origin.x = floor( NSMidX([self bounds]) - rect.size.width / 2 );
rect.origin.y = floor( NSMidY([self bounds]) - rect.size.height / 2 );
[[self title] drawInRect:rect withAttributes:att];
Anyway, something along those lines. You may want to offset a bit because of how your button draws, but this is the basic idea.
You could be interested in Setting a Button’s Image, which also explain how to position the title relative to the button’s image.