最近在做社交功能, 遇到一个坎, 点击对应的文字响应事件, 就像微博里那种, 有@谁的 这个后面就能点击跳到某某人的主页, 如果只是在前面, 像微博中的话题, 带#号的, 而且也不长, 这个倒是好解决, 一开始想到UITextView的超链接, 但是UITextView在XIB里又不能根据文字的多少而变大小, 我的想法是用label, 但是label怎么获取点击的范围, 这是重点, 当然, 如果只是像前面带#话题#这种, 在没有这个之前, 我的解决方法是, 在这个label上再加一个label用于显示#话题#,下面的label将这几个字拼接在前面, 用属性字符串将这几个字颜色搞成透明的, 只监听上面这个label的点击手势即可, 但是长远考虑, 这样不是长久之计, 如果很长, 换行了, 那就不好办了, 只能硬着头皮上了......
先看下效果图吧
基本上实现了文字点击响应事件, 简单测试, 应该可用
最主要的难点就是在于获取特定文字的范围
网上找过demo, 但是实现的思路不是特别明白, 所以我还是按照自己的思路来整
- 设定可点击的文字
- 获取可点击文字在控件上的rect
- 点击的时候, 遍历控件保存的rect数组, 在哪个数组里
- 找到对应的响应对象去响应事件
不知道会不会有人想说直接用TextView不就好了么?我要用label的原因, 就是因为它在XIB里能伸缩, 要是算出内容大小, 改变约束也不是不可以的
简单点说一下我的做法, 我是在label上加了一个textView, 获取文字的范围, 暂时没有找到能代理textView能去干这件事的
- (void)setUp{
self.clickItems = [NSMutableArray array];
self.clickAttachmentItems = [NSMutableDictionary dictionary];
self.userInteractionEnabled = YES;
self.clipsToBounds = YES;
self.textView.font = self.font;
self.textView.frame = CGRectMake(-5, -8, self.bounds.size.width + 10, self.bounds.size.height + 16);
}
为什么textView的x值和y值要设为-5和-8呢, 做过有占位文件的TextView的童鞋就知道了, textView的文字显示, 与边缘有间距, 距离左边就是5了, 上面差不多就是8这个样子, 实在不行自己再xib里调调就知道了
懒加载的一个TextView, 将背景色设为透明, 将文字颜色设为透明, 设为透明的主要原因是label上去显示内容(其实也可以用textView去显示内容的)
NSString *str = @"呵呵哒 :呵呵呵呵呵呵呵呵额哈哈哈呵呵呵呵呵 这里可以点击 呵呵呵额哈哈哈呵呵呵呵呵呵呵呵额哈哈哈呵呵呵呵呵呵呵呵额哈哈哈呵呵呵";
self.label.text = str;
self.label.font = [UIFont systemFontOfSize:20];
[self.label addClickText:@"呵呵哒 :" attributeds:@{NSForegroundColorAttributeName : [UIColor orangeColor]} transmitBody:(id)@"呵呵哒 被点击了" clickItemBlock:^(id transmitBody) {
[[[UIAlertView alloc] initWithTitle:@"提示" message:[NSString stringWithFormat:@"%@", transmitBody] delegate:self cancelButtonTitle:@"取消" otherButtonTitles: nil] show];
}];
[self.label addClickText:@"这里可以点击" attributeds:@{NSForegroundColorAttributeName : [UIColor greenColor]} transmitBody:(id)@"确实可以点击" clickItemBlock:^(id transmitBody) {
[[[UIAlertView alloc] initWithTitle:@"提示" message:[NSString stringWithFormat:@"%@", transmitBody] delegate:self cancelButtonTitle:@"取消" otherButtonTitles: nil] show];
}];
这是demo里的添加点击事件的代码
看看具体的实现吧
- (void)addClickText:(NSString *)text attributeds:(NSDictionary *)attributeds transmitBody:(id)transmitBody clickItemBlock:(FMLinkLabelClickItemBlock)clickBlock{
// 根据现有的文本生成可变的富文本
NSMutableAttributedString *attr = nil;
if (self.attributedText) {
attr = [self.attributedText mutableCopy];
} else {
attr = [[[NSAttributedString alloc] initWithString:self.text] mutableCopy];
}
// 锁定可以点击文字的范围
NSRange range = [[attr string] rangeOfString:text];
if (range.location != NSNotFound) {
[attr setAttributes:attributeds range:range];
self.attributedText = attr;
FMLinkLabelClickItem *item = [FMLinkLabelClickItem itemWithText:text range:range transmitBody:transmitBody];
item.clickBlock = clickBlock;
[self addClickItem:item];
[attr setAttributes:@{NSFontAttributeName : self.font} range:NSMakeRange(0, attr.length)];
self.textView.text = [attr string];
}
}
- (void)addClickItem:(FMLinkLabelClickItem *)item{
// 循环遍历每一个字符的范围, 防止换行导致的部分不能响应
for (int i = 0; i < item.range.length; i++) {
NSRange range = NSMakeRange(item.range.location + i, 1);
// 设置TextView的选中范围
self.textView.selectedRange = range;
// 获取选中范围在textView上的尺寸
CGRect rect = [self.textView firstRectForRange:self.textView.selectedTextRange];
// 将选中范围清空
self.textView.selectedRange = NSMakeRange(0, 0);
// 转换坐标系到本身上来
CGRect textRect = [self.textView convertRect:rect toView:self];
// 有点不准确, textView设置内容的时候container有偏移量吧 具体不太清楚
NSInteger remainder = (NSInteger)textRect.origin.y % (NSInteger)self.font.lineHeight;
if (remainder > 0) {
textRect.origin.y += (self.font.lineHeight - remainder);
}
// 加入这个尺寸到数组 方便判断
[item.textRects addObject:[NSValue valueWithCGRect:textRect]];
}
[self.clickItems addObject:item];
}
label的交互已经开启, 监听点击事件就好了
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
// 遍历所有的点击事件
[self.clickItems enumerateObjectsUsingBlock:^(FMLinkLabelClickItem *obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 遍历点击事件里的点击范围
[obj.textRects enumerateObjectsUsingBlock:^(NSValue *rectValue, NSUInteger idx, BOOL * _Nonnull stop1) {
if (CGRectContainsPoint([rectValue CGRectValue], point)) {
if (obj.clickBlock) {
obj.clickBlock(obj.transmitBody);
isClickText = YES;
*stop = YES;
*stop1 = YES;
}
}
}];
}];
}
用的block传值的, 用代理回调也是可以的, 感觉没有block来的直观, (后续会加上代理), 上面代码的注释都有, 就不废话了
献上demo地址FMLinkLabel