直播間搭建實作iOS直播聊天消息界面
近幾年直播一火再火,現在的直播已經不再是主播們唱唱歌了,連老羅都已經開始直播帶貨,一再重新整理抖音直播線上人數了。

估計很多人要失望了????????
要實作聊天消息界面,不可不用 UITableView。當幾年前我開始自學開發 iOS APP 時,我就開始使用 AsyncDisplayKit,現在已經更名為:Texture。
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.
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
建立
ASTableNode
和
UITableView
一樣,比較簡單。
@interface TestViewController () <ASTableDataSource, ASTableDelegate>
@property (nonatomic, strong) ASTableNode *tableNode;
@property (nonatomic, strong) NSMutableArray *dataSource;
@end
複制代碼
初始化:
_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
一樣,實作
dataSorce
delegate
(這裡暫時不寫對 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,看看效果:
是不是和抖音上的聊天界面效果差不多:
解析 MessageNode
- ASButtonNode
Demo 中主要用一個
ASButtonNode
主要有這幾點考慮。
- 參考很多直播聊天消息顯示界面,每個聊天體主要以 Text 文本為主,而且把關鍵的資訊用不同的顔色和大小做區分,是以用
比較合适。是以在NSMutableAttributedString
中主要以MessageNode
為主。在本文中,為了示範,主要拿昵稱和消息内容,用函數ASTextNode
拼接在一起。appendAttributedString
- 要為
添加一個半透明、圓角的「背景」層,是以需要增加一個ASTextNode
ASImageNode
- 如果對于複雜的消息,需要在姓名之前增加一個類似 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.
absoluteSpec.sizing = ASAbsoluteLayoutSpecSizingSizeToFit;
複制代碼
[ASInsetLayoutSpec
insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10)
child:absoluteSpec];
複制代碼