前言

最近换了一个作业,有点忙,记载笔记的时间越来越少了。关于RxSwift专栏的文章,笔者后续再抽时间记载。今天首要分享一个笔者碰到的一个需求,在完成的过程中决议封装一下,期望能够帮到有需求的小伙伴。\

需求:完成红色框框内的效果

Swift-UILabel实现用户协议图文混排

要求:

  • 按钮支撑点击选中和反选
  • 协议支撑点击
  • 支撑换行
  • 截断时也要支撑点击
  • 图文中心保持一致

剖析:

前3点不难,用按钮label组合一下即可,后边两点就要求有必要运用富文本了,因为截断的状况不固定,比如:

Swift-UILabel实现用户协议图文混排

或许

Swift-UILabel实现用户协议图文混排

这两种都不好处理,用按钮无法完成截断的处理。

完成

处理图片

图片的比较容易处理一些,将图片转成富文本,和后边内容拼接即可,这儿需求留意的是图片的中心点要和后边的案牍对齐,对齐能够采用基线偏移来处理(偏移的是后边的案牍)

            let baselineOffset = (largeFont.lineHeight - normalFont.lineHeight)/2.0 + (largeFont.descender - normalFont.descender)
            muAttributedString.addAttributes([NSAttributedString.Key.font: normalFont,
                                              NSAttributedString.Key.baselineOffset: baselineOffset],
                                             range: NSRange(location: normalImgAttrString.length, length: muAttributedString.length - normalImgAttrString.length))

获取案牍的区域

通常咱们想的是直接运用这个办法来获取

layoutManager.boundingRect(forGlyphRange: , in: )

这个办法关于第一种状况是能够处理的,可是后边截断的状况无法处理。

翻看layoutManager的API发现有一个办法能够处理截断的状况

open func enumerateEnclosingRects(forGlyphRange glyphRange: NSRange, withinSelectedGlyphRange selectedRange: NSRange, in textContainer: NSTextContainer, using block: @escaping (CGRect, UnsafeMutablePointer<ObjCBool>) -> Void)

枚举textContainer中glyphRange的封闭矩形。如果在第二个参数中给出了所选规模,则回来的矩形将正确用于绘制所选内容。

从官方的注解能够发现,将给定规模的文字的一切区域枚举一遍。从而经过第一个矩形和最后一个矩形即可覆盖一切的文字规模,如下图:

Swift-UILabel实现用户协议图文混排

完成代码:

func rectFor(string str : String, fromIndex: Int = 0) -> (CGRect, CGRect)?
    {
        // Find the range of the string
        guard self.text != nil else { return nil }
        let subStringToSearch : NSString = (self.text! as NSString).substring(from: fromIndex) as NSString
        var stringRange = subStringToSearch.range(of: str)
        if (stringRange.location != NSNotFound)
        {
            guard self.attributedText != nil else { return nil }
            // Add the starting point to the sub string
            stringRange.location += fromIndex
            let storage = NSTextStorage(attributedString: self.attributedText!)
            let layoutManager = NSLayoutManager()
            storage.addLayoutManager(layoutManager)
            let textContainer = NSTextContainer(size: self.frame.size)
            textContainer.lineFragmentPadding = 0
            textContainer.lineBreakMode = .byWordWrapping
            layoutManager.addTextContainer(textContainer)
            var glyphRange = NSRange()
            layoutManager.characterRange(forGlyphRange: stringRange, actualGlyphRange: &glyphRange)
            var firstWordRect = true
            var rect1 = CGRectZero
            var rect2 = CGRectZero
            layoutManager.enumerateEnclosingRects(forGlyphRange: glyphRange, withinSelectedGlyphRange: NSRange(location: NSNotFound, length: 1), in: textContainer) { wordRect, isStop in
                if firstWordRect {
                    rect1 = wordRect
                    firstWordRect = false
                }
                rect2 = wordRect
            }
            return (rect1, rect2)
        }
        return nil
    }

点击处理

@objc
    private func click(_ gesture: UITapGestureRecognizer) {
        guard let dict = callBackMap else {
            return
        }
        let point = gesture.location(in: self)
        for key in dict.keys {
            let (rect1, rect2) = rectFor(string: key)!
            let imgString = selected ? selectedImgAttrString : normalImgAttrString
            if containsPoint(minRect: rect1, maxRect: key == imgString.string ? CGRect(x: 0, y: 0, width: largeFont.pointSize, height: largeFont.pointSize):rect2, point: point) {
                if key == imgString.string {
                    selected = !selected
                }
                if let callBack = dict[key] {
                    callBack()
                }
            }
        }
    }

总结

笔者封装了一个UILabel的扩展,可方便运用。完好的demo已放在github,有爱好的小伙伴能够去下载用一下看看

留意事项:

图片和largefont的巨细最好保持一致,否则基线偏移的时候要特殊处理,这种状况在代码里做了注解

Swift-UILabel实现用户协议图文混排


更新

问题:

如果有多个重复的协议,第一个能够点击,后边的重复协议不支撑点击

处理:

1.文本颜色:添加协议时,遍历一切文本,得到该字串的一切range,从而修改文本颜色。
2.点击处理:同样遍历字串的一切range,直到点击的位置落在range区域内,或许查询无果不做响应

阶段性总结

笔者在遇到问题时,会尽量的处理,也欢迎小伙伴把碰到的问题提出来,一起学习进步。
在此感谢小伙伴season_zhu的提示,demo链接已更新。
支撑pod运用:
pod 'UILabelImageText'