NSString : 字符合集
每个
字符由唯一的Unicodee值表示,字母a用Unicode字符“小写拉丁字符A”表示,其值为97(U+0061)。中文字符“我”则用“汉子字符我”表示,其值为25105(U+6211)。任何时候,字母a或字符“我”出现在字符串中,其数值都是一样的。a的不同写法,就是a不同的
字形。字体主要是映射到字符的字形的合集。一种字体可以由多种字形来表达给定的字符。比如阿拉伯文中,同一个字符在单词中的不同位置会表现出不同的字形。字符数和字形数不一定相等,比如音调符号,这取决于字体是否提供特殊的字母,以及字符串中的字母编码。
-
同一个字体不同的大小,属于字体的扩展
-
加粗和斜体相当于选择字体
-
添加下划线,字形不变,字体也不变,相当于系统画了一条线。添加删除线这样的装饰会修改字体。
-
字体还可以微调,但可能会很复杂,包含条件逻辑、循环和变体,这些需要字体渲染引擎提供的虚拟机处理。
Text Kit 的组件
-
NSTextStorage:NSMutableAttributedString 管理文本
-
NSTextContainer:管理文本填充区域
-
NSLayoutManager:组件在文本容器上应用布局
每个NSTextStorage可以有多个NSLayoutManager,每个NSLayoutManager可以有多个NSTextContainer。
如果TextView有多个文本容器,就会变成静态的,无法响应用户的交互
// 创建文本容器
CGSize size = self.bounds.size;
size.width /= 2;
self.textContainer = [[NSTextContainer alloc] initWithSize:size];
// 创建一个布局管理器
self.layoutManager = [NSLayoutManager new];
self.layoutManager.delegate = self;
[self.layoutManager addTextContainer:self.textContainer];
// 为同样的文本提供两种布局
[self.textView.textStorage addLayoutManager:self.layoutManager];
- (void)layoutManagerDidInvalidateLayout:(NSLayoutManager *)sender {
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
NSLayoutManager *lm = self.layoutManager;
NSRange range = [lm glyphRangeForTextContainer:self.textContainer];
CGPoint point = CGPointZero;
[lm drawBackgroundForGlyphRange:range atPoint:point];
[lm drawGlyphsForGlyphRange:range atPoint:point];
}
排除路径:挖空指定区域,如果区域设置不合理导致放不下文本,NSLayoutManager可能会悄无生气的失败
UIBezierPath *exclusionPath = [UIBezierPath bezierPathWithRoundedRect:rect
cornerRadius:width/10];
self.textView.textContainer.exclusionPaths = @[exclusionPath];
中英文显示换行问题:
label.lineBreakMode = NSLineBreakByCharWrapping;
自定义Attributes
效果
// 自定义一个存储类 PTLScribbleTextStorage : NSTextStorage,完成赋值自定义富文本风格的操作
// 两个关键属性,记录文本内容的backingStore,是否需要刷新的dynamicTextNeedsUpdate
// NSTextStorage 可以理解为 NSMutableAttributedString 的抽象类,需要实现以下4个方法
/*
- (NSString *)string {返回backingStore}
- (NSDictionary *)attributesAtIndex:(NSUInteger)location
effectiveRange:(NSRangePointer)range {同步修改backingStore}
- (void)replaceCharactersInRange:(NSRange)range
withString:(NSString *)str {
1.[self beginEditing];
2.同步修改 backingStore
3.必须调用[self edited:NSTextStorageEditedCharacters|NSTextStorageEditedAttributes
range:range
changeInLength:str.length - range.length];
4.dynamicTextNeedsUpdate 标记为需要刷新
5.[self endEditing];
}
- (void)setAttributes:(NSDictionary *)attrs
range:(NSRange)range {
步骤同上,但不需要标记刷新
}
-(void)processEditing {
1.判断dynamicTextNeedsUpdate是否需要刷新,
2.[self editedRange]获取range,
3.[self setAttributes:attributesForToken range:substringRange];}
*/
// 自定义一个布局管理类 PTLScribbleLayoutManager : NSLayoutManager,完成自定义风格的富文本实现
// drawGlyphsForGlyphRange:背景修改
- (void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow
atPoint:(CGPoint)origin {
// Determine the character range so you can check the attributes
NSRange characterRange = [self characterRangeForGlyphRange:glyphsToShow
actualGlyphRange:NULL];
// Enumerate each time PTLRedactStyleAttributeName changes
[self.textStorage enumerateAttribute:PTLRedactStyleAttributeName
inRange:characterRange
options:0
usingBlock:^(id value,
NSRange attributeCharacterRange,
BOOL *stop) {
[self redactCharacterRange:attributeCharacterRange
ifTrue:value
atPoint:origin];
}];
}
- (void)redactCharacterRange:(NSRange)characterRange
ifTrue:(NSNumber *)value
atPoint:(CGPoint)origin {
// Switch back to glyph ranges, since we're drawing
NSRange glyphRange = [self glyphRangeForCharacterRange:characterRange
actualCharacterRange:NULL];
if ([value boolValue]) {
// Prepare the context. origin is in view coordinates.
// The methods below will return text container coordinates,
// so apply a translation
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, origin.x, origin.y);
[[UIColor blackColor] setStroke];
// Enumerate contiguous rectangles that enclose the redacted glyphs
NSTextContainer *
container = [self textContainerForGlyphAtIndex:glyphRange.location
effectiveRange:NULL];
[self enumerateEnclosingRectsForGlyphRange:glyphRange
withinSelectedGlyphRange:NSMakeRange(NSNotFound, 0)
inTextContainer:container
usingBlock:^(CGRect rect, BOOL *stop){
[self drawRedactionInRect:rect];
}];
CGContextRestoreGState(context);
}
else {
// Wasn’t redacted. Use default behavior.
[super drawGlyphsForGlyphRange:glyphRange atPoint:origin];
}
}
- (void)drawRedactionInRect:(CGRect)rect {
// Draw a box with an X through it.
// You could draw anything here.
UIBezierPath *path = [UIBezierPath bezierPathWithRect:rect];
CGFloat minX = CGRectGetMinX(rect);
CGFloat minY = CGRectGetMinY(rect);
CGFloat maxX = CGRectGetMaxX(rect);
CGFloat maxY = CGRectGetMaxY(rect);
[path moveToPoint: CGPointMake(minX, minY)];
[path addLineToPoint:CGPointMake(maxX, maxY)];
[path moveToPoint: CGPointMake(maxX, minY)];
[path addLineToPoint:CGPointMake(minX, maxY)];
[path stroke];
[@"season" drawInRect:rect withFont:[UIFont systemFontOfSize:12] lineBreakMode:NSLineBreakByCharWrapping alignment:NSTextAlignmentCenter];
}
// drawBackgroundForGlyphRange:修改边框
在贝塞尔曲线上绘制文本
// 获取贝塞尔曲线上的点
static double Bezier(double t, double P0, double P1, double P2,
double P3) {
return
(1-t)*(1-t)*(1-t) * P0
+ 3 * (1-t)*(1-t) * t * P1
+ 3 * (1-t) * t*t * P2
+ t*t*t * P3;
}
// 获取贝塞尔曲线上的斜率
static double BezierPrime(double t, double P0, double P1,
double P2, double P3) {
return
- 3 * (1-t)*(1-t) * P0
+ (3 * (1-t)*(1-t) * P1) - (6 * t * (1-t) * P1)
- (3 * t*t * P2) + (6 * t * (1-t) * P2)
+ 3 * t*t * P3;
}
//
- (void)drawText {
if ([self.attributedString length] == 0) { return; }
NSLayoutManager *layoutManager = self.layoutManager;
CGContextRef context = UIGraphicsGetCurrentContext();
NSRange glyphRange;
CGRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:0
effectiveRange:&glyphRange];
double offset = 0;
CGPoint lastGlyphPoint = self.P0;
CGFloat lastX = 0;
for (NSUInteger glyphIndex = glyphRange.location;
glyphIndex < NSMaxRange(glyphRange);
++glyphIndex) {
CGContextSaveGState(context);
CGPoint location = [layoutManager locationForGlyphAtIndex:glyphIndex];
CGFloat distance = location.x - lastX; // Assume single line
offset = [self offsetAtDistance:distance
fromPoint:lastGlyphPoint
andOffset:offset];
CGPoint glyphPoint = [self pointForOffset:offset];
double angle = [self angleForOffset:offset];
lastGlyphPoint = glyphPoint;
lastX = location.x;
CGContextTranslateCTM(context, glyphPoint.x, glyphPoint.y);
CGContextRotateCTM(context, angle);
[layoutManager drawGlyphsForGlyphRange:NSMakeRange(glyphIndex, 1)
atPoint:CGPointMake(-(lineRect.origin.x + location.x),
-(lineRect.origin.y + location.y))];
CGContextRestoreGState(context);
}
}












网友评论