UILabel部分文字可点击

源代码:https://github.com/lyb5834/YBAttributeTextTapAction地址

 如果想用富文本文件,可以参考的另外一篇博客;

https://www.cnblogs.com/hualuoshuijia/p/5732619.html

 

如果只需要做点击,只需要使用一个类别文件就可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#import <UIKit/UIKit.h>
 
@protocol RichTextDelegate <NSObject>
@optional
/**
 *  RichTextDelegate
 *
 *  @param string  点击的字符串
 *  @param range   点击的字符串range
 *  @param index   点击的字符在数组中的index
 */
- (void)didClickRichText:(NSString *)string
                   range:(NSRange)range
                   index:(NSInteger)index;
@end
 
@interface UILabel (RichText)
 
/**
 *  是否打开点击效果,默认是打开
 */
@property (nonatomic, assign) BOOL enabledClickEffect;
 
/**
 *  点击效果颜色 默认lightGrayColor
 */
@property (nonatomic, strong) UIColor *clickEffectColor;
 
/**
 *  给文本添加Block点击事件回调
 *
 *  @param strings  需要添加的字符串数组
 *  @param clickAction 点击事件回调
 */
- (void)clickRichTextWithStrings:(NSArray <NSString *> *)strings
                     clickAction:(void (^) (NSString *string, NSRange range, NSInteger index))clickAction;
 
/**
 *  给文本添加点击事件delegate回调
 *
 *  @param strings  需要添加的字符串数组
 *  @param delegate 富文本代理
 */
- (void)clickRichTextWithStrings:(NSArray <NSString *> *)strings
                        delegate:(id <RichTextDelegate> )delegate;

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
#import "UILabel+RichText.h"
#import <objc/runtime.h>
#import <CoreText/CoreText.h>
#import <Foundation/Foundation.h>
 
#define weakSelf(type)      __weak typeof(type) weak##type = type;
 
@interface RichTextModel : NSObject
 
@property (nonatomic, copy) NSString *str;
 
@property (nonatomic, assign) NSRange range;
 
@end
 
@implementation RichTextModel
 
@end
 
 
@implementation UILabel (RichText)
 
#pragma mark - AssociatedObjects
 
- (NSMutableArray *)attributeStrings {
     
    return objc_getAssociatedObject(self, _cmd);
}
 
- (void)setAttributeStrings:(NSMutableArray *)attributeStrings {
     
    objc_setAssociatedObject(self, @selector(attributeStrings), attributeStrings, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
 
- (NSMutableDictionary *)effectDic {
     
    return objc_getAssociatedObject(self, _cmd);
}
 
- (void)setEffectDic:(NSMutableDictionary *)effectDic {
     
    objc_setAssociatedObject(self, @selector(effectDic), effectDic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
 
- (BOOL)isClickAction {
     
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}
 
- (void)setIsClickAction:(BOOL)isClickAction {
     
    objc_setAssociatedObject(self, @selector(isClickAction), @(isClickAction), OBJC_ASSOCIATION_ASSIGN);
}
 
- (void (^)(NSString *, NSRange, NSInteger))clickBlock {
 
    return objc_getAssociatedObject(self, _cmd);
}
 
- (void)setClickBlock:(void (^)(NSString *, NSRange, NSInteger))clickBlock {
     
    objc_setAssociatedObject(self, @selector(clickBlock), clickBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
 
- (id<RichTextDelegate>)delegate {
     
    return objc_getAssociatedObject(self, _cmd);
}
 
- (BOOL)enabledClickEffect {
     
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}
 
- (void)setEnabledClickEffect:(BOOL)enabledClickEffect {
     
    objc_setAssociatedObject(self, @selector(enabledClickEffect), @(enabledClickEffect), OBJC_ASSOCIATION_ASSIGN);
//    self.isClickEffect = enabledClickEffect;
}
 
- (UIColor *)clickEffectColor {
     
    UIColor *obj = objc_getAssociatedObject(self, _cmd);
    if(obj == nil) {obj = [UIColor lightGrayColor];}
    return obj;
}
 
- (void)setClickEffectColor:(UIColor *)clickEffectColor {
     
    objc_setAssociatedObject(self, @selector(clickEffectColor), clickEffectColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
 
- (BOOL)isClickEffect {
     
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}
 
- (void)setIsClickEffect:(BOOL)isClickEffect {
     
    objc_setAssociatedObject(self, @selector(isClickEffect), @(isClickEffect), OBJC_ASSOCIATION_ASSIGN);
}
 
- (void)setDelegate:(id<RichTextDelegate>)delegate {
     
    objc_setAssociatedObject(self, @selector(delegate), delegate, OBJC_ASSOCIATION_ASSIGN);
}
 
#pragma mark - mainFunction
- (void)clickRichTextWithStrings:(NSArray<NSString *> *)strings clickAction:(void (^)(NSString *, NSRange, NSInteger))clickAction {
     
    [self richTextRangesWithStrings:strings];
     
    if (self.clickBlock != clickAction) {
        self.clickBlock = clickAction;
    }
}
 
- (void)clickRichTextWithStrings:(NSArray<NSString *> *)strings delegate:(id<RichTextDelegate>)delegate {
     
    [self richTextRangesWithStrings:strings];
     
    if ([self delegate] != delegate) {
         
        [self setDelegate:delegate];
    }
}
 
#pragma mark - touchAction
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if (!self.isClickAction) {
        return;
    }
     
    if (objc_getAssociatedObject(self, @selector(enabledClickEffect))) {
        self.isClickEffect = self.enabledClickEffect;
    }
     
    UITouch *touch = [touches anyObject];
     
    CGPoint point = [touch locationInView:self];
     
    weakSelf(self);
    [self richTextFrameWithTouchPoint:point result:^(NSString *string, NSRange range, NSInteger index) {
         
        if (weakself.clickBlock) {
            weakself.clickBlock (string , range , index);
        }
         
        if ([weakself delegate] && [[weakself delegate] respondsToSelector:@selector(didClickRichText:range:index:)]) {
            [[weakself delegate] didClickRichText:string range:range index:index];
        }
         
        if (weakself.isClickEffect) {
             
            [weakself saveEffectDicWithRange:range];
             
            [weakself clickEffectWithStatus:YES];
        }
    }];
}
 
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
     
    if((self.isClickAction) && ([self richTextFrameWithTouchPoint:point result:nil])) {
         
        return self;
    }
    return [super hitTest:point withEvent:event];
}
 
#pragma mark - getClickFrame
- (BOOL)richTextFrameWithTouchPoint:(CGPoint)point result:(void (^) (NSString *string , NSRange range , NSInteger index))resultBlock
{
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self.attributedText);
     
    CGMutablePathRef Path = CGPathCreateMutable();
     
    CGPathAddRect(Path, NULL, CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height));
     
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), Path, NULL);
     
    CFRange range = CTFrameGetVisibleStringRange(frame);
     
    if (self.attributedText.length > range.length) {
         
        UIFont *font ;
         
        if ([self.attributedText attribute:NSFontAttributeName atIndex:0 effectiveRange:nil]) {
             
            font = [self.attributedText attribute:NSFontAttributeName atIndex:0 effectiveRange:nil];
             
        }else if (self.font){
            font = self.font;
             
        }else {
            font = [UIFont systemFontOfSize:17];
        }
         
        CGPathRelease(Path);
         
        Path = CGPathCreateMutable();
         
        CGPathAddRect(Path, NULL, CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height + font.lineHeight));
         
        frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), Path, NULL);
    }
     
    CFArrayRef lines = CTFrameGetLines(frame);
     
    if (!lines) {
        CFRelease(frame);
        CFRelease(framesetter);
        CGPathRelease(Path);
        return NO;
    }
     
    CFIndex count = CFArrayGetCount(lines);
     
    CGPoint origins[count];
     
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
     
    CGAffineTransform transform = [self transformForCoreText];
     
    CGFloat verticalOffset = 0;
     
    for (CFIndex i = 0; i < count; i++) {
        CGPoint linePoint = origins[i];
         
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
         
        CGRect flippedRect = [self getLineBounds:line point:linePoint];
         
        CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);
         
        rect = CGRectInset(rect, 0, 0);
         
        rect = CGRectOffset(rect, 0, verticalOffset);
         
        NSParagraphStyle *style = [self.attributedText attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:nil];
         
        CGFloat lineSpace;
         
        if (style) {
            lineSpace = style.lineSpacing;
        }else {
            lineSpace = 0;
        }
         
        CGFloat lineOutSpace = (self.bounds.size.height - lineSpace * (count - 1) -rect.size.height * count) / 2;
         
        rect.origin.y = lineOutSpace + rect.size.height * i + lineSpace * i;
         
        if (CGRectContainsPoint(rect, point)) {
             
            CGPoint relativePoint = CGPointMake(point.x - CGRectGetMinX(rect), point.y - CGRectGetMinY(rect));
             
            CFIndex index = CTLineGetStringIndexForPosition(line, relativePoint);
             
            CGFloat offset;
             
            CTLineGetOffsetForStringIndex(line, index, &offset);
             
            if (offset > relativePoint.x) {
                index = index - 1;
            }
             
            NSInteger link_count = self.attributeStrings.count;
             
            for (int j = 0; j < link_count; j++) {
                 
                RichTextModel *model = self.attributeStrings[j];
                 
                NSRange link_range = model.range;
                if (NSLocationInRange(index, link_range)) {
                    if (resultBlock) {
                        resultBlock (model.str , model.range , (NSInteger)j);
                    }
                    CFRelease(frame);
                    CFRelease(framesetter);
                    CGPathRelease(Path);
                    return YES;
                }
            }
        }
    }
    CFRelease(frame);
    CFRelease(framesetter);
    CGPathRelease(Path);
    return NO;
}
 
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if (self.isClickEffect) {
         
        [self performSelectorOnMainThread:@selector(clickEffectWithStatus:) withObject:nil waitUntilDone:NO];
    }
}
 
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if (self.isClickEffect) {
         
        [self performSelectorOnMainThread:@selector(clickEffectWithStatus:) withObject:nil waitUntilDone:NO];
    }
}
 
- (CGAffineTransform)transformForCoreText
{
    return CGAffineTransformScale(CGAffineTransformMakeTranslation(0, self.bounds.size.height), 1.f, -1.f);
}
 
- (CGRect)getLineBounds:(CTLineRef)line point:(CGPoint)point
{
    CGFloat ascent = 0.0f;
    CGFloat descent = 0.0f;
    CGFloat leading = 0.0f;
    CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
    CGFloat height = ascent + fabs(descent) + leading;
     
    return CGRectMake(point.x, point.y , width, height);
}
 
#pragma mark - clickEffect
- (void)clickEffectWithStatus:(BOOL)status
{
    if (self.isClickEffect) {
        NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
         
        NSMutableAttributedString *subAtt = [[NSMutableAttributedString alloc] initWithAttributedString:[[self.effectDic allValues] firstObject]];
         
        NSRange range = NSRangeFromString([[self.effectDic allKeys] firstObject]);
         
        if (status) {
            [subAtt addAttribute:NSBackgroundColorAttributeName value:self.clickEffectColor range:NSMakeRange(0, subAtt.string.length)];
             
            [attStr replaceCharactersInRange:range withAttributedString:subAtt];
        }else {
             
            [attStr replaceCharactersInRange:range withAttributedString:subAtt];
        }
        self.attributedText = attStr;
    }
}
 
- (void)saveEffectDicWithRange:(NSRange)range
{
    self.effectDic = [NSMutableDictionary dictionary];
     
    NSAttributedString *subAttribute = [self.attributedText attributedSubstringFromRange:range];
     
    [self.effectDic setObject:subAttribute forKey:NSStringFromRange(range)];
}
 
#pragma mark - getRange
- (void)richTextRangesWithStrings:(NSArray <NSString *>  *)strings
{
    if (self.attributedText == nil) {
        self.isClickAction = NO;
        return;
    }
     
    self.isClickAction = YES;
     
    self.isClickEffect = YES;
     
    __block  NSString *totalStr = self.attributedText.string;
     
    self.attributeStrings = [NSMutableArray array];
     
    weakSelf(self);
    [strings enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
         
        NSRange range = [totalStr rangeOfString:obj];
         
        if (range.length != 0) {
             
            totalStr = [totalStr stringByReplacingCharactersInRange:range withString:[weakself getStringWithRange:range]];
             
            RichTextModel *model = [[RichTextModel alloc]init];
             
            model.range = range;
             
            model.str = obj;
             
            [weakself.attributeStrings addObject:model];
        }
    }];
}
 
- (NSString *)getStringWithRange:(NSRange)range
{
    NSMutableString *string = [NSMutableString string];
     
    for (int i = 0; i < range.length ; i++) {
         
        [string appendString:@" "];
    }
    return string;
}
 
@end

 使用方法:

1
2
3
4
5
6
7
8
9
10
NSString *text = @"池塘大桥下,游过一群鸭池塘,快来快来数一数,二四六七八,嘎嘎嘎嘎,真呀真多呀,fly,数不清到底多少鸭";
    NSMutableAttributedString *string = [[NSMutableAttributedString alloc]initWithString:text];
    [string addAttribute:action1 value:@"" range:NSMakeRange(2, 3)];
    [string addAttributes:@{NSStrikethroughStyleAttributeName:@"",NSForegroundColorAttributeName:[UIColor redColor]}  range:NSMakeRange(2, 3)];
    self.label2.userInteractionEnabled = true;
    self.label2.attributedText = string;
//    self.label2.enabledClickEffect = false;
    [self.label2 clickRichTextWithStrings:@[@"池塘"] clickAction:^(NSString *string, NSRange range, NSInteger index) {
        NSLog(@"-------");
    }];

 如果想用富文本文件,可以参考的另外一篇博客;

https://www.cnblogs.com/hualuoshuijia/p/5732619.html

 

posted @   新年新气象  阅读(7814)  评论(0编辑  收藏  举报
编辑推荐:
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
阅读排行:
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· PPT革命!DeepSeek+Kimi=N小时工作5分钟完成?
· What?废柴, 还在本地部署DeepSeek吗?Are you kidding?
· DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地
· 程序员转型AI:行业分析
历史上的今天:
2017-11-08 VUE 使用之:nextTick
点击右上角即可分享
微信分享提示