你如何抚摸 NSAttributedString 的 _outside_ ?
Posted
技术标签:
【中文标题】你如何抚摸 NSAttributedString 的 _outside_ ?【英文标题】:How do you stroke the _outside_ of an NSAttributedString? 【发布时间】:2010-12-17 04:06:20 【问题描述】:我一直在 NSAttributedString
对象上使用 NSStrokeWidthAttributeName
在绘制文本时为其添加轮廓。问题是笔画在文本的填充区域内。当文本很小(例如 1 像素厚)时,笔划会使文本难以阅读。我真正想要的是外面的中风。有什么办法吗?
我试过NSShadow
没有偏移和模糊,但它太模糊且难以看到。如果有一种方法可以在不产生任何模糊的情况下增加阴影的大小,那也可以。
【问题讨论】:
【参考方案1】:虽然可能有其他方法,但实现此目的的一种方法是首先仅使用笔画绘制字符串,然后仅使用填充绘制字符串,直接覆盖先前绘制的内容。 (Adobe InDesign 实际上有这个内置功能,它似乎只将笔划应用于字母的外部,这有助于提高可读性)。
这只是一个示例视图,展示了如何实现这一点(受http://developer.apple.com/library/mac/#qa/qa2008/qa1531.html 启发):
先设置属性:
@implementation MDInDesignTextView
static NSMutableDictionary *regularAttributes = nil;
static NSMutableDictionary *indesignBackgroundAttributes = nil;
static NSMutableDictionary *indesignForegroundAttributes = nil;
- (void)drawRect:(NSRect)frame
NSString *string = @"Got stroke?";
if (regularAttributes == nil)
regularAttributes = [[NSMutableDictionary
dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize:64.0],NSFontAttributeName,
[NSColor whiteColor],NSForegroundColorAttributeName,
[NSNumber numberWithFloat:-5.0],NSStrokeWidthAttributeName,
[NSColor blackColor],NSStrokeColorAttributeName, nil] retain];
if (indesignBackgroundAttributes == nil)
indesignBackgroundAttributes = [[NSMutableDictionary
dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize:64.0],NSFontAttributeName,
[NSNumber numberWithFloat:-5.0],NSStrokeWidthAttributeName,
[NSColor blackColor],NSStrokeColorAttributeName, nil] retain];
if (indesignForegroundAttributes == nil)
indesignForegroundAttributes = [[NSMutableDictionary
dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize:64.0],NSFontAttributeName,
[NSColor whiteColor],NSForegroundColorAttributeName, nil] retain];
[[NSColor grayColor] set];
[NSBezierPath fillRect:frame];
// draw top string
[string drawAtPoint:
NSMakePoint(frame.origin.x + 200.0, frame.origin.y + 200.0)
withAttributes:regularAttributes];
// draw bottom string in two passes
[string drawAtPoint:
NSMakePoint(frame.origin.x + 200.0, frame.origin.y + 140.0)
withAttributes:indesignBackgroundAttributes];
[string drawAtPoint:
NSMakePoint(frame.origin.x + 200.0, frame.origin.y + 140.0)
withAttributes:indesignForegroundAttributes];
@end
这会产生以下输出:
现在,它并不完美,因为字形有时会落在分数边界上,但是,它看起来肯定比默认值更好。
如果性能是一个问题,您总是可以考虑降低到稍低的级别,例如 CoreGraphics 或 CoreText。
【讨论】:
很好,详细的答案。感谢您花时间! 简单。杰出的。 ...并且应该完全没有必要...(对此应该已经存在支持)【参考方案2】:根据@NSGod 的回答将我的解决方案留在这里,结果与UILabel
中的正确定位几乎相同
在 ios 14 上使用默认系统字体抚摸字母时出现错误时也很有用(另请参阅 this question)
错误:
@interface StrokedTextLabel : UILabel
@end
/**
* https://***.com/a/4468880/3004003
*/
@implementation StrokedTextLabel
- (void)drawTextInRect:(CGRect)rect
if (!self.attributedText)
[super drawTextInRect:rect];
return;
NSMutableAttributedString *attributedText = self.attributedText.mutableCopy;
[attributedText enumerateAttributesInRange:NSMakeRange(0, attributedText.length) options:0 usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange range, BOOL *stop)
if (attrs[NSStrokeWidthAttributeName])
// 1. draw underlying stroked string
// use doubled stroke width to simulate outer border, because border is being stroked
// in both outer & inner directions with half width
CGFloat strokeWidth = [attrs[NSStrokeWidthAttributeName] floatValue] * 2;
[attributedText addAttributes:@NSStrokeWidthAttributeName : @(strokeWidth) range:range];
self.attributedText = attributedText;
// perform default drawing
[super drawTextInRect:rect];
// 2. draw unstroked string above
NSMutableParagraphStyle *style = [NSMutableParagraphStyle new];
style.alignment = self.textAlignment;
[attributedText addAttributes:@
NSStrokeWidthAttributeName : @(0),
NSForegroundColorAttributeName : self.textColor,
NSFontAttributeName : self.font,
NSParagraphStyleAttributeName : style
range:range];
// we use here custom bounding rect detection method instead of
// [attributedText boundingRectWithSize:...] because the latter gives incorrect result
// in this case
CGRect textRect = [self boundingRectWithAttributedString:attributedText forCharacterRange:NSMakeRange(0, attributedText.length)];
[attributedText boundingRectWithSize:rect.size options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
// adjust vertical position because returned bounding rect has zero origin
textRect.origin.y = (rect.size.height - textRect.size.height) / 2;
[attributedText drawInRect:textRect];
];
/**
* https://***.com/a/20633388/3004003
*/
- (CGRect)boundingRectWithAttributedString:(NSAttributedString *)attributedString
forCharacterRange:(NSRange)range
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size];
textContainer.lineFragmentPadding = 0;
[layoutManager addTextContainer:textContainer];
NSRange glyphRange;
// Convert the range for glyphs.
[layoutManager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange];
return [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
@end
Swift 版本
import Foundation
import UIKit
/// https://***.com/a/4468880/3004003
@objc(MUIStrokedTextLabel)
public class StrokedTextLabel : UILabel
override public func drawText(in rect: CGRect)
guard let attributedText = attributedText?.mutableCopy() as? NSMutableAttributedString else
super.drawText(in: rect)
return
attributedText.enumerateAttributes(in: NSRange(location: 0, length: attributedText.length), options: [], using: attrs, range, stop in
guard let strokeWidth = attrs[NSAttributedString.Key.strokeWidth] as? CGFloat else
return
// 1. draw underlying stroked string
// use doubled stroke width to simulate outer border, because border is being stroked
// in both outer & inner directions with half width
attributedText.addAttributes([
NSAttributedString.Key.strokeWidth: strokeWidth * 2
], range: range)
self.attributedText = attributedText
// perform default drawing
super.drawText(in: rect)
// 2. draw unstroked string above
let style = NSMutableParagraphStyle()
style.alignment = textAlignment
let attributes = [
NSAttributedString.Key.strokeWidth: NSNumber(value: 0),
NSAttributedString.Key.foregroundColor: textColor ?? UIColor.black,
NSAttributedString.Key.font: font ?? UIFont.systemFont(ofSize: 17),
NSAttributedString.Key.paragraphStyle: style
]
attributedText.addAttributes(attributes, range: range)
// we use here custom bounding rect detection method instead of
// [attributedText boundingRectWithSize:...] because the latter gives incorrect result
// in this case
var textRect = boundingRect(with: attributedText, forCharacterRange: NSRange(location: 0, length: attributedText.length))
attributedText.boundingRect(
with: rect.size,
options: .usesLineFragmentOrigin,
context: nil)
// adjust vertical position because returned bounding rect has zero origin
textRect.origin.y = (rect.size.height - textRect.size.height) / 2
attributedText.draw(in: textRect)
)
/// https://***.com/a/20633388/3004003
private func boundingRect(
with attributedString: NSAttributedString?,
forCharacterRange range: NSRange
) -> CGRect
guard let attributedString = attributedString else
return .zero
let textStorage = NSTextStorage(attributedString: attributedString)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: bounds.size)
textContainer.lineFragmentPadding = 0
layoutManager.addTextContainer(textContainer)
var glyphRange = NSRange()
// Convert the range for glyphs.
layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)
return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
【讨论】:
以上是关于你如何抚摸 NSAttributedString 的 _outside_ ?的主要内容,如果未能解决你的问题,请参考以下文章