XMPPFramework iOS开发(七)即时通讯

来源:互联网 发布:知乎专栏搜索 编辑:程序博客网 时间:2024/06/10 05:09

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
原创粉丝点击