前言
最近换了一个作业,有点忙,记载笔记的时间越来越少了。关于RxSwift专栏的文章,笔者后续再抽时间记载。今天首要分享一个笔者碰到的一个需求,在完成的过程中决议封装一下,期望能够帮到有需求的小伙伴。\
需求:完成红色框框内的效果
要求:
- 按钮支撑点击选中和反选
- 协议支撑点击
- 支撑换行
- 截断时也要支撑点击
- 图文中心保持一致
剖析:
前3点不难,用按钮label组合一下即可,后边两点就要求有必要运用富文本了,因为截断的状况不固定,比如:
或许
这两种都不好处理,用按钮无法完成截断的处理。
完成
处理图片
图片的比较容易处理一些,将图片转成富文本,和后边内容拼接即可,这儿需求留意的是图片的中心点要和后边的案牍对齐,对齐能够采用基线偏移来处理(偏移的是后边的案牍)
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的封闭矩形。如果在第二个参数中给出了所选规模,则回来的矩形将正确用于绘制所选内容。
从官方的注解能够发现,将给定规模的文字的一切区域枚举一遍。从而经过第一个矩形和最后一个矩形即可覆盖一切的文字规模,如下图:
完成代码:
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的巨细最好保持一致,否则基线偏移的时候要特殊处理,这种状况在代码里做了注解
更新
问题:
如果有多个重复的协议,第一个能够点击,后边的重复协议不支撑点击
处理:
1.文本颜色:添加协议时,遍历一切文本,得到该字串的一切range,从而修改文本颜色。
2.点击处理:同样遍历字串的一切range,直到点击的位置落在range区域内,或许查询无果不做响应
阶段性总结
笔者在遇到问题时,会尽量的处理,也欢迎小伙伴把碰到的问题提出来,一起学习进步。
在此感谢小伙伴season_zhu的提示,demo链接已更新。
支撑pod运用:
pod 'UILabelImageText'