用TextKit搞点事情

先搞清楚族谱

TextKit WWDC2013 Session 210
TextKit WWDC2013 Session 210

再搞清楚架构

重要的几个类
重要的几个类
  1. TextKit框架主要的几个View,主要包括UILabel, UITextView, UITextField;
  2. TextKit是NS一支的,所以不光只在iOS中使用;
  3. Text container对应NSTextContainer,它定义了文本排版区域,很明显,文字是在这个区域中被管理的;
  4. Text storage对应NSTextStorage,继承自NSMutableAttributedString,用于存储文本字形和相关属性;
  5. Layout manager对应NSLayoutManager,负责对文字进行编辑排版处理,在上面第4条中的类保存的数据可以通过本条中的类转换显示到视图中,其中这个单词字形(glyphs)需注意;

这张图很直观

UITextView composition
UITextView composition

我们来做点事情

首先我们需要一个自定义类型的UITextView,用它来对我们的文字内容进行自定义修改;

class ProblemView: UITextView {
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
image_1ce5b0af015ldu4f476hvv1bhn2g.png-114.8kB
image_1ce5b0af015ldu4f476hvv1bhn2g.png-114.8kB

UITextView的初始化方法中有一个textContainer参数,很明显是我们上面提到的,且如图中三种颜色的框框所示,是我们上面提到的管理类,且全部为只读属性;
并且就上面的三种类之前的管理,如果我们要自己管理的话,应该也是一个树型的结构;

接下来我们要配合NSAttributedString添加显示强大的富文本;

NSAttributedString

    private(set) var attributedProblem: NSAttributedString = NSAttributedString.init()
    {
        willSet {
            self.text = newValue.string
            self.font = font    //字体是会影响宽度的,所以该项如果是在外部设置的话,应该提前修改
            
            self.textContainer.lineBreakMode = .byWordWrapping
            self.textStorage.replaceCharacters(in: NSRange(location: 0, length: newValue.length), with: newValue)
            
            let size = self.sizeThatFits(CGSize(width: self.bounds.width, height: CGFloat(MAXFLOAT)))
            self.textContainer.size = size
        }
        
        didSet {
            
        }
    }

我们需要一个具有计算属性的变量,当我们在内部修改该变量时做一些操作;上面的代码解释几点:

  • 我们需要在替换textview内容的时候重新计算内部空间,所以必须要在计算前将内容复制下来;上面代码第4行;
  • 第7行中我们给出了一个示范,在这里可以修改与NSTextContainer相关的属性;
  • 第8行我们开始进行文本替换,替换的内容为设置的新的富文本内容,范围则是该文本的NSRange
  • 接下来我们需要重新计算内部大小,这个内容大小就类似于ScrollViewcontentSize,计算的过程中我们要指定它的宽度,令高为最大值,结果会根据宽度计算出相应的文本需要高度;最后将size设置为textContainer的size,这里一定要注意;
  • 这里如果是self.sizeToFit()方法,则textView的size会根据内容变大,而不是内部空间变大,这是与上面一条的区别;

那传一段文本进来吧

    //设置题目文字属性
    func setupSubject(text: String, font: UIFont) {
        
        let changeString = NSMutableAttributedString.init(string: text)
        let desTextRange = NSRange(location: 0, length: text.count)
        changeString.addAttribute(NSAttributedStringKey.font, value: font, range: desTextRange)
        changeString.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.blue, range: desTextRange)
        
        self.font = font

        self.attributedProblem = changeString
    }

如果你以前用过富文本,那这个方法看起来没什么,无非就是做了两件事:

  1. 生成一个富文本;(有关于富文本的内容,可以自行谷歌一车)
  2. 将文本设置到我们的textview里;

我想改变一段文字

如果我们想修改某一截文字的属性,那么我们只需要将保存的富文本取出,然后截取位置,替换掉原先的富文本,然后重新设置到属性中去,如下:

    //当前要发生替换的区域
    private(set) var optionsRange: NSRange?
    //替换选项文字
    func replaceOption(options: String, replaceOptions: String, font: UIFont) {
        
        //给选项添加属性
        let changeString = NSMutableAttributedString.init(attributedString: self.attributedProblem)
        let optionString = self.textOptions(text: replaceOptions, font: font)
        
        //选项最终位置
        if let optionsRange = changeString.string.range(of: options) {
            let range = NSRange(optionsRange, in: options)
            changeString.replaceCharacters(in: range, with: optionString)
            self.optionsRange = range
        } else {
            self.optionsRange = NSRange(location: 0, length: 0)
        }
        
        self.attributedProblem = changeString
    }

当然我们要替换的文本当然也可以是富文本;

//上面的第8行中的方法
    //给选项添加属性
    func textOptions(text: String, font: UIFont) -> NSAttributedString {
        //选项左右添加空格
        let addSpace = "  " + text + "  "
        
        //选项文字属性
        let optionString = NSMutableAttributedString(string: addSpace)
        let optionRange = NSRange(location: 2, length: text.count)
        
        optionString.addAttribute(NSAttributedStringKey.font, value: font.withSize(20), range: optionRange)
        optionString.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.red, range: optionRange)
        optionString.addAttribute(NSAttributedStringKey.strokeColor, value: UIColor.white, range: optionRange)
        optionString.addAttribute(NSAttributedStringKey.strokeWidth, value: -3, range: optionRange)
        optionString.addAttribute(NSAttributedStringKey.underlineStyle, value: 1, range: optionRange)
        
        return optionString
    }

这里需要注意的就是swift-Range与oc-NSRange的转换,关于swift的Range个人感觉比以前难用了,但是更加通用了吧,说下转换:

        if let optionsRange = changeString.string.range(of: options) {
            let range = NSRange(optionsRange, in: options)
            changeString.replaceCharacters(in: range, with: optionString)
            self.optionsRange = range
        } else {
            self.optionsRange = NSRange(location: 0, length: 0)
        }
        //这里我把发生替换的部分用变量保存了下来

也许还可以替换图片

    //添加富文本图片
    // originString 原始字符串
    // replaceRange 要替换的文字范围
    // size 替换图片大小
    func replacePictureForText(originString: NSAttributedString, replaceRange: NSRange, size: CGSize) -> NSAttributedString {
        let attachment = NSTextAttachment()
        attachment.image = UIImage.init(named: "study_select_words")
        attachment.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        
        //最终结果
        let originMutiString = NSMutableAttributedString.init(attributedString: originString)
        
        //生成图片
        let attachString = NSAttributedString(attachment: attachment)
        let mutiAttachString = NSMutableAttributedString(attributedString: attachString)
        
        //替换成图片
        originMutiString.replaceCharacters(in: replaceRange, with: mutiAttachString)
        return originMutiString
    }

无他,仍然只是需要找到一个替换区域,替换成想要的可支持的富文本属性;

别人不会告诉你的事

最后想告诉你我们可以通过NSRange来得到所在区域的坐标,这是个无意中谷歌到的东西,如下:

let rect = self.problem.textContainer.layoutManager?.boundingRect(forGlyphRange: self.problem.optionsRange!,in: self.problem.textContainer)

self.problem.scrollRangeToVisible(self.problem.optionsRange!)

找到目标区域,并定位到该区域;

以上。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容