在掌阅APP阅读小说时,会出现长按文字划线的功能,自己琢磨了一下,总结一下。
0.效果图
Dec-20-2018 14-34-01.gif1.添加长按手势
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
if longPress == nil {
longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(longGesture:)))
addGestureRecognizer(longPress)
}
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAction))
addGestureRecognizer(tapGesture)
}
2.在长按手势的方法里面获取到选中的range以及对应的rect,并刷新界面UI
- 首先根据点击位置,获取到点击位置及其相邻位置的两个点的range;
- 将获取到的range转换成对应的frame坐标;
- 手势滑动时,更新range的位置和对应的frame;
- 保存手势最初位置的rang,以便于手势移动的更新range的位置;
- 根据frame绘制界面;
@objc func longPressAction(longGesture: UILongPressGestureRecognizer) {
var originPoint = CGPoint.zero
switch longPress.state {
case .began:
print("longPress-Began")
originPoint = longGesture.location(in: self)
originRange = getTouchLocationRange(point: originPoint)
rects = getRangeRects(range: selectedRange, ctframe: ctFrame)
selectedRange = originRange
setNeedsDisplay()
case .changed:
let finalRange = getTouchLocationRange(point: longGesture.location(in: self))
if finalRange.location == 0 || finalRange.location == NSNotFound {
return
}
var range = NSRange(location: 0, length: 0)
range.location = min(finalRange.location, originRange.location)
if finalRange.location > originRange.location {
range.length = finalRange.location - originRange.location + finalRange.length
} else {
range.length = originRange.location - finalRange.location + originRange.length
}
selectedRange = range
rects = getRangeRects(range: selectedRange, ctframe: ctFrame)
setNeedsDisplay()
case .ended:
print("longPress-Ended")
case .cancelled:
print("longPress-Cancelled")
default:
break
}
}
3.其中根据点击位置获取range和根据range获取rect的两个方法如下
//MARK: - 获取点击位置的两个字符的range
private func getTouchLocationRange(point: CGPoint) -> NSRange {
var resultRange = NSRange(location: 0, length: 0)
guard let ctFrame = ctFrame else { return resultRange }
var lines = CTFrameGetLines(ctFrame) as Array
var origins = [CGPoint](repeating: CGPoint.zero, count: lines.count)
CTFrameGetLineOrigins(ctFrame, CFRange(location: 0, length: 0), &origins)
for i in 0..<lines.count {
let line = lines[i] as! CTLine
let origin = origins[I]
var ascent: CGFloat = 0
var descent: CGFloat = 0
CTLineGetTypographicBounds(line, &ascent, &descent, nil)
let lineRect = CGRect(x: origin.x, y: self.frame.height - origin.y - (ascent + descent), width: CTLineGetOffsetForStringIndex(line, 100000, nil), height: ascent + descent)
if lineRect.contains(point) {
let lineRange = CTLineGetStringRange(line)
for j in 0..<lineRange.length {
let index = lineRange.location + j
var offsetX = CTLineGetOffsetForStringIndex(line, index, nil)
var offsetX2 = CTLineGetOffsetForStringIndex(line, index + 1, nil)
offsetX += origin.x
offsetX2 += origin.x
let runs = CTLineGetGlyphRuns(line) as Array
for k in 0..<runs.count {
let run = runs[k] as! CTRun
let runRange = CTRunGetStringRange(run)
if runRange.location <= index && index <= (runRange.location + runRange.length - 1) {
// 说明在当前的run中
var ascent: CGFloat = 0
var descent: CGFloat = 0
CTRunGetTypographicBounds(run, CFRange(location: 0, length: 0), &ascent, &descent, nil)
let frame = CGRect(x: offsetX, y: self.frame.height - origin.y - (ascent + descent), width: (offsetX2 - offsetX) * 2, height: ascent + descent)
if frame.contains(point) {
// 每次获取两个字符的长度
resultRange = NSRange(location: index, length: 2)
}
}
}
}
}
}
return resultRange
}
//MARK: - 获取range所占用的rects
private func getRangeRects(range: NSRange, ctframe: CTFrame?) -> [CGRect] {
var rects = [CGRect]()
guard let ctframe = ctframe else { return rects }
guard range.location != NSNotFound else { return rects }
var lines = CTFrameGetLines(ctframe) as Array
var origins = [CGPoint](repeating: CGPoint.zero, count: lines.count)
CTFrameGetLineOrigins(ctframe, CFRange(location: 0, length: 0), &origins)
for i in 0..<lines.count {
let line = lines[i] as! CTLine
let origin = origins[I]
let lineCFRange = CTLineGetStringRange(line)
if lineCFRange.location != NSNotFound {
let lineRange = NSRange(location: lineCFRange.location, length: lineCFRange.length)
if lineRange.location + lineRange.length > range.location && lineRange.location < (range.location + range.length) {
var ascent: CGFloat = 0
var descent: CGFloat = 0
var startX: CGFloat = 0
var contentRange = NSRange(location: range.location, length: 0)
let end = min(lineRange.location + lineRange.length, range.location + range.length)
contentRange.length = end - contentRange.location
CTLineGetTypographicBounds(line, &ascent, &descent, nil)
let y = origin.y - descent
startX = CTLineGetOffsetForStringIndex(line, contentRange.location, nil)
let endX = CTLineGetOffsetForStringIndex(line, contentRange.location + contentRange.length, nil)
let rect = CGRect(x: origin.x + startX, y: y, width: endX - startX, height: ascent + descent)
rects.append(rect)
}
}
}
return rects
}
网友评论