Texture ASTableNode 实现iOS直播聊天消息界面

近几年直播一火再火,如今的直播已经再也不是主播们唱唱歌了,连老罗都已经开始直播带货,一再刷新抖音直播在线人数了。html

IMG_1885

但今天咱们不是来讲怎么作直播的,是来看看直播场景里的聊天消息界面是如何实现的。node

估计不少人要失望了😀😀bash

要实现聊天消息界面,不可不用 UITableView。当几年前我开始自学开发 iOS APP 时,我就开始使用 AsyncDisplayKit,如今已经改名为:Texture。app

Keeps the most complex iOS user interfaces smooth and responsive. Texture is an iOS framework built on top of UIKit that keeps even the most complex user interfaces smooth and responsive. It was originally built to make Facebook's Paper possible, and goes hand-in-hand with pop's physics-based animations — but it's just as powerful with UIKit Dynamics and conventional app designs. More recently, it was used to power Pinterest's app rewrite.less

As the framework has grown, many features have been added that can save developers tons of time by eliminating common boilerplate style structures common in modern iOS apps. If you've ever dealt with cell reuse bugs, tried to performantly preload data for a page or scroll style interface or even just tried to keep your app from dropping too many frames you can benefit from integrating Texture.函数

参考Texture 官网布局

之后把每次用到的 Nodes 心得写出来,今天来讲一说使用 ASTableNode测试

初始化 ASTableNode

建立 ASTableNodeUITableView 同样,比较简单。flex

@interface TestViewController () <ASTableDataSource, ASTableDelegate>

@property (nonatomic, strong) ASTableNode *tableNode;

@property (nonatomic, strong) NSMutableArray *dataSource;

@end
复制代码

初始化:ui

_tableNode = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
    
_tableNode.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

_tableNode.backgroundColor = [UIColor.clearColor colorWithAlphaComponent:0.0];

_tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone;

[self.view addSubnode:_tableNode];

_tableNode.frame = CGRectMake(0, self.view.bounds.size.height - 300, 300, 200);

// 填充测试数据
_dataSource = [NSMutableArray arrayWithArray:@[
        @{@"type": @"TEXT", @"text": @"你好", @"nickname": @"yemeishu"},
        @{@"type": @"TEXT", @"text": @"你好,这个主播不错哦~", @"nickname": @"yemeishu"},
        @{@"type": @"TEXT", @"text": @"如今直播还能够带货了", @"nickname": @"yemeishu"}
]];

_tableNode.delegate = self;
_tableNode.dataSource = self;
_tableNode.view.allowsSelection = NO;
复制代码

UITableView 同样,实现 dataSorcedelegate (这里暂时不写对 Node 操做):

#pragma mark - ASTableNode
- (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary *message = self.dataSource[(NSUInteger) indexPath.row];
    return ^{
        return [[MessageNode alloc] initWithMessage: message];
    };
}

- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section
{
    return self.dataSource.count;
}
复制代码

如今能够建立 ASCellNode 子类。

编写 ASCellNode 子类

ASCellNode, as you may have guessed, is the cell class of Texture. Unlike the various cells in UIKit, ASCellNode can be used with ASTableNodes, ASCollectionNodes and ASPagerNodes, making it incredibly flexible.

ASCellNode 核心函数主要有四个,咱们的重点在 layoutSpecThatFits 上。

  • -init – Thread safe initialization.

  • -layoutSpecThatFits: – Return a layout spec that defines the layout of your cell.

  • -didLoad – Called on the main thread. Good place to add gesture recognizers, etc.

  • -layout – Also called on the main thread. Layout is complete after the call to super which means you can do any extra tweaking you need to do.

具体 MessageNode 类直接看代码,只要将每一个人聊天的信息发给 MessageNode 填充内容:

@interface MessageNode : ASCellNode
- (instancetype)initWithMessage:(NSDictionary *)message;
@end
复制代码

这里为了简单实现效果,只是显示消息者姓名和消息内容。

#import "MessageNode.h"

@interface ZJMessageNode()
@property (strong, nonatomic) ASButtonNode *textNode;
@end

@implementation MessageNode {

}
- (instancetype)initWithMessage:(NSDictionary *)message {
    self = [super init];
    if (self) {
        _textNode = [[ASButtonNode alloc] init];
        NSString* nickname = @"";
        NSString* text = @"";
        if ([message[@"type"] isEqual: @"TEXT"]) {
            nickname = [NSString stringWithFormat:@"[%@]:",message[@"nickname"]];
            text = message[@"text"];
        } else {
            nickname = @"其余人";
            text = @"其余消息";
        }
        NSMutableAttributedString* string = [[NSMutableAttributedString alloc] initWithString:@""];

        NSAttributedString* nameString = [[NSAttributedString alloc] initWithString:nickname attributes:@{
                NSFontAttributeName : [UIFont systemFontOfSize:14.0],
                NSForegroundColorAttributeName: UIColorMakeWithHex(@"#FF9900")
        }];

        NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
        paragraphStyle.lineSpacing = 5.0;
        NSAttributedString* textString = [[NSAttributedString alloc] initWithString: text attributes:@{
                NSFontAttributeName : [UIFont systemFontOfSize:14.0],
                NSForegroundColorAttributeName: UIColor.ZJ_tintColor,
                NSParagraphStyleAttributeName: paragraphStyle
        }];
        [string appendAttributedString:nameString];
        [string appendAttributedString:textString];
        _textNode.titleNode.attributedText = string;
        _textNode.titleNode.maximumNumberOfLines = 3;

        _textNode.backgroundImageNode.image = [UIImage as_resizableRoundedImageWithCornerRadius:8
                                                                                    cornerColor:UIColor.clearColor
                                                                                      fillColor: [UIColor colorWithRed:26/255.0 green:26/255.0 blue:26/255.0 alpha:0.5]];
        [self addSubnode:_textNode];
    }

    return self;
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    [_textNode.titleNode setTextContainerInset:UIEdgeInsetsMake(9, 14.5, 9, 8.5)];
    ASAbsoluteLayoutSpec *absoluteSpec = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[_textNode]];
    // ASAbsoluteLayoutSpec's .sizing property recreates the behavior of ASDK Layout API 1.0's "ASStaticLayoutSpec"
    absoluteSpec.sizing = ASAbsoluteLayoutSpecSizingSizeToFit;

    return [ASInsetLayoutSpec
            insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10)
                                child:absoluteSpec];
}
@end
复制代码

好了,让咱们运行下 Demo,看看效果:

是否是和抖音上的聊天界面效果差很少:

IMG_1877

解析 MessageNode

  • ASButtonNode

Demo 中主要用一个 ASButtonNode 主要有这几点考虑。

  1. 参考不少直播聊天消息显示界面,每一个聊天体主要以 Text 文本为主,并且把关键的信息用不一样的颜色和大小作区分,因此用 NSMutableAttributedString 比较合适。因此在 MessageNode 中主要以 ASTextNode 为主。在本文中,为了演示,主要拿昵称和消息内容,用函数 appendAttributedString 拼接在一块儿。
  2. 要为 ASTextNode 添加一个半透明、圆角的「背景」层,因此须要增长一个 ASImageNode
  3. 若是对于复杂的消息,须要在姓名以前增长一个相似 VIP 等级图标等,这也就有可能还须要一个 ASImageNode

因此要能知足以上三点要求,最好的 Node 就是 ASButtonNode

若是咱们直接在 MessageNode 放三个元素 (一个 ASTextNode,两个 ASImageNode) 也能知足须要,但元素间的布局和定位就很差设计了,无形增长代码量和难度。

  • ASAbsoluteLayoutSpec

因为使用了 ASTableNode,对每个消息体的最大宽度默认都和 ASTableNode 同样。因此在布局时,若是咱们采用其余的 ASLayoutSpec 的布局方式,呈现的结果就很难像直播窗口那样了,可以实时根据文本的长度显示,不至于每一个消息体都是等宽的,很差看。

因此本文推荐使用 ASAbsoluteLayoutSpec

Within ASAbsoluteLayoutSpec you can specify exact locations (x/y coordinates) of its children by setting their layoutPosition property. Absolute layouts are less flexible and harder to maintain than other types of layouts.

ASAbsoluteLayoutSpec has one property:

sizing. Determines how much space the absolute spec will take up. Options include: Default, and Size to Fit. Note that the Size to Fit option will replicate the behavior of the old ASStaticLayoutSpec.

这里我设定 sizing 为:

absoluteSpec.sizing = ASAbsoluteLayoutSpecSizingSizeToFit;
复制代码

最后就是给各个消息体设定一个 EdgeInsets,分开点,省得每一个消息体都是挨着的:

[ASInsetLayoutSpec
            insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10)
                                child:absoluteSpec];
复制代码

参考: Layout Specs

总结

这是一篇简单使用 ASTableNode 记录。固然还有不少 ASTableNode 属性和方法都没介绍和使用,在接下来的编码过程当中再分享出来,还有包括各类各样的 Layout 布局的使用。

敬请关注!

相关文章
相关标签/搜索