用TextKit搞点事情

先搞清楚族谱

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,用它来对我们的文字内容进行自定义修改;

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

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!)

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

以上。。。

猜你喜欢

转载自blog.csdn.net/jianin45/article/details/80416619