1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 套路继续 .txt 小说阅读器功能开发

套路继续 .txt 小说阅读器功能开发

时间:2023-05-12 08:23:04

相关推荐

套路继续  .txt 小说阅读器功能开发

1, 解决一个 bug

正文结尾 (最后一行最后一个字)跟右边界, 有多余的空白间隔

Core Text 的渲染流程,就是富文本绘制

从流程上看,

感觉这一页的文字分配少了,给他加点字,就满了

// 拿到一个章节的富文本,计算出每一页的富文本,从哪里开始,哪里结束// 得到一个范围的数组,就知道了每一页的文字class func pagingRanges(attrString:NSAttributedString, rect:CGRect) ->[NSRange] {var rangeArray = [NSRange]()let framesetter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString)// 屏幕显示区域let path = CGPath(rect: rect, transform: nil)var range = CFRangeMake(0, 0)var rangeOffset = 0repeat{let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(rangeOffset, 0), path, nil)range = CTFrameGetVisibleStringRange(frame)rangeArray.append(NSMakeRange(rangeOffset, range.length))rangeOffset += range.length}while(rangeOffset < attrString.length)return rangeArray}

这样做,效果很不好

解决:

修改富文本属性, 怎么换行的

// 行间距paragraphStyle.lineSpacing = lineSpacing// 加了这么一句paragraphStyle.lineBreakMode = .byCharWrapping// 段间距paragraphStyle.paragraphSpacing = paragraphSpacing// 对齐paragraphStyle.alignment = .justified

2, 复制一整行

基于 dengzemiao/DZMeBookRead

长按,可复制一整行

长按的时候,要找出这一行,

给这一行涂上颜色,

出来一个可复制的菜单

/// 长按事件@objc private func longAction(long:UILongPressGestureRecognizer) {// 触摸位置let point = long.location(in: self)// 触摸位置switch long.state {case .began:// 触摸开始 触摸中// 发送通知, 处理其他 UI// ...//case .changed:default:// 触摸结束// 获得选中区域selectRange = CoreText.GetTouchLineRange(point: point, frameRef: frameRef)// 获得选中选中范围rects = CoreText.GetRangeRects(range: selectRange!, frameRef: frameRef, content: pagingModel.content?.string)// 显示光标cursor(isShow: true)// 显示菜单self.showMenu(isShow: true)// 重绘setNeedsDisplay()// 发送通知, 处理其他 UI// ...}}

找出这一行

长按手势,可以拿到一个点,

当前阅读界面,有一帧的文字CTFrame

/// 获得触摸位置那一行文字的Range////// - Parameters:/// - point: 触摸位置/// - frameRef: CTFrame/// - Returns: CTLineclass func GetTouchLineRange(point:CGPoint, frameRef:CTFrame?) ->NSRange {var range:NSRange = NSMakeRange(NSNotFound, 0)let line = GetTouchLine(point: point, frameRef: frameRef)if line != nil {let lineRange = CTLineGetStringRange(line!)range = NSMakeRange(lineRange.location == kCFNotFound ? NSNotFound : lineRange.location, lineRange.length)}return range}/// 获得触摸位置在哪一行////// - Parameters:/// - point: 触摸位置/// - frameRef: CTFrame/// - Returns: CTLineclass func GetTouchLine(point:CGPoint, frameRef:CTFrame?) ->CTLine? {var line:CTLine? = nilif frameRef == nil { return line }let frameRef:CTFrame = frameRef!let path:CGPath = CTFrameGetPath(frameRef)let bounds:CGRect = path.boundingBox// 获取全部行let lines:[CTLine] = CTFrameGetLines(frameRef) as! [CTLine]if lines.isEmpty { return line }let lineCount = lines.countlet origins = malloc(lineCount * MemoryLayout<CGPoint>.size).assumingMemoryBound(to: CGPoint.self)CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), origins)// 一行一行的往下翻for i in 0..<lineCount {let origin:CGPoint = origins[i]let tempLine:CTLine = lines[i]var lineAscent:CGFloat = 0var lineDescent:CGFloat = 0var lineLeading:CGFloat = 0CTLineGetTypographicBounds(tempLine, &lineAscent, &lineDescent, &lineLeading)let lineWidth:CGFloat = bounds.widthlet lineheight:CGFloat = lineAscent + lineDescent + lineLeading// 每一行的区域var lineFrame = CGRect(x: origin.x, y: bounds.height - origin.y - lineAscent, width: lineWidth, height: lineheight)lineFrame = lineFrame.insetBy(dx: -SPACE_5, dy: -SPACE_5)if lineFrame.contains(point) {line = tempLinebreak}}free(origins)return line}

将选中的一整行,涂上颜色

触发, 上面的手势代码中

selectRangerects, 赋值了。重绘,就好了

// 获得选中区域selectRange = ...// 获得选中范围rects = ...// 重绘setNeedsDisplay()

触发绘制

/// 绘制override func draw(_ rect: CGRect) {if (frameRef == nil) {return}let ctx = UIGraphicsGetCurrentContext()ctx?.textMatrix = CGAffineTransform.identityctx?.translateBy(x: 0, y: bounds.size.height)ctx?.scaleBy(x: 1.0, y: -1.0)if selectRange != nil , !rects.isEmpty {// 渲染,选中行的背景let path = CGMutablePath()READ_COLOR_MAIN.withAlphaComponent(0.5).setFill()path.addRects(rects)ctx?.addPath(path)ctx?.fillPath()// 先把选中行的背景涂色,再把文字渲染出来,很科学}// 渲染文字CTFrameDraw(frameRef!, ctx!)}

点击复制,相关

复制,就是调用剪贴板UIPasteboard

简单

/// 复制事件@objc private func clickCopy() {if let range = selectRange{let tempContent = pagingModel.contentDispatchQueue.global().async {UIPasteboard.general.string = tempContent?.string.substring(range)}// 重置状态// ...}}

其余
光标的显示

光标就是两个控件,显示光标,简单

选择选中区域

更改选中区域,就是光标的拖拽,逻辑与前文类似,

效果是整行整行的复制

3, 灵活的复制。上面只是定位到某一行,现在定位到那一行的那个字

从 Swift 变到了 Objective-C

基于 GGGHub/Reader

也是通过长按手势触发

渲染出来,老三步

拿到一个点击位置,一个点 CGPoint

计算出当前区域 rect

渲染出来

-(void)longPress:(UILongPressGestureRecognizer *)longPress{// 拿到一个点CGPoint point = [longPress locationInView:self];// ...// 处理 UI 状态if (longPress.state == UIGestureRecognizerStateBegan || longPress.state == UIGestureRecognizerStateChanged) {CGRect rect = [LSYReadParser parserRectWithPoint:point range:&_selectRange frameRef:_frameRef];// ...// 处理 UI 状态if (!CGRectEqualToRect(rect, CGRectZero)) {_pathArray = @[NSStringFromCGRect(rect)];// 触发绘制[self setNeedsDisplay];}}else if (longPress.state == UIGestureRecognizerStateEnded) {// ...// 恢复 UI 状态}}

同上文的逻辑一致,

拿到一个点,一帧文字,

得到选中文字的范围 selectRange,通过传参获取,

得到选中文字的区域,CGRect, 通过返回值获取

+(CGRect)parserRectWithPoint:(CGPoint)point range:(NSRange *)selectRange frameRef:(CTFrameRef)frameRef{CFIndex index = -1;CGPathRef pathRef = CTFrameGetPath(frameRef);CGRect bounds = CGPathGetBoundingBox(pathRef);CGRect rect = CGRectZero;// 拿到每一行NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frameRef);if (!lines) {return rect;}NSInteger lineCount = [lines count];CGPoint *origins = malloc(lineCount * sizeof(CGPoint)); //给每行的起始点开辟内存if (lineCount) {CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), origins);// 查找每一行,看是否包含那个点// 查到了,就返回for (int i = 0; i<lineCount; i++) {CGPoint baselineOrigin = origins[i];CTLineRef line = (__bridge CTLineRef)[lines objectAtIndex:i];CGFloat ascent,descent,linegap; //声明字体的上行高度和下行高度和行距CGFloat lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &linegap);CGRect lineFrame = CGRectMake(baselineOrigin.x, CGRectGetHeight(bounds)-baselineOrigin.y-ascent, lineWidth, ascent+descent+linegap+[LSYReadConfig shareInstance].lineSpace); //没有转换坐标系左下角为坐标原点 字体高度为上行高度加下行高度if (CGRectContainsPoint(lineFrame,point)){// 定位到行了CFRange stringRange = CTLineGetStringRange(line);// 定位到字index = CTLineGetStringIndexForPosition(line, point);CGFloat xStart = CTLineGetOffsetForStringIndex(line, index, NULL);CGFloat xEnd;//默认选中两个单位if (index > stringRange.location+stringRange.length-2) {xEnd = xStart;xStart = CTLineGetOffsetForStringIndex(line,index-2,NULL);(*selectRange).location = index-2;}else{xEnd = CTLineGetOffsetForStringIndex(line,index+2,NULL);(*selectRange).location = index;}// 选中的 2 个字(*selectRange).length = 2;rect = CGRectMake(origins[i].x+xStart,baselineOrigin.y-descent,fabs(xStart-xEnd), ascent+descent);break;}}}free(origins);return rect;}

将选中的 2 个字,涂上颜色

触发

// 赋值_pathArray = ...// 触发绘制[self setNeedsDisplay];

绘制

-(void)drawRect:(CGRect)rect{if (!_frameRef) {return;}CGContextRef ctx = UIGraphicsGetCurrentContext();CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);CGContextTranslateCTM(ctx, 0, self.bounds.size.height);CGContextScaleCTM(ctx, 1.0, -1.0);CGRect leftDot,rightDot = CGRectZero;_menuRect = CGRectZero;// 绘制选中区域[self drawSelectedPath:_pathArray LeftDot:&leftDot RightDot:&rightDot];// 绘制文字CTFrameDraw(_frameRef, ctx);if (_imageArray.count) {// ...// 有图片,绘制图片}// 绘制左右光标// ...}// 绘制选中区域// 有两个返回值, 通过参数返回// leftDot, 顶部行的区域// rightDot, 底部行的区域-(void)drawSelectedPath:(NSArray *)array LeftDot:(CGRect *)leftDot RightDot:(CGRect *)rightDot{// ...// 处理其他 UI 状态CGMutablePathRef _path = CGPathCreateMutable();[[UIColor cyanColor]setFill];for (int i = 0; i < [array count]; i++) {CGRect rect = CGRectFromString([array objectAtIndex:i]);CGPathAddRect(_path, NULL, rect);// 计算返回状态if (i == 0) {*leftDot = rect;// ...// 处理其他 UI 状态}if (i == [array count]-1) {*rightDot = rect;}}// 涂色CGContextRef ctx = UIGraphicsGetCurrentContext();CGContextAddPath(ctx, _path);CGContextFillPath(ctx);CGPathRelease(_path);}

调整选中区域

-(void)pan:(UIPanGestureRecognizer *)pan{CGPoint point = [pan locationInView:self];// ...// 处理其他 UI 状态if (pan.state == UIGestureRecognizerStateBegan || pan.state == UIGestureRecognizerStateChanged) {[self showMagnifier];self.magnifierView.touchPoint = point;if (CGRectContainsPoint(_rightRect, point)||CGRectContainsPoint(_leftRect, point)) {if (CGRectContainsPoint(_leftRect, point)) {_direction = NO; //从左侧滑动}else{_direction= YES; //从右侧滑动}_selectState = YES;}if (_selectState) {// 计算出来,当前选中区域NSArray *path = [LSYReadParser parserRectsWithPoint:point range:&_selectRange frameRef:_frameRef paths:_pathArray direction:_direction];_pathArray = path;// 去渲染[self setNeedsDisplay];}}if (pan.state == UIGestureRecognizerStateEnded) {// ...// 处理其他 UI 状态}}

计算出来,当前选中区域的逻辑,与上文的代码,差不多

4, 框出每一行

有了前面的基础, 框出每一行就很简单

-(void)drawRect:(CGRect)rect{if (!_frameRef) {return;}// 框出每一行CGPathRef pathRef = CTFrameGetPath(_frameRef);CGRect bounds = CGPathGetBoundingBox(pathRef);NSArray *lines = (__bridge NSArray *)CTFrameGetLines(_frameRef);if (lines) {NSInteger lineCount = [lines count];CGPoint *origins = malloc(lineCount * sizeof(CGPoint)); //给每行的起始点开辟内存CTFrameGetLineOrigins(_frameRef, CFRangeMake(0, 0), origins);// 遍历每一行for (int i = 0; i<lineCount; i++) {CGPoint baselineOrigin = origins[i];CTLineRef line = (__bridge CTLineRef)[lines objectAtIndex:i];CGFloat ascent,descent,linegap; //声明字体的上行高度和下行高度和行距CGFloat lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &linegap);// 如果是空行,不用管if (lineWidth > 1){CGRect lineFrame = CGRectMake(baselineOrigin.x, CGRectGetHeight(bounds)-baselineOrigin.y-ascent, lineWidth, ascent+descent+linegap);//没有转换坐标系左下角为坐标原点 字体高度为上行高度加下行高度UIBezierPath * path = [UIBezierPath bezierPathWithRect: lineFrame];[UIColor.orangeColor setStroke];[path stroke];}}free(origins);}// 渲染文字// 其他照旧// ...

github repo

相关博客 iOS: .txt 小说阅读器功能开发的 5 个老套路

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。