Cocoa:如何在Mail.app中绘制插入文本?

时间:2014-01-05 00:57:43

标签: objective-c macos cocoa appkit

enter image description here

如何在Cocoa(OS X)中绘制这种文本样式?它似乎在几个Apple应用程序中使用,包括Mail(如上图所示)和Xcode侧边栏中的几个位置。我环顾四周,但未能找到任何资源,建议如何重现这种特定的文本样式。它看起来像一个插入阴影,我的第一个猜测是尝试使用NSShadow,模糊半径设置为负值,但显然只允许正值。有什么想法吗?

3 个答案:

答案 0 :(得分:3)

我有一些代码可以绘制一个浮雕单元格(我相信最初编写的是Jonathon Mah)。它可能不会完全符合您的要求,但它会给您一个开始的地方:

@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

答案 1 :(得分:2)

从屏幕截图中看,它看起来像是用内阴影绘制的文字。因此,使用模糊半径为0的标准NSShadow方法将不起作用,因为它仅在文本下方/上方绘制阴影。

使用内部阴影绘制文本有两个步骤。

<强> 1。获取文本的绘图路径

为了能够在文本字形中绘制阴影,您需要从字符串创建贝塞尔曲线路径。 Apple示例代码 SpeedometerView 具有a category,可将方法-bezierWithFont:添加到NSString。运行项目以查看如何使用此方法。

<强> 2。用内阴影填充路径

在贝塞尔曲线路径下绘制阴影很容易,但在中绘制阴影并非易事。幸运的是,NSBezierPath+MCAdditions类别添加了-[NSBezierPath fillWithInnerShadow:]方法,以简化此操作。

答案 2 :(得分:2)

请不要选择这个作为答案我刚刚实施上述建议以获得乐趣并将其放在这里,因为它可能对将来有用的人有用!

https://github.com/danieljfarrell/InnerShadowTextFieldCell

Subclass of NSTextFieldCell which uses an inner shadow.

根据 indragie wil-shipley 的建议,这里是NSTextFieldCell的子类,它用内部阴影绘制文本。

头文件

// InnerShadowTextFieldCell.h
#import <Cocoa/Cocoa.h>
@interface InnerShadowTextFieldCell : NSTextFieldCell
@property (strong) NSShadow *innerShadow;
@end

现在是实施文件,

// 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

这可能会变得更加强大,但只是绘制静态标签似乎很好。

确保在Interface Builder中更改文本颜色文本背景颜色属性,否则您将无法看到阴影。