簡單的聊天界面

在近期的一個項目中,用到了客服功能,然而聊天的實現并不是用的即時通訊的SDK,而是用的一個網頁版的某通,這就需要自己寫一個聊天的界面,下面總結一下這個聊天界面的搭建過程。

一、基礎UI

談到聊天界面,我們最熟悉的就是QQ、微信等的聊天界面,看到這兩種的聊天界面的形式,都是將消息逐條展示出來,我們不難想到,聊天界面的主體要用UItableView實現,而每條消息就是一個cell,至于cell中的內容,無非就是三項:時間、對話人的頭像、消息的內容。既然大概結構已經清楚,那么下面我們倆看看具體的實現。

1、數據的處理

在自定義cell之前,我們首先來看看消息的數據結構,將數據轉換成Model,以便對數據進行更方便的處理。

Snip20170323_3.png

數據看起來非常簡單,其意義分別為:text-消息內容;time-消息的發送時間;type-消息的類型(0:自己發送的消息,1:對方發送的消息)。

那么我們在定制Model的時候是不是就只需要這幾種屬性呢?答案是NO。因為我們知道,聊天的內容不是只有文本內容,有時候還會有圖片,表情,語音,設置還會有文件,那么我們就還需要一個區分消息種類的屬性messageType。此外我們知道,QQ聊天界面中,并不是所有的消息發送時間都會顯示的,只有當新消息與上一條消息的發送時間不同時才會顯示時間,那么我們就還需要一個區別時間是否展示的屬性hiddenTime。

下面是消息模型XTMessage的屬性:

@property (nonatomic, copy) NSString *time;//對話類型:0:自己的消息;1:對方的消息
@property (nonatomic, strong) NSNumber *type;//消息類型:0:文字消息;1:圖片;2:文件
@property(nonatomic,assign)int messageType;
@property(nonatomic,copy)NSString *message;//文本消息
@property(nonatomic,strong)UIImage *messageImage;//圖片消息
@property(nonatomic,strong)NSData *messageData;//文件消息
@property (nonatomic, assign, getter = isHiddenTime) BOOL hiddenTime;//是否隱藏時間

下面是字典轉模型的具體實現方法:

- (instancetype)initWithDictionary:(NSDictionary *)dict
{
    self = [super init];
    if (self) {
        _type = dict[@"type"];
        _time = dict[@"time"];
        if (_messageType == 0) {//文本消息
            _message = dict[@"text"];
        } else if (_messageType == 1) {//圖片消息
            _messageImage = [UIImage imageNamed:dict[@"text"]];
        } else {//文件或語音消息
            _messageData = dict[@"text"];
        }
    }
    return self;
}

2、FrameModel的處理

上面我們將原始數據處理完了,接下來我們就要為自定義cell做準備了。對于cell中的控件無非就是要顯示頭像的一個UIimageView,一個顯示時間的label,然后就是展示消息內容的,而展示內容我們這里選擇的是UIbutton,因為UIbutton既可以展示文本,又能展示圖片,能最大限度滿足我們的需求。這里我們要注意了,首先對于消息發送者的不同,頭像的位置是不同的;消息內容的不同,展示內容控件的size也是動態變化的。下面我們說說對此的處理。

首先我們聲明一下XTMessageFrame的公開屬性:

@property (nonatomic, assign, readonly) CGRect titleLabelFrame;//時間標題的高度
@property (nonatomic, assign, readonly) CGRect contentBtnFrame;
@property (nonatomic, assign, readonly) CGRect iconImageViewFrame;
@property (nonatomic, assign) CGFloat cellHeight;//行高
@property(nonatomic,strong)XTMessage *messageModel;

下面我們來實現XTMessageFrame的私有方法。首先我們先定義一個- (CGSize)sizeWithText:(NSString *)text;根據文本長度自適應大小,代碼如下:

- (CGSize)sizeWithText:(NSString *)text {
    return [text boundingRectWithSize:CGSizeMake(SCREEN_WIDTH - W(150), MAXFLOAT)options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : FONTONE} context:nil].size;
}

下面我們來實現MessageModel的set方法:
首先判斷時間label的高度:

//設置時間標題label的高度 如果時間隱藏 高度為0 即不顯示
CGFloat titleLabelHeight = messageModel.hiddenTime ? 0 : H(30);

時間label的frame:

_titleLabelFrame = CGRectMake(0, 0, SCREEN_WIDTH, titleLabelHeight);

下面我們定義兩個宏:

#define kContentBtnWidth (contentBtnSize.width + W(40))//內容的寬度
#define kContentBtnHeight (contentBtnSize.height + H(35))//內容的高度

下面根據消息的類型,得出內容按鈕的size:

CGSize contentBtnSize;

if (_messageModel.messageType == 0) {

    //文字大小
    contentBtnSize = [self sizeWithText:_messageModel.message];

} else if (_messageModel.messageType == 1) {

    CGSize size = _messageModel.messageImage.size;
    contentBtnSize = CGSizeMake(W(150), size.height/size.width * W(150));

} else {

    contentBtnSize = CGSizeMake(W(150), H(150));

}

接下來根據發送消息的對象,設置頭像和內容button的frame:

//頭像大小 50 * 50
if ([_messageModel.type isEqualToNumber:@0]) {//如果是自己發送的消息

    _iconImageViewFrame = CGRectMake(SCREEN_WIDTH - iconWH - margin, CGRectGetMaxY(_titleLabelFrame), iconWH, iconWH);//頭像在左側
    _contentBtnFrame = CGRectMake(SCREEN_WIDTH - kContentBtnWidth - iconWH - margin - margin, CGRectGetMaxY(_titleLabelFrame), kContentBtnWidth, kContentBtnHeight);//設置內容button

}else {//如果是對方發送的消息

    _iconImageViewFrame = CGRectMake(margin, CGRectGetMaxY(_titleLabelFrame), iconWH, iconWH);//頭像在右側
    _contentBtnFrame = CGRectMake(margin + iconWH + margin,CGRectGetMaxY(_titleLabelFrame),kContentBtnWidth,kContentBtnHeight);

}

最后根據cell中控件的frame設置cell的行高:

_cellHeight = MAX(CGRectGetMaxY(_contentBtnFrame), CGRectGetMaxY(_titleLabelFrame)) + margin;

3、自定義cell

首先重寫initWithStyle方法,在cell中初始化timeTitleLab(時間label)、headImageView(頭像imageView)、contentBtn(內容按鈕),并進行一些初始設置。

實現setMessageFrame方法:

- (void)setMessageFrame:(XTMessageFrame *)messageFrame
{

    _messageFrame = messageFrame;
    [self setSubViewData];
    [self setSubViewFrame];

}

setSubViewFrame方法,就是給cell中控件的frame賦值,這個方法相信就不用多說了,我們來說說setSubViewData方法,在這個方法中,我們要做的就是根據消息發送人,以及消息類型,分別給控件的內容賦值,比如當消息是自己發送的時候,代碼如下:

self.headImageView.image = [UIImage imageNamed:@"me"];

UIImage *btnBackImage = [UIImage imageNamed:@"chat_send_nor"];

[self.contentBtn setBackgroundImage:[btnBackImage stretchableImageWithLeftCapWidth:btnBackImage.size.width / 2 topCapHeight:btnBackImage.size.height / 2] forState:UIControlStateNormal];

UIImage *btnBackImageHelight = [UIImage imageNamed:@"chat_send_press_pic"];

[self.contentBtn setBackgroundImage:[btnBackImageHelight stretchableImageWithLeftCapWidth:btnBackImageHelight.size.width / 2 topCapHeight:btnBackImageHelight.size.height / 2] forState:UIControlStateHighlighted];

if (self.messageFrame.messageModel.messageType == 0) {//如果是文本信息

    [self.contentBtn setTitle:self.messageFrame.messageModel.message forState:UIControlStateNormal];

} else if (self.messageFrame.messageModel.messageType == 1) {

    [self.contentBtn setImage:self.messageFrame.messageModel.messageImage forState:UIControlStateNormal];
    [self.contentBtn setImageEdgeInsets:UIEdgeInsetsMake(H(20), W(20), H(20), W(20))];

} else {

    [self.contentBtn setImage:[UIImage imageNamed:@"wenj"] forState:UIControlStateNormal];

}

這里要強調一點,對于聊天內容button的backgroundImage,需要根據內容的大小進行大小的拉伸,這里我們用到的是- (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight方法,這個函數是UIImage的一個實例函數,它的功能是創建一個內容可拉伸,而邊角不拉伸的圖片,需要兩個參數,第一個是左邊不拉伸區域的寬度,第二個參數是上面不拉伸的高度。我自己的理解(認識)是在左邊、上面找一個基點,對基點范圍以外的內容拉伸。

具體的代碼已上傳GitHub:XTChatViewController

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 2017.02.22 可以練習,每當這個時候,腦袋就犯困,我這腦袋真是神奇呀,一說讓你做事情,你就犯困,你可不要太...
    Carden閱讀 1,378評論 0 1
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,381評論 25 708
  • 今天午飯后,散步,看到小鎮上一家人的門口掛著藍染(あいぞめ)的布上寫的【I hope peace】這也許只是單單的...
    WoodSage閱讀 670評論 0 0
  • 我們都是幸運兒 上帝,關了一扇門,總會打開一扇窗的。 人生難免有挫折,但也有快樂的存在。在忙忙碌碌的一天中,我們經...
    玖珞神閱讀 223評論 0 1
  • 感賞自己開啟新的生活模式,以后早睡早起,晚上10:30之前休息,早上5點起床,學習鍛煉。希望自己可以堅持下去,并有...
    完美蛻變閱讀 218評論 0 1