Permalink
Find file
b7a78e5 Nov 29, 2016
@xiangwangfeng @chrisray108
278 lines (212 sloc) 8.76 KB

自定义消息

前言

虽然云信 SDK 内置了包括 图片音频视频 等多媒体消息,但有时候总归不能很好满足用户的需求,用户基于自己的应用场景往往会需要提供特殊的消息类型,在这一篇文章里面,将为你介绍如何从 model 到 UI,一步步实现自定义消息。

自定义消息模型

在云信 SDK 中,我们会需要使用 NIMCustomObject 来实现自定义消息类型,内部包含一个实现 NIMCustomAttachment 协议的对象,开发者需要提供一个实现 NIMCustomAttachment 协议的对象来完成自定义消息的发送和收发。

创建协议对象

创建一个实现 NIMCustomAttachment 协议的对象

@interface Attachment : NSObject<NIMCustomAttachment>

//简单新建一个有标题和副标题的自定义消息

@property (nonatomic,copy) NSString *title;

@property (nonatomic,copy) NSString *subTitle;

@end

实现 NIMCustomAttachment 协议

当我们拥有一个消息对象后,我们需要将这个对象发送到对端。这里的问题是:网络层我们传输的是流式数据,我们怎么样才能将一个 id<NIMCustomAttachment> 转换为数据流。这需要我们的 id<NIMCustomAttachment> 提供序列化它自己的方法,而这个方法已经定义在 NIMCustomAttachment 中。即:

- (NSString *)encodeAttachment;

通过实现这个方法,最终将 id<NIMCustomAttachmetn> 转换为数据流,并由云信 SDK 进行投递。在实际场景下,一条自定义消息往往会附带多媒体信息,如图片,音频等,同样 NIMCustomAttachment 也提供了相应的接口,开发只需要实现相应接口,所有的上传下载操作都可以由云信 SDK 完成。

上传

#pragma mark - 上传相关接口
/**
 *  是否需要上传附件
 *
 *  @return 是否需要上传附件
 */
- (BOOL)attachmentNeedsUpload;

/**
 *  需要上传的附件路径
 *
 *  @return 路径
 */
- (NSString *)attachmentPathForUploading;

/**
 *  更新附件URL
 *
 *  @param urlString 附件url
 */
- (void)updateAttachmentURL:(NSString *)urlString;

下载

#pragma mark - 下载相关接口
/**
 *  是否需要下载附件
 *
 *  @return 是否需要上传附件
 */
- (BOOL)attachmentNeedsDownload;

/**
 *  需要下载的附件url
 *
 *  @return 附件url
 */
- (NSString *)attachmentURLStringForDownloading;

/**
 *  需要下载的附件本地路径
 *
 *  @return 附件本地路径
 *  @discussion 上层需要保证路径的
 */
- (NSString *)attachmentPathForDownloading;

发送自定义消息

自定义消息的发送和其他消息类型并没有任何不同,直接调用 NIMChatManager 的发送接口即可。

接收自定义消息

和发送自定义消息不同,接收自定义消息需要上层提供额外的支持。在构造消息时,我们提到上层需要提供将自定义消息转换二进制流的协议实现,而同样的,当收到一条自定义消息时,SDK 并不清楚如何将这一串二进制流转换为对应的对象模型,需要上层提供。

在云信 SDK 中,我们使用 NIMCustomAttachmentCoding 协议支持自定义消息的反序列化。开发者需要实现对应的方法

@interface AttachmentDecoder : NSObject<NIMCustomAttachmentCoding>
@end
@implementation AttachmentDecoder

- (id<NIMCustomAttachment>)decodeAttachment:(NSString *)content{
    //所有的自定义消息都会走这个解码方法,如有多种自定义消息请自行做好类型判断和版本兼容。这里仅演示最简单的情况。
    id<NIMCustomAttachment> attachment;
    NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
    if (data) {
        NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        if ([dict isKindOfClass:[NSDictionary class]]) {
            NSString *title = dict[@"title"];
            NSString *subTitle = dict[@"subTitle"];
            Attachment *myAttachment = [[Attachment alloc] init];
            myAttachment.title = title;
            myAttachment.subTitle = subTitle;
            attachment = myAttachment;
        }
    }
    return attachment;
}

@end

并在 - (BOOL)application: didFinishLaunchingWithOptions: 中注入

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
[NIMCustomObject registerCustomDecoder:[[AttachmentDecoder alloc]init]];
... 
}

自定义消息界面

新建气泡内容

气泡内容类需要继承 NIMSessionMessageContentView,并使用 - (instancetype)initSessionMessageContentView 作为初始化方法。内容里根据业务需求自行排版。

示例内容:

@interface ContentView : NIMSessionMessageContentView

@property (nonatomic,strong) UILabel *titleLabel;

@property (nonatomic,strong) UILabel *subTitleLabel;

@end
@implementation ContentView

- (instancetype)initSessionMessageContentView{
    self = [super initSessionMessageContentView];
    if (self) {
        _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        _titleLabel.font = [UIFont systemFontOfSize:13.f];
        _subTitleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        _subTitleLabel.font = [UIFont systemFontOfSize:12.f];
        [self addSubview:_titleLabel];
        [self addSubview:_subTitleLabel];
    }
    return self;
}

- (void)refresh:(NIMMessageModel*)data{
    //务必调用super方法
    [super refresh:data];

    NIMCustomObject *object = data.message.messageObject;
    Attachment *attachment = object.attachment;

    self.titleLabel.text = attachment.title;
    self.subTitleLabel.text = attachment.subTitle;

    if (!self.model.message.isOutgoingMsg) {
        self.titleLabel.textColor = [UIColor blackColor];
        self.subTitleLabel.textColor = [UIColor blackColor];
    }else{
        self.titleLabel.textColor = [UIColor whiteColor];
        self.subTitleLabel.textColor = [UIColor whiteColor];
    }

    [_titleLabel sizeToFit];
    [_subTitleLabel sizeToFit];
}
- (void)layoutSubviews{
    [super layoutSubviews];
    CGFloat titleOriginX = 10.f;
    CGFloat titleOriginY = 10.f;
    CGFloat subTitleOriginX = (self.frame.size.width  - self.subTitleLabel.frame.size.width) / 2;
    CGFloat subTitleOriginY = self.frame.size.height  - self.subTitleLabel.frame.size.height - 10.f;

    CGRect frame = self.titleLabel.frame;
    frame.origin = CGPointMake(titleOriginX, titleOriginY);
    self.titleLabel.frame = frame;

    frame = self.subTitleLabel.frame;
    frame.origin = CGPointMake(subTitleOriginX, subTitleOriginY);
    self.subTitleLabel.frame = frame;
}

@end

2.新建自定义消息气泡布局配置,配置需要实现 NIMCellLayoutConfig 协议。这里除了自定义消息歪,其他消息沿用内置配置,所以配置类继承基类 NIMCellLayoutConfig

@interface CellLayoutConfig : NIMCellLayoutConfig<NIMCellLayoutConfig>
@end
@implementation CellLayoutConfig

- (CGSize)contentSize:(NIMMessageModel *)model cellWidth:(CGFloat)width{
    //填入内容大小
    if ([self isSupportedCustomModel:model]) {
       //先判断是否是需要处理的自定义消息
       return CGSizeMake(200, 50);
    }
    //如果不是自己定义的消息,就走内置处理流程
    return [super contentSize:model
                    cellWidth:width];
}

- (NSString *)cellContent:(NIMMessageModel *)model{
    //填入contentView类型
    if ([self isSupportedCustomModel:model]) {
       //先判断是否是需要处理的自定义消息
       return @"ContentView";
    }
    //如果不是自己定义的消息,就走内置处理流程
    return [super cellContent:model];
}

- (UIEdgeInsets)cellInsets:(NIMMessageModel *)model{
    //填入气泡距cell的边距,选填
    if ([self isSupportedCustomModel:model]) {
       //先判断是否是需要处理的自定义消息
       return UIEdgeInsetsMake(5, 5, 5, 5);
    }
    //如果不是自己定义的消息,就走内置处理流程
    return [super cellInsets:model];
}


- (UIEdgeInsets)contentViewInsets:(NIMMessageModel *)model{
    //填入内容距气泡的边距,选填
    if ([self isSupportedCustomModel:model]) {
       //先判断是否是需要处理的自定义消息
       return UIEdgeInsetsMake(5, 5, 5, 5);
    }
    //如果不是自己定义的消息,就走内置处理流程
    return [super contentViewInsets:model];
}
@end

3.将创建好的布局配置类注入到组件中,保证在会话页实例化之前注入即可。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   ...

    //注册 NIMKit 自定义排版配置
    [[NIMKit sharedKit] registerLayoutConfig:[CellLayoutConfig class]];

   ...
- }