关闭

XMPPFramework iOS开发(七)即时通讯

标签: ios即时通讯发送消息聊天XMPP
1695人阅读 评论(0) 收藏 举报
分类:

XMPPFramework iOS开发(七)即时通讯(文字和图片)

一、程序目标

这里写图片描述

聊天界面如图,需要实现的功能有:发送文字消息、发送图片消息。

二、使用准备

2.1 开启消息模块

和电子名片、花名册模块一样,要想使用XMPP框架下的聊天功能,需要开启消息模块。

//消息模块
#import "XMPPMessageArchiving.h"
#import "XMPPMessageArchivingCoreDataStorage.h"
//消息模块
@property (nonatomic, strong, readonly) XMPPMessageArchiving *msgArchiving;
//消息数据存储模块
@property (nonatomic, strong, readonly) XMPPMessageArchivingCoreDataStorage *msgArchivingStorage;
//4.添加消息模块
    if (_msgArchivingStorage == nil) {
        _msgArchivingStorage = [[XMPPMessageArchivingCoreDataStorage alloc] init];
        _msgArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:_msgArchivingStorage];
        [_msgArchiving activate:_xmppStream];
    }

要记得在之前说过的teardown方法里面,释放花名册、消息模块等资源。此外,XMPP还提供了断线重连的功能,很简单,声明后激活即可。

    //断网自动连接
    XMPPReconnect *_reconnect;
    //5.自动连接模块
    _reconnect = [[XMPPReconnect alloc] init];
    [_reconnect activate:_xmppStream];

2.2 声明成员变量

@interface WCChatViewController : UIViewController

/**
 *  正在聊天的好友的Jid
 */
@property (nonatomic, strong) XMPPJID *friendJid;

@end
@interface WCChatViewController () <NSFetchedResultsControllerDelegate, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate>{
    NSFetchedResultsController *_resultContr;
}
@property (weak, nonatomic) IBOutlet UITableView *tableView;

/**
 *  输入框距离底部的约束
 */
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint;

@property (weak, nonatomic) IBOutlet UITextField *textField;

- (IBAction)addBtnClick:(id)sender;

@end

2.3 聊天数据格式

可以在程序中打印message的内容,其格式如下:

聊天数据格式

其中,接受到的message中:
1. from、to指通信双方的jid
2. body指通信内容

发送给对方的message中:
1. type的值是自己定义的
2. body同样指通信内容

2.4 监听键盘弹出情况

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kbWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kbWillHide:) name:UIKeyboardWillHideNotification object:nil];
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark 键盘即将显示
- (void)kbWillShow:(NSNotification *)noti
{
    CGFloat kbHeight = [noti.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;

    self.bottomConstraint.constant = kbHeight;
}

#pragma mark 键盘即将隐藏
- (void)kbWillHide:(NSNotification *)noti
{
    self.bottomConstraint.constant = 0;
}

#pragma mark 退出键盘
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    [self.view endEditing:YES];
}

三、发送/接收消息

3.1 获取聊天数据

和花名册模块一样,XMPP会先把聊天数据上传到服务器,再缓存到本地并生成sqlite文件。因此,获取的方法也是一样的:

    //加载数据库的聊天数据
    //1.上下文
    NSManagedObjectContext *msgContext = [[WCXMPPTool sharedWCXMPPTool].msgArchivingStorage mainThreadManagedObjectContext];

    //2.请求查询哪张表
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"XMPPMessageArchiving_Message_CoreDataObject"];

    //设置过滤条件,只要自己和当前好友的聊天数据
    NSString *loginUserJid = [WCXMPPTool sharedWCXMPPTool].xmppStream.myJID.bare;
    //数据库表的属性名可在XMPP/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchiving.xcdatamodeld/中查看
    //streamBareJidStr指自己的jid,bareJidStr指和自己通信的用户的jid
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr = %@ AND bareJidStr = %@", loginUserJid, self.friendJid.bare];
    request.predicate = predicate;

    //如果以时间排序,timestamp是固定写法
    NSSortDescriptor *timeSort = [NSSortDescriptor sortDescriptorWithKey:@"timestamp" ascending:YES];
    request.sortDescriptors = @[timeSort];

    //3.执行请求
    //3.1创建结果控制器
    _resultContr = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:msgContext sectionNameKeyPath:nil cacheName:nil];
    _resultContr.delegate = self;
    //3.2执行
    NSError *error = nil;
    [_resultContr performFetch:&error];

    //列表下拉到最底部
    if (_resultContr.fetchedObjects.count > 0) {
        NSIndexPath *lastIndex = [NSIndexPath indexPathForRow:_resultContr.fetchedObjects.count-1 inSection:0];
        [self.tableView scrollToRowAtIndexPath:lastIndex atScrollPosition:UITableViewScrollPositionBottom animated:YES];
    }

3.2 发送文本消息

#pragma mark 按下键盘的发送键(遵守了UITextFieldDelegate协议)
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    NSString *text = self.textField.text;

    //发送聊天数据
    //上面说过,chat是定义给message的xml数据中type节点的值,之后获取聊天内容就根据这个节点的值获取
    XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:self.friendJid];
    [msg addBody:text];
    //sendElement在发送在线状态的时候也用过
    [[WCXMPPTool sharedWCXMPPTool].xmppStream sendElement:msg];

    //清空输入框
    self.textField.text = nil;

    return YES;
}

3.3 发送图片消息


#pragma mark 选择附件按钮
- (IBAction)addBtnClick:(id)sender {

    //选择图库中的图片
    UIImagePickerController *imagePc = [[UIImagePickerController alloc] init];
    imagePc.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;

    imagePc.delegate = self;

    //进入图库选择图片
    [self presentViewController:imagePc animated:YES completion:nil];
}

#pragma mark 图片选择器代理方法
#pragma mark 图片选择完成
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    ZHLog(@"%@", info);

    //获取到图片
    UIImage *image = info[UIImagePickerControllerOriginalImage];

    //把图片转化为data类型并发送
    [self sendAttchmentWithData:UIImagePNGRepresentation(image) bodyType:@"image"];

    //退出图片选择器
    [self dismissViewControllerAnimated:YES completion:nil];
}


#pragma mark 发送附件
- (void)sendAttchmentWithData:(NSData *)data bodyType:(NSString *)bodyType
{
    XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:self.friendJid];

    //新增属性节点,存放发送的消息数据的类型
    [msg addAttributeWithName:@"bodyType" stringValue:bodyType];
    //发送图片其实不需要body了,但没有body就发送不出去,获取图片的时候需要另外处理body的内容,不要让它显示
    [msg addBody:bodyType];

    //图片的data数据经过base64编码 转换为 字符串类型
    NSString *base64Str = [data base64EncodedStringWithOptions:0];

    //添加自定义节点,body存放文本消息/消息类型,自定义节点存放图片的数据内容
    XMPPElement *attachment = [XMPPElement elementWithName:@"attachment" stringValue:base64Str];
    [msg addChild:attachment];

    //发送
    [[WCXMPPTool sharedWCXMPPTool].xmppStream sendElement:msg];
}

3.4 显示消息内容

#pragma mark 返回列表行数
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _resultContr.fetchedObjects.count;
}

#pragma mark 设置列表数据,显示聊天内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"message";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:ID];
    }

    XMPPMessageArchiving_Message_CoreDataObject *msgObj = _resultContr.fetchedObjects[indexPath.row];

    //1.获取原始的xml数据
    XMPPMessage *message = msgObj.message;
    //2.获取消息的类型
    NSString *bodyType = [message attributeStringValueForName:@"bodyType"];

    if ([bodyType isEqualToString:@"image"]) {  //图片
        //遍历message的子节点
        NSArray *children = message.children;
        for (XMPPElement *note in children) {
            //获取节点的名字,attachment是刚刚定义的存放图片数据的节点
            if ([[note name] isEqualToString:@"attachment"]) {
                //获取附件字符串,转换为data,再转为图片
                NSString *imgBase64Str = [note stringValue];
                NSData *imgData = [[NSData alloc] initWithBase64EncodedString:imgBase64Str options:0];
                UIImage *image = [UIImage imageWithData:imgData];

                cell.imageView.image = image;
                cell.textLabel.text = nil;
            }
        }
    }else { //纯文本
        cell.textLabel.text = msgObj.body;
        cell.imageView.image = nil;
    }


    return cell;
}

3.5 刷新列表数据

#pragma mark 列表内容改变
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    //刷新列表
    [self.tableView reloadData];

    //列表下拉到最底部
    NSIndexPath *lastIndex = [NSIndexPath indexPathForRow:_resultContr.fetchedObjects.count-1 inSection:0];
    [self.tableView scrollToRowAtIndexPath:lastIndex atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}

四、问题

上面的代码是可以运行的,但同时还有几个地方没做好:

  1. 聊天数据的排版。上面的代码没有对聊天数据进行排版,因此可以显示,但不能知道某条消息是谁发送的,是什么时候发送的。要解决这个问题,需要自定义cell,这里不细讲,以后写UITableView相关的博客的时候再说。

  2. 发送图片耗能过大。一般来说,图片数据不应该直接发送,开发这种应用应该配备一台文件服务器。正确的发送图片的方式应该是这样的:
    a) 发送图片时首先把图片上传到文件服务器
    b) 服务器返回文件路径给用户
    c) openfire服务器只保存这个文件路劲
    d) 对方接收图片的时候,只获取到文件路径
    e) 之后再根据这个路径去文件服务器下载图片

解决这个问题需要自己编写文件服务器,笔者不具备这方面的知识,所以略过不谈。

五、小结

即时通讯思维导图


发送文本消息流程图.


发送图片流程图

以上。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:33914次
    • 积分:1165
    • 等级:
    • 排名:千里之外
    • 原创:86篇
    • 转载:0篇
    • 译文:0篇
    • 评论:6条
    最新评论