博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(一一二)图文混排中特殊文字的点击与事件处理
阅读量:5161 次
发布时间:2019-06-13

本文共 7375 字,大约阅读时间需要 24 分钟。

在上一篇文章中提到了对attributedText的特殊处理,将当中的话题、URL都用红色进行了表示。例如以下图所看到的:

本节要实现的功能是这种attributedText在点击话题、URL时应该有所响应,而在点击其它位置的时候把事件传递给父类处理。

要获取到点击的位置非常easy。仅仅须要重写touchesBegan方法就可以拿到触摸点,比較棘手的是推断出触摸位置的文字的范围(CGRect),假设能拿到点击文字的rect,就能对rect进行高亮处理和事件处理。

把这个需求拆分,我们应该依照以下的步骤进行:

①在拼接attributedText的时候记录下全部特殊文字的位置和内容、保存到模型里。再把模型存入数组。

#import 
@interface TextSpecial : NSObject@property (nonatomic, copy) NSString *text;@property (nonatomic, assign) NSRange range;@end
由于微博数据也是通过模型传递的,为微博模型定义属性来保存全部的模型,以便兴许处理。

@property (nonatomic, strong) NSMutableArray *specialSegments;
在上节介绍的代码中,增加一段用于保存特殊文字的代码:

[self.specialSegments removeAllObjects];        NSInteger cnt = parts.count;    for (NSInteger i = 0; i < cnt; i++) {        TextSegment *ts = parts[i];        if (ts.isEmotion) {            NSTextAttachment *attach = [[NSTextAttachment alloc] init];            attach.image = [UIImage imageNamed:@"avatar_vgirl"];            attach.bounds = CGRectMake(0, -3, 15, 15);            NSAttributedString *emotionStr = [NSAttributedString attributedStringWithAttachment:attach];            [attributedText appendAttributedString:emotionStr];        }else if(ts.isSpecial){            NSAttributedString *special = [[NSAttributedString alloc] initWithString:ts.text attributes:@{NSForegroundColorAttributeName:[UIColor redColor]}];                        // 保存全部特殊文字的内容和位置,保存到模型。再把模型存入数组。

TextSpecial *spec = [[TextSpecial alloc] init]; spec.text = ts.text; NSInteger loc = attributedText.length; NSInteger len = ts.text.length; spec.range = NSMakeRange(loc, len); [self.specialSegments addObject:spec]; [attributedText appendAttributedString:special]; }else{ [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:ts.text]]; } }

微博模型在传递给用于显示微博的Cell时,能够把上面的特殊文字模型数组传入。

②拿到了全部特殊字符的位置,以下的需求就是通过特殊文字的位置得到特殊文字的rect。由于仅仅要遍历这些rect,假设触摸点在这些rect之中,则把相关位置的rect都高亮和兴许处理就可以。

如今问题的关键就是依据字串位置拿到rect,这个功能仅仅有通过UITextView才干实现。UITextView依照以下的步骤能够得到特殊文字的全部rect。

这里之所以提到全部rect,是由于可能出现特殊文字有换行的情况。则两行的特殊文字都应该被高亮,这样就会有多个rect。因此传入一个特殊文字的范围,返回一系列rect是必要的

过程例如以下:

1.设置TextView的selectedRange属性。用特殊文字的range传入。

2.调用TextView的selectionRectsForRange:方法,传入TextView的selectedTextRange属性,拿到特殊文字的rect数组。

3.由于仅仅是为了拿到rect而选中了TextView,并非为了真的选中部分。因此应该取消选择,让selectedRange回到(0,0),即不选择。

self.tv.selectedRange = spec.range; // spec是一个特殊文字模型,当中的range指的是这个特殊文字在attributedText中的位置。NSArray *rects = [self.tv selectionRectsForRange:self.tv.selectedTextRange];self.tv.selectedRange = NSMakeRange(0, 0); // 重置选中范围。仅仅是为了使用选中范围来获取rect而不是真的选中。

微博Cell中用于显示微博主体的为自己定义的UITextView,为了方便拓展,使用UIView包着UITextView。重写layoutSubviews方法来让UITextView的尺寸和UIView一致,而这个UIView暴露出attributedText用于显示上一节处理好的文字。暴露出特殊文字数组。用于接收上面计算的结果。进行兴许推断。

这个自己定义TextView的.h代码例如以下:

#import 
#import
@interface SGStatusTextView : UIView@property (nonatomic, copy) NSAttributedString *attributedText;@property (nonatomic, strong) NSArray *specialSegments;@end
基本的初始化代码,主要包含创建TextView的尺寸,把从模型传入的attributedText传入到TextView的attributedText以便显示。

@interface SGStatusTextView ()@property (nonatomic, weak) UITextView *tv;@end@implementation SGStatusTextView- (instancetype)initWithFrame:(CGRect)frame{        self = [super initWithFrame:frame];    if (self) {                UITextView *tv = [[UITextView alloc] init];                // UITextView默认上面有20的内边距,应该改动textContainerInset        tv.textContainerInset = UIEdgeInsetsMake(0, -5, 0, -5);        tv.editable = NO;        tv.scrollEnabled = NO;        tv.userInteractionEnabled = NO;        //self.userInteractionEnabled = NO;        tv.backgroundColor = [UIColor clearColor];        tv.font = ContentFont;                [self addSubview:tv];        _tv = tv;    }        return self;    }- (void)setAttributedText:(NSAttributedString *)attributedText{        _tv.attributedText = attributedText;    }- (void)layoutSubviews{        [super layoutSubviews];        _tv.frame = self.bounds;    }
以下仅仅要重写touchedBegan。遍历最前面得到的特殊文字的模型数组,对每一个模型通过textView拿到rect。再推断触摸点是否在rect内,假设在。
这个特殊文字有关的全部rect都要高亮处理(出如今特殊文字换行显示的情况)。代码例如以下。当中selected变量用于推断是否有触摸到特殊文字,假设处理到停止对当前特殊文字全部rect的遍历,再又一次開始一次遍历,把全部rect高亮,接着跳出对特殊文字模型数组的遍历。完毕一次触摸事件的处理。

注意高亮文字是通过加入带圆角的UIView遮盖实现的,在index=0处。也就是最以下加入遮盖,能够保证遮盖不挡住文字,用tag标记,以便在触摸结束时删除。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{        CGPoint pt = [[touches anyObject] locationInView:self];    BOOL selected = NO;        for (TextSpecial *spec in self.specialSegments) {        self.tv.selectedRange = spec.range;        NSArray *rects = [self.tv selectionRectsForRange:self.tv.selectedTextRange];        self.tv.selectedRange = NSMakeRange(0, 0); // 重置选中范围,仅仅是为了使用选中范围来获取rect而不是真的选中。        for (UITextSelectionRect *selectionRect in rects) {            CGRect rect = selectionRect.rect;            if (rect.size.width == 0 || rect.size.height == 0) continue;                        if (CGRectContainsPoint(rect, pt)) {                selected = YES;                break;            }                    }                if (selected) {            for (UITextSelectionRect *selectionRect in rects) {                CGRect rect = selectionRect.rect;                if (rect.size.width == 0 || rect.size.height == 0) continue;                                UIView *cover = [[UIView alloc] initWithFrame:rect];                cover.layer.cornerRadius = 5;                cover.backgroundColor = [UIColor greenColor];                cover.tag = CoverTag;                [self insertSubview:cover atIndex:0];                            }            break;        }            }    }
触摸事件的完毕包含取消和完毕两种情况。
假设是完毕,应该延时移除遮盖。否则单击看不出反应,仅仅有长按才干看出反应(由于单击之后遮盖立马被移除)。取消则不必延时,由于取消是由于被其它事件打断。

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{        // 为了能实现单击的响应,应该延时移除视图。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ for (UIView *view in self.subviews) { if (view.tag == CoverTag) { [view removeFromSuperview]; } } }); } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{ for (UIView *view in self.subviews) { if (view.tag == CoverTag) { [view removeFromSuperview]; } } }

③上面处理之后尽管能够处理特殊文字的点击事件了,可是在普通文字部分的点击也被textView给处理了。这样就不能实现正常的微博业务逻辑中,点击Cell中的非特殊部分。由tableView处理事件。

这就须要用到系统的事件响应机制:

iOS的事件处理优先考虑最上面的控件。系统会调用以下的方法来询问是否由当前控件处理事件:

// 触摸事件传递时会调用以下的方法询问是否处理- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{    // point是触摸点,用于推断是否在当前控件内,返回NO表示不处理。返回YES表示处理触摸事件。    return NO;    }
不要一味的返回YES。由于返回YES即使点击的位置不在当前控件上,可是当前控件在最上层,事件会被私吞。

在调用了这个事件之后,假设返回YES,系统会继续调用当前控件的以下的方法询问应该由谁处理该事件。

// 仅仅有pointInside::返回YES的控件才会调用此方法,能够决定由谁处理事件,不实现由自己处理。- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{    }
通过这种方法能够由当前控件任意决定由哪个控件处理触摸事件。

经过上面的逻辑,我们仅仅须要重写pointInside::方法。推断触摸点是否在特殊文字的rect上,假设是则处理事件,否则不处理,交由父控件Cell、再传递到祖先控件tableView处理。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{    for (TextSpecial *spec in self.specialSegments) {                self.tv.selectedRange = spec.range;        NSArray *rects = [self.tv selectionRectsForRange:self.tv.selectedTextRange];        self.tv.selectedRange = NSMakeRange(0, 0); // 重置选中范围。仅仅是为了使用选中范围来获取rect而不是真的选中。

for (UITextSelectionRect *selectionRect in rects) { CGRect rect = selectionRect.rect; if (rect.size.width == 0 || rect.size.height == 0) continue; if (CGRectContainsPoint(rect, point)) { return YES; } } } return NO; }

转载于:https://www.cnblogs.com/jhcelue/p/6962985.html

你可能感兴趣的文章
解决Ubuntu下博通网卡驱动问题
查看>>
【bzoj2788】Festival
查看>>
执行gem install dryrun错误
查看>>
HTML5简单入门系列(四)
查看>>
实现字符串反转
查看>>
转载:《TypeScript 中文入门教程》 5、命名空间和模块
查看>>
苹果开发中常用英语单词
查看>>
[USACO 1.4.3]等差数列
查看>>
Shader Overview
查看>>
Reveal 配置与使用
查看>>
Java中反射的学习与理解(一)
查看>>
C语言初学 俩数相除问题
查看>>
B/S和C/S架构的区别
查看>>
[Java] Java record
查看>>
jQuery - 控制元素显示、隐藏、切换、滑动的方法
查看>>
postgresql学习文档
查看>>
Struts2返回JSON数据的具体应用范例
查看>>
js深度克隆对象、数组
查看>>
socket阻塞与非阻塞,同步与异步
查看>>
团队工作第二天
查看>>