天天看點

直播間搭建實作iOS直播聊天消息界面

直播間搭建實作iOS直播聊天消息界面

近幾年直播一火再火,現在的直播已經不再是主播們唱唱歌了,連老羅都已經開始直播帶貨,一再重新整理抖音直播線上人數了。

直播間搭建實作iOS直播聊天消息界面
但今天我們不是來說怎麼做直播的,是來看看直播場景裡的聊天消息界面是如何實作的。
直播間搭建實作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,看看效果:

直播間搭建實作iOS直播聊天消息界面

是不是和抖音上的聊天界面效果差不多:

直播間搭建實作iOS直播聊天消息界面

解析 MessageNode

  • ASButtonNode

Demo 中主要用一個

ASButtonNode

主要有這幾點考慮。

  1. 參考很多直播聊天消息顯示界面,每個聊天體主要以 Text 文本為主,而且把關鍵的資訊用不同的顔色和大小做區分,是以用

    NSMutableAttributedString

    比較合适。是以在

    MessageNode

    中主要以

    ASTextNode

    為主。在本文中,為了示範,主要拿昵稱和消息内容,用函數

    appendAttributedString

    拼接在一起。
  2. 要為

    ASTextNode

    添加一個半透明、圓角的「背景」層,是以需要增加一個

    ASImageNode

  3. 如果對于複雜的消息,需要在姓名之前增加一個類似 VIP 等級圖示等,這也就有可能還需要一個

    ASImageNode

是以要能滿足以上三點要求,最好的 Node 就是

ASButtonNode

直播間搭建實作iOS直播聊天消息界面

如果我們直接在

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];
複制代碼