Texture 基本控件 Node

这是 Texture 文档系列翻译,其中结合了自己的理解和工作中的使用体会。如果哪里有误,希望指出。

  1. Texture 核心概念

  2. Texture 布局 Layout

  3. Texture 便捷方法

  4. Texture 性能优化

  5. Texture 容器 Node Containers

  6. Texture 基本控件 Node

  7. Texture 中 Node 的生命周期

ASDisplayNode

Node 基础知识

ASDisplayNode是对UIViewCALayer的抽象。ASDisplayNode初始化并拥有视图的方式与UIView创建并拥有CALayer的方式一致。

ASDisplayNode *node = [[ASDisplayNode alloc] init];
node.backgroundColor = [UIColor orangeColor];
node.bounds = CGRectMake(0, 0, 100, 100);

NSLog(@"Underlying view: %@", node.view);

node 拥有UIView的所有属性。因此,使用 node 时会发现其与UIKit很像。

view 和 layer 的属性均会转发给 node,可以轻松访问。

ASDisplayNode *node = [[ASDisplayNode alloc] init];
node.clipsToBounds = YES;                 // not .masksToBounds
node.borderColor = [UIColor blueColor];  //layer name when there is no UIView equivalent

NSLog(@"Backing layer: %@", node.layer);

从上述代码可以看到,属性命名规则与UIView保持一致,除非UIView没有对应属性。与处理UIView类似,也可以操作CALayer

Node 使用position而非center,这是唯一例外。

node 用在 node container 中时,node 所有属性均会在后台线程设置,并且会用该 node 收集的缓存属性延迟构造视图、图层。开发者无需担心跳转到后台线程,因为 Texture 会自动处理这些问题,但有必要知道这些是在后台线程处理的。

封装视图

在某些情况下,希望为 node 初始化方法提供基于 view 的支持。view 通过 block 提供,这样可以延迟构造。这样创建的 node 同步绘制,因为只有封装的_ASDisplayView才可以异步绘制。

ASDisplayNode *node = [ASDisplayNode alloc] initWithViewBlock:^{
    SomeView *view  = [[SomeView alloc] init];
    return view;
}];

通过上述方法,可以将已经存在的UIView转换为ASDisplayNode

ASCellNode

ASCellNode是 Texture 中的 cell 类,可用于ASTableNodeASCollectionNodeASPagerNode

三种初始化 cell 方式

有以下三种初始化ASCellNode方式:

  • 子类化ASCellNode
  • 使用已有ASViewController初始化。
  • 使用UIViewCALayer初始化。

通常需要实现以下方法:

  • init:线程安全的初始化方法。
  • layoutSpecThatFits:返回 cell 的布局规范。
  • didLoad在主线程调用,用于添加手势、访问 view 的操作。
  • layout在主线程调用,调用 super 后布局即已完成,可以进一步调整布局。

使用ASViewControllerUIView初始化 cell 的方法在Texture 容器 Node Containers这篇文章已经讲过。

ASButtonNode

基本用途

ASButtonNode继承自ASControlNode,这一点与UIButton继承自UIControl类似。但ASButtonNode通过开启 layer back 可以大幅减少主线程工作量。

Control State

如果你用过setTitle:forControlState:,那你已经知道如何使用ASButtonNode了。ASButtonNode额外增加了一些属性,方面进行自定义设置:

[buttonNode setTitle:@"Button Title Normal" withFont:nil withColor:[UIColor blueColor] forState:ASControlStateNormal];

// 可以把 title 设置为富文本
[self.buttonNode setAttributedTitle:attributedTitle forState:ASControlStateNormal];

Target-Action 对

UIKit类似,可以添加 target-action 响应各种事件。

[buttonNode addTarget:self action:@selector(buttonPressed:) forControlEvents:ASControlNodeEventTouchUpInside];

Content Alignment

ASButtonNode提供了contentVerticalAlignmentcontentHorizontalAlignment属性,可以方便设置 button node 中的 titleLabel 和 image。

self.buttonNode.contentVerticalAlignment = ASVerticalAlignmentTop;
self.buttonNode.contentHorizontalAlignment = ASHorizontalAlignmentMiddle;

如果ASButtonNode不同状态标题长度不同,button node 会自动拉伸、压缩自身大小。这是因为设置标题时,会调用setNeedsLayout

如果想要点击 button node 后改变为选中状态,需要手动设置。

ASTextNode

ASTextNode是 Texture 的主要文本 node,用以替换UILabelASTextNode支持富文本,继承自ASControlNode。也就是当只为UIButton设置titleLabel时,也可以使用ASTextNode

基本使用

ASTextNodeUILabel类似,但ASTextNode只接受富文本。

NSDictionary *attrs = @{ NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue" size:12.0f] };
NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"Hey, here's some text." attributes:attrs];

_node = [[ASTextNode alloc] init];
_node.attributedText = string;

如上所示,想要创建ASTextNode只需 alloc、init、设置文本即可。

截断

当要显示内容所需区域大于提供的展示区域时,会尽可能多的展示内容,并进行截断。使用truncationAttributedText可以替换截断标志。

    _textNode = [[ASTextNode alloc] init];
    _textNode.attributedText = string;
    _textNode.truncationAttributedText = [[NSAttributedString alloc] initWithString:@"¶¶¶"];

显示如下:

TexturetextNodeTruncation.png

不设置truncationAttributedText时,使用默认截断字符“...“。

链接

为了将文本块设置为链接,首先需要设置linkAttributes数组,该数组的元素作为设置链接的 key。当设置文本属性时,使用上述 key 标记 URL。

_textNode.linkAttributeNames = @[ kLinkAttributeName ];

NSString *blurb = @"kittens courtesy placekitten.com \U0001F638";
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:blurb];
[string addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"HelveticaNeue-Light" size:16.0f] range:NSMakeRange(0, blurb.length)];
[string addAttributes:@{
                      kLinkAttributeName: [NSURL URLWithString:@"http://placekitten.com/"],
                      NSForegroundColorAttributeName: [UIColor grayColor],
                      NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot),
                      }
              range:[blurb rangeOfString:@"placekitten.com"]];
_textNode.attributedText = string;
_textNode.userInteractionEnabled = YES;

链接为浅灰色,且带有下划线。如下所示:

TexturekittenLink.png

ASTextNodeDelegate

遵守ASTextNodeDelegate协议可以对 text node 的事件进行响应。例如,对点击 URL 进行响应:

- (void)textNode:(ASTextNode *)richTextNode tappedLinkAttribute:(NSString *)attribute value:(NSURL *)URL atPoint:(CGPoint)point textRange:(NSRange)textRange
{
  // the link was tapped, open it
  [[UIApplication sharedApplication] openURL:URL];
}

另外,还可以对长按、高亮进行响应:

- (void)textNode:(ASTextNode *)textNode longPressedLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point textRange:(NSRange)textRange;

- (BOOL)textNode:(ASTextNode *)textNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point;

- (BOOL)textNode:(ASTextNode *)textNode shouldLongPressLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point;

ASImageNode

ASImageNode用以替换UIImageView,主要区别在于ASImageNode图片解码异步进行,另外也有更为高级的改进。例如,支持 GIF 和imageModificationBlock

基本使用

使用 image node 和使用 image view 类似:

ASImageNode *imageNode = [[ASImageNode alloc] init];

imageNode.image = [UIImage imageNamed:@"someImage"];
imageNode.contentMode = UIViewContentModeScaleAspectFill;

图像变换

通常,改变图像外观对于主线程是一项昂贵操作,因此希望将其移动到后台线程进行。

通过为imageNode赋值imageModificationBlock,可以为图片指定一组需要异步执行的操作。例如,圆角、边框、图案叠加等。

点击修改图像块 Image Modification Blocks查看详细内容。

图像裁剪

ASImageNodecontentMode设置为UIViewContentModeScaleAspectFill时,其会自动拉伸图片以占完可用空间,并裁剪掉超出区域。

默认情况下,拉伸后的图片以 image node 中心进行布局。以下图为例,猫的头部会被裁剪掉一部分:

TexturecatsMiddle.png

通过设置cropRect属性,可以偏移图片裁剪区域。rect 单位为 unit rectangle,使用图片高宽的百分比。例如,CGRectMake(0.5, 0, 0.5, 1.0)会显示右半部分图片。默认为CGRectMake(0.5, 0.5, 0.0, 0.0)。如果想使图片左对齐,可以设置cropRectx值为0.0,也就是图片的原点为(0, 0)

self.animalImageNode.cropRect = CGRectMake(0, 0, 0.0, 0.0);

宽高设置为0.0表示图片不进行拉伸。

TexturecatsFace.png

也可以设置x值为1.0,以使图像右对齐。

ASNetworkImageNode

如果要显示远程图像,可以使用ASNetworkImageNode。只需为ASNetworkImageNodeURL属性设置 URL,图像将异步加载、显示。ASNetworkImageNode继承自ASImageNode

ASNetworkImageNode *imageNode = [[ASNetworkImageNode alloc] init];
imageNode.URL = [NSURL URLWithString:@"https://raw.githubusercontent.com/wiki/pro648/tips/images/URLSessionPreview.png"];

布局 ASNetworkImageNode

ASNetworkImageNode没有固有大小,因此需要显式设置如何布局ASNetworkImageNode

布局方式一:style.perferredSize

如果希望将 image node 设置为固定大小,则可以设置style.preferredSize属性。

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constraint
{
    imageNode.style.preferredSize = CGSizeMake(100, 200);
    ...
    return finalLayoutSpec;
}
布局方式二:ASRatioLayoutSpec

设置图片高宽比例(ratio),而非固定大小进行布局:

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constraint
{
    CGFloat ratio = 3.0/1.0;
    ASRatioLayoutSpec *imageRatioSpec = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:ratio child:self.imageNode];
    ...
    return finalLayoutSpec;
}

Under the Hood

如果使用 Texture 时不包括PINRemoteImagePINCache依赖项,则将失去对渐进式图片( progressive jpeg)的支持。同时需要提供遵守ASImageCacheProtocol协议的自定义缓存策略。

渐进式图片 Progressive JPEG Support

因为 Texture 依赖于PINRemoteImage,网络图片支持加载 progressive JPEG。也就是说,如果服务器支持渐进式加载,则会先加载较低质量图片,随着其他数据到达再显示高质量图片。

想要启用渐进式加载,只需将shouldRenderProgressImages属性设置为YES即可。

networkImageNode.shouldRenderProgressImages = YES;

这里使用的是一张图片,渐进式加载。如果服务器只支持普通 JPEG 图片,但提供多个清晰度的版本,则应使用ASMultiplexImageNode

自动缓存

ASNetworkImageNode默认使用PinCache对网络图片进行缓存。

支持播放 GIF

ASNetworkImageNode通过PINRemoteImage的 beta 版PINAnimatedImage提供 GIF 支持。除非将shouldCacheImage属性设置为NO,否则将不支持播放本地 GIF。

ASVideoNode

ASVideoNode可以方便高效的播放视频。

基本使用

最简单的使用方式就是为ASVideoNode设置AVAsset

ASVideoNode *videoNode = [[ASVideoNode alloc] init];

AVAsset *asset = [AVAsset assetWithURL:[NSURL URLWithString:@"https://video-ssl.itunes.apple.com/itunes-assets/Video114/v4/3d/23/5f/3d235f2d-7f0d-716a-7d68-a1752a462d33/mzvf_8279490716204901151.640x480.h264lc.U.p.m4v"]];
videoNode.asset = asset;

自动播放 循环播放 静音

通过设置简单的BOOL值,可以实现自动播放、循环播放和静音。

如果想要视频进入可见状态时自动播放,将shouldAutoplay设置为YES即可;将shouldAutoRepeat设置为YES视频将循环播放;将muted设置为YES视频将自动静音。

视频自动、静音、循环播放设置如下:

videoNode.shouldAutoplay = YES;
videoNode.shouldAutorepeat = YES;
videoNode.muted = YES;

占位图

ASVideoNode继承自ASNetworkImageNode,可以通过URL属性添加占位图。如果不设置占位图,会自动解码视频的第一帧并设置为占位图。

ASVideoNodeDelegate

ASVideoNodeDelegate协议提供了视频状态变化。该协议内所有方法均为可选实现。如果想要在视频播放完毕时作出响应:

- (void)videoDidPlayToEnd:(ASVideoNode *)videoNode;

ASVideoNode.h文件可以查看ASVideoNodeDelegate协议内所有方法。

ASVideoPlayerNodeASVideoNode基础上添加了播放控件,ASVideoPlayerNode继承自ASDisplayNode

除上面提到的 node,Texture 还提供了ASMapNodeASControlNodeASScrollNodeASEditableTextNodeASMultiplexImageNode,这里不再继续介绍。

上一篇:Texture 容器 Node Containers

下一篇:Texture 中 Node 的生命周期

欢迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/Texture%20基本控件%20Node.md