UILabel实现UITextFiled效果

2018-01-23  本文已影响0人  angry_zxy

实现方案

在iOS6中, UILabel, UITextField, UITextView都是基于string Drawing 和 WebKit构建的



iOS7, 苹果引入了Textkit, TextKit位于Core Text之上, TextViews(指UILabel, UITextField, UITextView等文本控件)之下, 相比于TextViews直接提供的api, TextKit有更灵活的接口, 能够实现文字排版和渲染. 由图可以看出, UITextFiled是基于Textkit实现的, 完全可以用Textkit将UILabel配置成一个UITextFiled.


Textkit

Text Kit

Text Kit中的对象
  • Text View是用来显示文本内容的控件,主要包括UILabel、UITextView和UITextField。
  • Text containers对应着NSTextContainer类。NSTextContainer定义了文本可以排版的区域。一般来说,都是矩形区域,当然,也可以根据需求,通过子类化NSTextContainer来创建别的一些形状,例如圆形、不规则的形状等。NSTextContainer不仅可以创建文本可以填充的区域,它还维护着一个数组——该数组定义了一个区域,排版的时候文字不会填充该区域,因此,我们可以在排版文字的时候,填充非文本元素(例如图片,如图4所示)。
  • Layout manager对应着NSLayoutManager类。该类负责对文字进行编辑排版处理——通过将存储在NSTextStorage中的数据转换为可以在视图控件中显示的文本内容,并把统一的字符编码映射到对应的字形(glyphs)上,然后将字形排版到NSTextContainer定义的区域中。
  • Text storage对应着NSTextStorage类。该类定义了Text Kit扩展文本处理系统中的基本存储机制。NSTextStorage继承自NSmutableAttributedString,主要用来存储文本的字符和相关属性。另外,当NSTextStorage中的字符或属性发生了改变,会通知NSLayoutManager,进而做到文本内容的显示更新。

这其实就是一个MVC模型, 通常情况下, NSTextStorage、NSLayoutManager和NSTextContainer是一一对应的, 当然在需要分页排版的时候, 也可以一对多.

Core Text

从实现上来看, Text Kit 和Core Text都可以实现将UILabel配置成UITextFiled那样的控件, Core Text是将文本直接渲染到图形上下文, 功能更加强大, 性能更好, 能够完全控制每个字型(CTRun对象)的渲染, 但是接口也相对复杂. Text Kit是对Core Text的一层封装, 能够解决一些简单文字的排版问题. 考虑到项目需求, TextKit足以完成.


Core Text中的对象

字型(Glyphs)

字符 + 字体 = 字型

功能实现

文本显示

配置NSTextStorage、NSLayoutManager和NSTextContainer的依赖关系:

 [_textStorage addLayoutManager:_layoutManager];
 [_layoutManager addTextContainer:_textContainer];

自定义UILabel 的drawTextInRect:方法

- (void)drawTextInRect:(CGRect)rect {
     NSRange range = NSMakeRange(0, self.textStorage.length);

    //绘制背景
    [_layoutManager drawBackgroundForGlyphRange:range atPoint:CGPointMake(kLeftPadding, kTopPadding)];
    //绘制文字
    if (_textStorage.length > 0) {
        [_layoutManager drawGlyphsForGlyphRange:range atPoint:CGPointMake(kLeftPadding, kTopPadding)];
    }
}

文本滚动

当文本超过输入框的宽度时, 继续编辑文字需要有文字滚动, 其实就是在绘制的时候, 修改绘制起点, 以达到滚动的效果

    [_layoutManager drawGlyphsForGlyphRange:range atPoint:CGPointMake(kLeftPadding, kTopPadding)];

文本编辑

insert

    [_textStorage insertAttributedString:attrString atIndex:_glyphIndex];
  1. 文本不超过输入框的 insert, 此时的 insert 只需修改NSTextStorage中的字符, 然后刷新光标位置即可:
image.png

2.文本超过输入框大小, 最后一个字符可见, 即左边有隐藏文字, 右边无隐藏文字, 此时仍然是修改NSTextStorage中的字符, 还需要计算滚动量(表示了左边隐藏的宽度):


image.png

3.文本超过输入框大小, 最后一个字符不可见, 即左边有隐藏文字, 右边也有隐藏文字, 此时需要判断: 在该位置insert 了新的文本之后, 新的光标是否在输入框范围之内. 如果还在输入框范围之内, 那么就是修改NSTextStorage中的字符即可.


image.png

否则, 还需要计算滚动量:


image.png

综上: 其实可以合并成两种情况:
1.插入点追加了文字之后, 小于label的MaxX, 则直接在后面追加文字, 更新光标位置即可
2.插入点追加了文字之后, 大于Label的MaxX, 则右边界固定, 光标移动到最右端, 文字向左滚动

Delete

[_textStorage deleteCharactersInRange:range];

1.文本不超过输入框的情况下的删除, 直接修改 NSTextStorage 中的字符即可:

image.png

2.文本超过输入框 delete, 最后一个字符可见, 即左边有隐藏文字, 右边无隐藏文字. 这种情况需要判断删除后的文本有没有超过输入框, 如果仍然超过了, 删除当前字符, 保持最后一个字符位置不变(文字右边固定), 计算新的滚动量, 表现为文字向右滚动. 如果没有超过, 删除当前字符, 将文字全部显示出来, 表现为向右滚动:


image.png

3.文本超过输入框 delete, 最后一个字符不可见, 即左边有隐藏文字, 右边也有隐藏文字. 这种情况需要判断删除了当前文字之后, 光标位置有没有超出文本框的范围(到了左边界以左), 如果仍在输入框范围, 则只需要修改 NSTextStorage 的字符即可:


image.png

否则, 需要计算滚动量, 使得滚动后的光标正好在输入框的起始位置(数值为0)


image.png

综上: 根据最后一个字符是否可见, 分为左端固定与右端固定的情况

Touch

touch 文本后, 需要计算出 touch 的字符, 以及该字符的 frame, 然后将光标更新到这个位置, 根据touch坐标获取被 touch 的字符 index:

NSInterge index=  [_layoutManager glyphIndexForPoint:point inTextContainer:_textContainer];

根据字符获取字符的 frame:

CGRect bounding =[_layoutManager boundingRectForGlyphRange:range inTextContainer:_textContainer];

粘贴菜单

粘贴操作就是读取系统粘贴板的字符串, insert 操作.


点击前
点击后

手势

主要参考了UITextField 的行为:

  1. 当光标在某一位置时, 再次点击这个位置, 弹出菜单
  2. 长按光标, 可左右移动光标, 停止移动时, 弹出菜单

自定义属性

遇到的问题

上一篇 下一篇

猜你喜欢

热点阅读