美文网首页
14.精妙的文本布局

14.精妙的文本布局

作者: LucXion | 来源:发表于2021-12-14 18:27 被阅读0次

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);
  }
}

相关文章

  • 14.精妙的文本布局

    NSString : 字符合集每个字符由唯一的Unicodee值表示,字母a用Unicode字符“小写拉丁字符A”...

  • CornerViewLayout & CornerVie

    右下角角标文本,斜体文本,标签文本,使用CornerViewLayout作为父布局,则在该父布局右下角绘制三角形角...

  • 第四周笔记

    用户开发中的布局开发 布局和控件(完成UI设计) 涉及布局layout和控件View 线性布局和相对布局 显示文本...

  • CSS2-3的盒模型

    Css2定义了四种布局模式 1) 块布局:呈现文档的布局模式 2) 行内布局:呈现文本的布局模式 3) 表格布局:...

  • HTML中表单的格式和样式布局问题,就拿用样式和Fieldset

    使用表格来定义表单布局时,可在表单中实现更为统一的空间布局,无论文本框之前的标签文本长短如何,所有的文本框都是在垂...

  • Flutter笔记(三) - Flutter的基础Widget

    1. 文本Widget 1.1. 普通文本展示 在Flutter中,文本的控制参数分为两类: 控制文本布局的参数:...

  • CoreText编程指南(布局操作)

    Demo地址 通用文本布局操作 这一章描述一些通用的文本布局操作,显示怎么用CoreText来实现它们。下面是本章...

  • PHP从入门到精通,027第三章CSS之DIV+CSS标准化布局

    四、DIV+CSS标准化布局 (五)、浮动布局 浮动元素和文本的关系 说明:文本是不会钻入到浮动元素下面的 清除浮...

  • 学习时刻

    教师对文本的智慧解读和精妙设计,是“共生共长”的前提。一千个读者眼里有一千个哈姆雷特,教师解读文本的能力和智慧...

  • 3. Flutter - 基础组件 之 Text

    1. Text 属性介绍 Text继承自StatelessWidget,Text 主要通过设置 文本布局 及 文本...

网友评论

      本文标题:14.精妙的文本布局

      本文链接:https://www.haomeiwen.com/subject/rcppfrtx.html