最近在做社交功能, 遇到一個坎, 點擊對應的文字響應事件, 就像微博里那種, 有@誰的 這個后面就能點擊跳到某某人的主頁, 如果只是在前面, 像微博中的話題, 帶#號的, 而且也不長, 這個倒是好解決, 一開始想到UITextView的超鏈接, 但是UITextView在XIB里又不能根據(jù)文字的多少而變大小, 我的想法是用label, 但是label怎么獲取點擊的范圍, 這是重點, 當然, 如果只是像前面帶#話題#這種, 在沒有這個之前, 我的解決方法是, 在這個label上再加一個label用于顯示#話題#,下面的label將這幾個字拼接在前面, 用屬性字符串將這幾個字顏色搞成透明的, 只監(jiān)聽上面這個label的點擊手勢即可, 但是長遠考慮, 這樣不是長久之計, 如果很長, 換行了, 那就不好辦了, 只能硬著頭皮上了......
先看下效果圖吧
基本上實現(xiàn)了文字點擊響應事件, 簡單測試, 應該可用
最主要的難點就是在于獲取特定文字的范圍
網(wǎng)上找過demo, 但是實現(xiàn)的思路不是特別明白, 所以我還是按照自己的思路來整
- 設定可點擊的文字
- 獲取可點擊文字在控件上的rect
- 點擊的時候, 遍歷控件保存的rect數(shù)組, 在哪個數(shù)組里
- 找到對應的響應對象去響應事件
不知道會不會有人想說直接用TextView不就好了么?我要用label的原因, 就是因為它在XIB里能伸縮, 要是算出內(nèi)容大小, 改變約束也不是不可以的
簡單點說一下我的做法, 我是在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里調(diào)調(diào)就知道了
懶加載的一個TextView, 將背景色設為透明, 將文字顏色設為透明, 設為透明的主要原因是label上去顯示內(nèi)容(其實也可以用textView去顯示內(nèi)容的)
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里的添加點擊事件的代碼
看看具體的實現(xiàn)吧
- (void)addClickText:(NSString *)text attributeds:(NSDictionary *)attributeds transmitBody:(id)transmitBody clickItemBlock:(FMLinkLabelClickItemBlock)clickBlock{
// 根據(jù)現(xiàn)有的文本生成可變的富文本
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{
// 循環(huán)遍歷每一個字符的范圍, 防止換行導致的部分不能響應
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);
// 轉(zhuǎn)換坐標系到本身上來
CGRect textRect = [self.textView convertRect:rect toView:self];
// 有點不準確, textView設置內(nèi)容的時候container有偏移量吧 具體不太清楚
NSInteger remainder = (NSInteger)textRect.origin.y % (NSInteger)self.font.lineHeight;
if (remainder > 0) {
textRect.origin.y += (self.font.lineHeight - remainder);
}
// 加入這個尺寸到數(shù)組 方便判斷
[item.textRects addObject:[NSValue valueWithCGRect:textRect]];
}
[self.clickItems addObject:item];
}
label的交互已經(jīng)開啟, 監(jiān)聽點擊事件就好了
- (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傳值的, 用代理回調(diào)也是可以的, 感覺沒有block來的直觀, (后續(xù)會加上代理), 上面代碼的注釋都有, 就不廢話了
獻上demo地址FMLinkLabel