在掌閱APP閱讀小說(shuō)時(shí)炬守,會(huì)出現(xiàn)長(zhǎng)按文字劃線的功能眠菇,自己琢磨了一下毁枯,總結(jié)一下慈缔。
0.效果圖
1.添加長(zhǎng)按手勢(shì)
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.在長(zhǎng)按手勢(shì)的方法里面獲取到選中的range以及對(duì)應(yīng)的rect,并刷新界面UI
- 首先根據(jù)點(diǎn)擊位置种玛,獲取到點(diǎn)擊位置及其相鄰位置的兩個(gè)點(diǎn)的range藐鹤;
- 將獲取到的range轉(zhuǎn)換成對(duì)應(yīng)的frame坐標(biāo);
- 手勢(shì)滑動(dòng)時(shí)蒂誉,更新range的位置和對(duì)應(yīng)的frame教藻;
- 保存手勢(shì)最初位置的rang,以便于手勢(shì)移動(dòng)的更新range的位置右锨;
- 根據(jù)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.其中根據(jù)點(diǎn)擊位置獲取range和根據(jù)range獲取rect的兩個(gè)方法如下
//MARK: - 獲取點(diǎn)擊位置的兩個(gè)字符的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) {
// 說(shuō)明在當(dāng)前的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) {
// 每次獲取兩個(gè)字符的長(zhǎng)度
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
}