Habber - IOS XMPP 客户端 教程 (四)聊天界面制作

chat


Hello world!

终于到了聊天界面了,虽然是自己做的,但是写博客把制作过程重新回顾一下也是比较累脑……

OK,进入正题!

由于用的是UUChatTableView,改了些东西,xib和storyboard差不多但是还是不一样,从auto layout的计算上就不同、

因为UUChat上添加了群聊功能,我的app并没有这个功能,所以,删!

治标先治本,让我们来看一下ChatModel
ChatModel.h

#import <Foundation/Foundation.h>

@interface ChatModel : NSObject

@property (nonatomic, strong) NSMutableArray *dataSource;

- (void)loadDataSource;

- (void)addSpecifiedItem:(NSDictionary *)dic;

@end

ChatModel.m

#import "ChatModel.h"

#import "UUMessage.h"
#import "UUMessageFrame.h"

@implementation ChatModel

static NSString *previousTime = nil;

//初始化信息数组
- (void)loadDataSource {
    self.dataSource = [NSMutableArray array];
}

// 添加自己的cell格式
- (void)addSpecifiedItem:(NSDictionary *)dic
{
    UUMessageFrame *messageFrame = [[UUMessageFrame alloc]init];
    UUMessage *message = [[UUMessage alloc] init];
    NSMutableDictionary *dataDic = [NSMutableDictionary dictionaryWithDictionary:dic];

    if ([[dic objectForKey:@"sender"] isEqualToString:@"Me"]) {
        [dataDic setObject:@(UUMessageFromMe) forKey:@"from"];
    } else {
        [dataDic setObject:@(UUMessageFromOther) forKey:@"from"];
    }
    [dataDic setObject:[[NSDate date] description] forKey:@"strTime"];
    if ([[dic objectForKey:@"sender"] isEqualToString:@"Me"]) {
        [dataDic setObject:@"Me" forKey:@"strName"];
    } else {
        [dataDic setObject:@"He" forKey:@"strName"];
    }
    [dataDic setObject:@"" forKey:@"strIcon"];

    [message setWithDict:dataDic];
    [message minuteOffSetStart:previousTime end:dataDic[@"strTime"]];
    messageFrame.showTime = message.showDateLabel;
    [messageFrame setMessage:message];

    if (message.showDateLabel) {
        previousTime = dataDic[@"strTime"];
    }
    [self.dataSource addObject:messageFrame];
}


@end

Model看上去删了一大堆东西只剩下了这两个接口,
这里要注意的是区分对方发送的消息和我发送的消息。

这里你应该注意到了,图片上对方的人名用“He”来替代,和这里的设置有关,
本来应该把对方的名字一起传过来的,偷了个懒、、
其实很简单,我都已经把人命传递过来了,只要在这里设置个接口传输过来就好了~

聊天界面

用代码画的界面好就好在容易修改~
不过我还是喜欢用IB,因为我懒。。。
UUChatViewController.h

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

@interface UUChatViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, HabberMessageDelegate>

//接收传递过来的人名
@property (strong, nonatomic) NSString *chatUserName;

//接收好友界面的消息
@property (strong, nonatomic) NSMutableArray *messages;

//返回按钮
- (IBAction)back:(UIBarButtonItem *)sender;

@end

UUChatViewController.m

//
//  RootViewController.m
//  UUChatTableView
//
//  Created by shake on 15/1/4.
//  Copyright (c) 2015年 uyiuyao. All rights reserved.
//

#import "UUChatViewController.h"
#import "UUInputFunctionView.h"
#import "MJRefresh.h"
#import "UUMessageCell.h"
#import "ChatModel.h"
#import "UUMessageFrame.h"
#import "UUMessage.h"

@interface UUChatViewController () <UUInputFunctionViewDelegate,UUMessageCellDelegate,UITableViewDataSource,UITableViewDelegate> {
    AppDelegate *mainDelegate;
}

@property (strong, nonatomic) ChatModel *chatModel;

@property (weak, nonatomic) IBOutlet UITableView *chatTableView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint;

@end

@implementation UUChatViewController{
    UUInputFunctionView *IFView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = _chatUserName;

    //设置tableView透明
    _chatTableView.backgroundView = nil;
    _chatTableView.backgroundColor = [UIColor clearColor];
    _chatTableView.opaque = NO;
    //初始化聊天数组并取得代理
    [self loadBaseViewsAndData];
    mainDelegate = [self getAppDelegate];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    mainDelegate.messageDelegate = self;

    //add notification
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyboardChange:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyboardChange:) name:UIKeyboardWillHideNotification object:nil];
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(tableViewScrollToBottom) name:UIKeyboardDidShowNotification object:nil];

    //把朋友界面传送过来的消息读取显示
    NSLog(@"%lu", (unsigned long)_messages.count);
    for (NSDictionary *dic in _messages) {
        NSLog(@"%@", dic);
        [self dealTheFunctionData:dic];
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter]removeObserver:self];
}

#pragma mark - Get appDelegate for xmppStream
//获取xmppStream
- (AppDelegate *)getAppDelegate {
    return (AppDelegate *)[[UIApplication sharedApplication] delegate];
}

- (XMPPStream *)xmppStream {
    return [[self getAppDelegate] xmppStream];
}

#pragma mark - 作者画的界面
- (void)loadBaseViewsAndData
{
    self.chatModel = [[ChatModel alloc]init];
    [_chatModel loadDataSource];

    IFView = [[UUInputFunctionView alloc]initWithSuperVC:self];
    IFView.delegate = self;
    [self.view addSubview:IFView];

    [self.chatTableView reloadData];
    [self tableViewScrollToBottom];
}

-(void)keyboardChange:(NSNotification *)notification
{
    NSDictionary *userInfo = [notification userInfo];
    NSTimeInterval animationDuration;
    UIViewAnimationCurve animationCurve;
    CGRect keyboardEndFrame;

    [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
    [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
    [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];

    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:animationDuration];
    [UIView setAnimationCurve:animationCurve];

    //adjust ChatTableView's height
    //由于原作是xib,storyboard已经不一样,这里改了一下布局size计算
    if (notification.name == UIKeyboardWillShowNotification) {
        self.bottomConstraint.constant -= keyboardEndFrame.size.height + 40;
    }else{
        self.bottomConstraint.constant = - 40;
    }

    [self.view layoutIfNeeded];

    //adjust UUInputFunctionView's originPoint
    CGRect newFrame = IFView.frame;
    newFrame.origin.y = keyboardEndFrame.origin.y - newFrame.size.height;
    IFView.frame = newFrame;

    [UIView commitAnimations];

}

//tableView Scroll to bottom
- (void)tableViewScrollToBottom
{
    if (self.chatModel.dataSource.count==0)
        return;

    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.chatModel.dataSource.count-1 inSection:0];
    [self.chatTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}

#pragma mark - HabberMessageDelegate implements
//好了,这边接收到数据进行判断种类并传递
- (void)newMessageReceived:(NSDictionary *)messageContent {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSDictionary *dic = [NSDictionary dictionary];
        NSString *msg = [messageContent objectForKey:@"msg"];
        NSString *imageStr = [messageContent objectForKey:@"photo"];
        NSString *voiceStr = [messageContent objectForKey:@"voice"];
        NSString *voiceTimeStr = [messageContent objectForKey:@"voiceTime"];
        NSString *from = [messageContent objectForKey:@"sender"];
        if (imageStr.length > 0) {
            NSData *imgData = [[NSData alloc] initWithBase64EncodedString:imageStr options:0];
            UIImage *image = [UIImage imageWithData:imgData];
            dic = @{@"picture": image,
                    @"type": @(UUMessageTypePicture),
                    @"sender": from};
        } else if (voiceStr.length > 0) {
            NSData *voiceData = [[NSData alloc] initWithBase64EncodedString:voiceStr options:0];
            dic = @{@"voice": voiceData,
                    @"strVoiceTime": voiceTimeStr,
                    @"type": @(UUMessageTypeVoice),
                    @"sender": from};
        } else {
            dic = @{@"strContent": msg,
                    @"type": @(UUMessageTypeText),
                    @"sender": from};
        }

        [self dealTheFunctionData:dic];
    });
}

#pragma mark - InputFunctionViewDelegate
//这里作者给我们的接口省了好多事、注意真机调试的时候不如模拟器速度要快
- (void)UUInputFunctionView:(UUInputFunctionView *)funcView sendMessage:(NSString *)message
{
    NSDictionary *dic = @{@"strContent": message,
                          @"type": @(UUMessageTypeText),
                          @"sender": @"Me"};
    funcView.TextViewInput.text = @"";
    [funcView changeSendBtnWithPhoto:YES];
    [self dealTheFunctionData:dic];
    [self sendXML:message image:nil voice:nil time:0];
}

- (void)UUInputFunctionView:(UUInputFunctionView *)funcView sendPicture:(UIImage *)image
{
    NSDictionary *dic = @{@"picture": image,
                          @"type": @(UUMessageTypePicture),
                          @"sender": @"Me"};
    [self dealTheFunctionData:dic];
    [self sendXML:@"" image:image voice:nil time:0];
}

- (void)UUInputFunctionView:(UUInputFunctionView *)funcView sendVoice:(NSData *)voice time:(NSInteger)second
{
    NSDictionary *dic = @{@"voice": voice,
                          @"strVoiceTime": [NSString stringWithFormat:@"%d",(int)second],
                          @"type": @(UUMessageTypeVoice),
                          @"sender": @"Me"};
    [self dealTheFunctionData:dic];
    [self sendXML:@"" image:nil voice:voice time:second];
}

- (void)dealTheFunctionData:(NSDictionary *)dic
{
    [self.chatModel addSpecifiedItem:dic];
    [self.chatTableView reloadData];
    [self tableViewScrollToBottom];
}

#pragma mark - 发送XML封装数据
- (void)sendXML:(NSString *)message image:(UIImage *)img voice:(NSData *)voice time:(NSUInteger)second{
    //生成xml
    //<body>
    NSXMLElement *body = [NSXMLElement elementWithName:@"body"];
    [body setStringValue:message];
    //<message>
    NSXMLElement *mes = [NSXMLElement elementWithName:@"message"];
    //<message type = chat>
    [mes addAttributeWithName:@"type" stringValue:@"chat"];
    //<message type = "chat" to = _chatUserName>
    [mes addAttributeWithName:@"to" stringValue:_chatUserName];
    //<message type = "chat" to = _chatUserName from = ...>
    [mes addAttributeWithName:@"from" stringValue:[[NSUserDefaults standardUserDefaults] stringForKey:USERID]];
    //<message ...><body></body></message>
    [mes addChild:body];

    if (img) {
        //坑的我不轻的编码,这里因为png比较大(不清楚到底是转换出错还是xml有大小限制,但感觉是前者,毕竟好像base64是针对小文件编码的吧?),所以发送失败或接收不到(或者我得等个5分钟?),所以选用压缩后传送。
//        NSData *imgData = UIImagePNGRepresentation(img);
        NSData *imgData = UIImageJPEGRepresentation(img, 0.1);
        NSString *imgStr=[imgData base64EncodedStringWithOptions:0];

        //<message ...><body></body><img></img></message>
        NSXMLElement *imgAttachment = [NSXMLElement elementWithName:@"image"];
        [imgAttachment setStringValue:imgStr];
        [mes addChild:imgAttachment];
    }

    if (voice) {
        NSString *voiceStr = [voice base64EncodedStringWithOptions:0];
        NSXMLElement *voiceAttachment = [NSXMLElement elementWithName:@"voice"];
        [voiceAttachment setStringValue:voiceStr];
        [voiceAttachment addAttributeWithName:@"voiceTime" unsignedIntegerValue:second];
        [mes addChild:voiceAttachment];
    }

    //发送消息
    [[self xmppStream] sendElement:mes];
}

#pragma mark - tableView delegate & datasource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return self.chatModel.dataSource.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UUMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellID"];
    if (cell == nil) {
        cell = [[UUMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CellID"];
        cell.delegate = self;
    }
    [cell setMessageFrame:self.chatModel.dataSource[indexPath.row]];
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return [self.chatModel.dataSource[indexPath.row] cellHeight];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [self.view endEditing:YES];
}

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    [self.view endEditing:YES];
}

#pragma mark - cellDelegate
//这里我就没改、或许你有兴趣添加详细资料、
- (void)headImageDidClick:(UUMessageCell *)cell userId:(NSString *)userId{
    // headIamgeIcon is clicked
    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:cell.messageFrame.message.strName message:@"headImage clicked" delegate:nil cancelButtonTitle:@"sure" otherButtonTitles:nil];
    [alert show];
}

//返回上一层
- (IBAction)back:(UIBarButtonItem *)sender {
    [self dismissViewControllerAnimated:YES completion:nil];
}

@end

Finally, 聊天的主窗口也写完了,这样不出错的话程序应该能够运行了!
我的源码已经提供,可以按照对比调试。

后面还有一篇总结与反思,有兴趣可以看看。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值