iOS两丫技术之UILabel性能不够的解决方法

 更新时间:2022年08月02日 10:07:39   作者:dengjiangszhan  
这篇文章主要介绍了iOS中控件UILabel性能不够而自定义UILabel的过程,UILable是iPhone界面最基本的控件,主要用来显示文本信息,下面通过本文我们来了解一下

主要参照 YYKit

YYKit 博大精深,就像少林武功

Async View

为了异步 + runloop 空闲时绘制,

因为苹果的 UILabel 性能不够 6

Async Layer

思路: UI 操作,必须放在主线程,

然而图形处理,可以放在子线程,

( 开辟图形上下文,进行绘制,取出图片 )

最后一步,放在主线程,就好了

layer.contents = image

Custom View 中, layer 类,重新制定为异步 layer

+ (Class)layerClass {
    return YYAsyncLayer.class;
}

建立绘制任务

创建一个绘制任务,YYAsyncLayerDisplayTask

关键是里面的绘制方法 display

拿到异步图层 layer 创建的图形上下文,

调一下坐标系,( Core Text 的原点,在左下方 )

文本 map 为富文本,

富文本 map 为一帧,

一帧拆分为好多 CTLine,

一行一行地展示

- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
    // capture current state to display task
    NSString *text = _text;
    UIFont *fontX = _font;
    YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new];
    CGFloat h_h = self.bounds.size.height;
    CGFloat w_w = self.bounds.size.width;
    task.display = ^(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)) {
        if (isCancelled()) return;
        //在这里由于绘制文字会颠倒
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            CGContextSetTextMatrix(context, CGAffineTransformIdentity);
            CGContextTranslateCTM(context, 0, h_h);
            CGContextScaleCTM(context, 1.0, -1.0);
        }];
        NSAttributedString* str = [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: fontX, NSForegroundColorAttributeName: UIColor.blueColor}];
        CTFramesetterRef ref = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)str);
        CGPathRef path = CGPathCreateWithRect(CGRectMake(0, 0, w_w, 3000), nil);
        CTFrameRef pic = CTFramesetterCreateFrame(ref, CFRangeMake(0, 0), path, nil);
        CFArrayRef arr = CTFrameGetLines(pic);
        NSArray *array = (__bridge NSArray*)arr;
        int i = 0;
        int cnt = (int)array.count;
        CGPoint originsArray[cnt];
        CTFrameGetLineOrigins(pic, CFRangeMake(0, 0), originsArray);
        CGFloat y_y = h_h - 60;
        while (i < cnt) {
            NSLog(@"%f", originsArray[i].y);
            CTLineRef line = (__bridge CTLineRef)(array[i]);
            CGContextSetTextPosition(context, 0, y_y - i * 30);
            CTLineDraw(line, context);
            i += 1;
        }
    };
    return task;
}

Async Layer 中, 启动绘制任务,

先处理下继承关系,

再执行上文提到的绘制任务

- (void)display {
    super.contents = super.contents;
    [self _displayAsync];
}

执行绘制任务,

拿到任务,没有绘制内容,就算了

再判断,自身的大小 ( size ), 合不合规

大小 CGSize(1, 1), 就继续,

子线程,先开辟图形上下文,

再处理背景色,

如果顺利,执行上文的绘制步骤,

从图形上下文中,取出 image, 交给 layer.contents

- (void)_displayAsync{
    __strong id<YYAsyncLayerDelegate> delegate = (id)self.delegate;
    YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
    if (!task.display) {
        self.contents = nil;
        return;
    }
        CGSize size = self.bounds.size;
        BOOL opaque = self.opaque;
        CGFloat scale = self.contentsScale;
        CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;
        if (size.width < 1 || size.height < 1) {
            CGImageRef image = (__bridge_retained CGImageRef)(self.contents);
            self.contents = nil;
            if (image) {
                dispatch_async(YYAsyncLayerGetReleaseQueue(), ^{
                    CFRelease(image);
                });
            }
            CGColorRelease(backgroundColor);
            return;
        }
        dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
            if (isCancelled()) {
                CGColorRelease(backgroundColor);
                return;
            }
            UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
            CGContextRef context = UIGraphicsGetCurrentContext();
            if (opaque) {
                CGContextSaveGState(context); {
                    if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
                        CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
                        CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
                        CGContextFillPath(context);
                    }
                    if (backgroundColor) {
                        CGContextSetFillColorWithColor(context, backgroundColor);
                        CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
                        CGContextFillPath(context);
                    }
                } CGContextRestoreGState(context);
                CGColorRelease(backgroundColor);
            }
            task.display(context, size, isCancelled);
            if (isCancelled()) {
                UIGraphicsEndImageContext();
                return;
            }
            UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            if (isCancelled()) {
                return;
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                if (isCancelled() == NO) {
                    self.contents = (__bridge id)(image.CGImage);
                }
            });
        });
}

RunLoop

触发

设置样式后,不会立即触发,重绘

先保存起来

- (void)setText:(NSString *)text {
    _text = text.copy;
    [[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
}

调用异步图层的绘制任务

- (void)contentsNeedUpdated {
    // do update
    [self.layer setNeedsDisplay];
}

事件的保存

先把方法调用的 target 和 action, 保存为对象

YYTransactionSetup(); 单例方法,初始化

把方法调用的对象,添加到集合

@implementation YYTransaction
+ (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{
    if (!target || !selector) return nil;
    YYTransaction *t = [YYTransaction new];
    t.target = target;
    t.selector = selector;
    return t;
}
- (void)commit {
    if (!_target || !_selector) return;
    YYTransactionSetup();
    [transactionSet addObject:self];
}

空闲的时候,把事情给办了,不影响帧率

下面的单例方法,初始化事件任务集合,

run loop 回调中,执行

不干涉, 主 runloop

static NSMutableSet *transactionSet = nil;
static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    if (transactionSet.count == 0) return;
    NSSet *currentSet = transactionSet;
    transactionSet = [NSMutableSet new];
    [currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
        [transaction.target performSelector:transaction.selector];
    }];
}
static void YYTransactionSetup() {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        transactionSet = [NSMutableSet new];
        CFRunLoopRef runloop = CFRunLoopGetMain();
        CFRunLoopObserverRef observer;
        observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
                                           kCFRunLoopBeforeWaiting | kCFRunLoopExit,
                                           true, 
                                           0xFFFFFF,
                                           YYRunLoopObserverCallBack, NULL);
        CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
        CFRelease(observer);
    });
}

YYLabel

功能相当强大的渲染工具,

在上文异步渲染的基础上

支持各种样式

增加了一层抽象 YYTextLayout

YYLabel 中的绘制任务,

如果需要更新,就创建新的布局 layout ,

如果居中 / 底部对其,处理下 layout 布局

然后 layout 绘制

@implementation YYLabel
- (YYTextAsyncLayerDisplayTask *)newAsyncDisplayTask {
    // create display task
    YYTextAsyncLayerDisplayTask *task = [YYTextAsyncLayerDisplayTask new];
    task.display = ^(CGContextRef context, CGSize size, BOOL (^isCancelled)(void)) {
        if (isCancelled()) return;
        if (text.length == 0) return;
        YYTextLayout *drawLayout = layout;
        if (layoutNeedUpdate) {
            layout = [YYTextLayout layoutWithContainer:container text:text];
            shrinkLayout = [YYLabel _shrinkLayoutWithLayout:layout];
            if (isCancelled()) return;
            layoutUpdated = YES;
            drawLayout = shrinkLayout ? shrinkLayout : layout;
        }
        CGSize boundingSize = drawLayout.textBoundingSize;
        CGPoint point = CGPointZero;
        if (verticalAlignment == YYTextVerticalAlignmentCenter) {
            if (drawLayout.container.isVerticalForm) {
                point.x = -(size.width - boundingSize.width) * 0.5;
            } else {
                point.y = (size.height - boundingSize.height) * 0.5;
            }
        } else if (verticalAlignment == YYTextVerticalAlignmentBottom) {
            if (drawLayout.container.isVerticalForm) {
                point.x = -(size.width - boundingSize.width);
            } else {
                point.y = (size.height - boundingSize.height);
            }
        }
        point = YYTextCGPointPixelRound(point);
        [drawLayout drawInContext:context size:size point:point view:nil layer:nil debug:debug cancel:isCancelled];
    };
    return task;
}
@end

绘制各种

先绘制背景,

其次画阴影,

下划线,

文字,

图片

边框

@implementation YYTextLayout
- (void)drawInContext:(CGContextRef)context
                 size:(CGSize)size
                point:(CGPoint)point
                 view:(UIView *)view
                layer:(CALayer *)layer
                debug:(YYTextDebugOption *)debug
                cancel:(BOOL (^)(void))cancel{
    @autoreleasepool {
        if (self.needDrawBlockBorder && context) {
            if (cancel && cancel()) return;
            YYTextDrawBlockBorder(self, context, size, point, cancel);
        }
        if (self.needDrawBackgroundBorder && context) {
            if (cancel && cancel()) return;
            YYTextDrawBorder(self, context, size, point, YYTextBorderTypeBackgound, cancel);
        }
        if (self.needDrawShadow && context) {
            if (cancel && cancel()) return;
            YYTextDrawShadow(self, context, size, point, cancel);
        }
        if (self.needDrawUnderline && context) {
            if (cancel && cancel()) return;
            YYTextDrawDecoration(self, context, size, point, YYTextDecorationTypeUnderline, cancel);
        }
        if (self.needDrawText && context) {
            if (cancel && cancel()) return;
            YYTextDrawText(self, context, size, point, cancel);
        }
        if (self.needDrawAttachment && (context || view || layer)) {
            if (cancel && cancel()) return;
            YYTextDrawAttachment(self, context, size, point, view, layer, cancel);
        }
        if (self.needDrawInnerShadow && context) {
            if (cancel && cancel()) return;
            YYTextDrawInnerShadow(self, context, size, point, cancel);
        }
        if (self.needDrawStrikethrough && context) {
            if (cancel && cancel()) return;
            YYTextDrawDecoration(self, context, size, point, YYTextDecorationTypeStrikethrough, cancel);
        }
        if (self.needDrawBorder && context) {
            if (cancel && cancel()) return;
            YYTextDrawBorder(self, context, size, point, YYTextBorderTypeNormal, cancel);
        }
        if (debug.needDrawDebug && context) {
            if (cancel && cancel()) return;
            YYTextDrawDebug(self, context, size, point, debug);
        }
    }
}

进入绘制文字

还有图片

这里的绘制粒度,比较上文,

粒度更加的细

上文是 CTLine,

这里是 CTRun

// 注意条件判断,
// 与保存 / 恢复图形上下文
static void YYTextDrawAttachment(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, UIView *targetView, CALayer *targetLayer, BOOL (^cancel)(void)) {
    BOOL isVertical = layout.container.verticalForm;
    CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
    for (NSUInteger i = 0, max = layout.attachments.count; i < max; i++) {
        YYTextAttachment *a = layout.attachments[i];
        if (!a.content) continue;
        UIImage *image = nil;
        UIView *view = nil;
        CALayer *layer = nil;
        if ([a.content isKindOfClass:[UIImage class]]) {
            image = a.content;
        } else if ([a.content isKindOfClass:[UIView class]]) {
            view = a.content;
        } else if ([a.content isKindOfClass:[CALayer class]]) {
            layer = a.content;
        }
        if (!image && !view && !layer) continue;
        if (image && !context) continue;
        if (view && !targetView) continue;
        if (layer && !targetLayer) continue;
        if (cancel && cancel()) break;
        CGSize asize = image ? image.size : view ? view.frame.size : layer.frame.size;
        CGRect rect = ((NSValue *)layout.attachmentRects[i]).CGRectValue;
        if (isVertical) {
            rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(a.contentInsets));
        } else {
            rect = UIEdgeInsetsInsetRect(rect, a.contentInsets);
        }
        rect = YYTextCGRectFitWithContentMode(rect, asize, a.contentMode);
        rect = YYTextCGRectPixelRound(rect);
        rect = CGRectStandardize(rect);
        rect.origin.x += point.x + verticalOffset;
        rect.origin.y += point.y;
        if (image) {
            CGImageRef ref = image.CGImage;
            if (ref) {
                CGContextSaveGState(context);
                CGContextTranslateCTM(context, 0, CGRectGetMaxY(rect) + CGRectGetMinY(rect));
                CGContextScaleCTM(context, 1, -1);
                CGContextDrawImage(context, rect, ref);
                CGContextRestoreGState(context);
            }
        } else if (view) {
            view.frame = rect;
            [targetView addSubview:view];
        } else if (layer) {
            layer.frame = rect;
            [targetLayer addSublayer:layer];
        }
    }
}

本文,最后一个问题:

上面 layout 的绘制信息,怎么取得的?

先拿文本,创建 CTFrame, CTFrame 中拿到 CTLine 数组

然后遍历每一行,与计算

@implementation YYTextLayout
+ (YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range {
    // ...
    ctSetter = CTFramesetterCreateWithAttributedString((CFTypeRef)text);
    if (!ctSetter) goto fail;
    ctFrame = CTFramesetterCreateFrame(ctSetter, YYTextCFRangeFromNSRange(range), cgPath, (CFTypeRef)frameAttrs);
    if (!ctFrame) goto fail;
    lines = [NSMutableArray new];
    ctLines = CTFrameGetLines(ctFrame);
   // ...
   for (NSUInteger i = 0, max = lines.count; i < max; i++) {
        YYTextLine *line = lines[i];
        if (truncatedLine && line.index == truncatedLine.index) line = truncatedLine;
        if (line.attachments.count > 0) {
            [attachments addObjectsFromArray:line.attachments];
            [attachmentRanges addObjectsFromArray:line.attachmentRanges];
            [attachmentRects addObjectsFromArray:line.attachmentRects];
            for (YYTextAttachment *attachment in line.attachments) {
                if (attachment.content) {
                    [attachmentContentsSet addObject:attachment.content];
                }
            }
        }
    }
    // ...
}

github repo

到此这篇关于iOS两丫技术之UILabel性能不够的解决方法的文章就介绍到这了,更多相关iOS UILabe内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解iOS之关于double/float数据计算精度问题

    详解iOS之关于double/float数据计算精度问题

    本篇文章主要介绍了iOS之关于double/float数据计算精度问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • iOS自定义UIButton点击动画特效

    iOS自定义UIButton点击动画特效

    这篇文章主要为大家详细介绍了iOS自定义UIButton点击动画特效,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-04-04
  • 零基础学习iOS直播之播放

    零基础学习iOS直播之播放

    对于直播来说,客户端主要做两件事情,推流和播放。本篇主要对播放进行详细介绍,需要的朋友一起来看下吧
    2016-12-12
  • iOS10 widget实现3Dtouch 弹出菜单

    iOS10 widget实现3Dtouch 弹出菜单

    这篇文章主要介绍了 iOS10 widget实现3Dtouch 弹出菜单的相关资料,需要的朋友可以参考下
    2016-12-12
  • iOS 无卡顿同时使用圆角、阴影和边框的实现

    iOS 无卡顿同时使用圆角、阴影和边框的实现

    这篇文章主要介绍了iOS 无卡顿同时使用圆角、阴影和边框的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-01-01
  • iOS开发之UITableView详解

    iOS开发之UITableView详解

    在iOS开发中UITableView可以说是使用最广泛的控件,我们平时使用的软件中到处都可以看到它的影子,类似于微信、QQ、新浪微博等软件基本上随处都是UITableView。当然它的广泛使用自然离不开它强大的功能,今天这篇文章将针对UITableView重点展开讨论
    2016-04-04
  • ios开发中的容错处理示例详解

    ios开发中的容错处理示例详解

    最近发现还是有很多朋友在问类似解析时容错问题怎么解决,所以下面这篇文章主要给大家介绍了关于ios开发中的容错处理的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们一起来看看吧
    2018-05-05
  • iOS实现富文本编辑器的方法详解

    iOS实现富文本编辑器的方法详解

    大家在开发的时候经常会用到富文本编辑器,所以这篇文章就给大家整理了如何使用iOS实现富文本编辑器的方法,相信本文对大家具有一定的参考借鉴价值,有需要的朋友们可以一起来看看。
    2016-10-10
  • iOS应用开发中SQLite的初步配置指南

    iOS应用开发中SQLite的初步配置指南

    这篇文章主要介绍了iOS应用开发中SQLite的初步配置指南,SQLite是一个极轻量级可作嵌入式的数据库,非常适合入门开发者使用,需要的朋友可以参考下
    2015-12-12
  • iOS自定义时间滚动选择控件

    iOS自定义时间滚动选择控件

    这篇文章主要为大家详细介绍了iOS自定义时间滚动选择控件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-04-04

最新评论