前言
前面我们几篇文章我们一直在说关于XMPPFramework中好友关系相关的东东,今天即时通讯最重要的是什么?通讯聊天呀,所以,我们今天就说一下XMPPFramewor聊天的实现.SDChat比较简陋的聊天界面如下所示.
XMPPFramework实现聊天功能的相关方法
在XMPPFramework中实现聊天功能的类主要是XMPPSteam、XMPPMessageArchiving和XMPPMessageArchivingCoreDataStorage,其中XMPPSteam的作用主要是消息发送、消息接受等一系列相关的代理回调.XMPPMessageArchiving和XMPPMessageArchivingCoreDataStorage则是消息历史记录本地查询的相关类.下面则是关于聊天的相关方法.
//发送消息
- (void)sendElement:(NSXMLElement *)element;
//消息发送成功的代理回调
-(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message;
//消息发送失败的代理回调
-(void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error;
//接受到新的消息
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message;
XMPPFramework的聊天功能实现
聊天功能的逻辑相对好友方面的逻辑说还是比较简单的.我们先看一下两个用户之间发送一条消息的流程图.如下所示.
通过上面的流程图我们已经对XMPPFramework的发送消息的整体流程有了一个大概的了解.那么接下来我们就说一下SDChat中关于聊天功能的代码实现.
首先,在SDXmppManager中我们先对聊天功能的本地化存储对象进行初始化.
@property(nonatomic,strong)XMPPMessageArchiving *messageArchiving;
@property(nonatomic,strong)NSManagedObjectContext *messageContext;
XMPPMessageArchivingCoreDataStorage *messageArchivingCoreDataStorage= [XMPPMessageArchivingCoreDataStorage sharedInstance];
self.messageArchiving = [[XMPPMessageArchiving alloc]initWithMessageArchivingStorage:messageArchivingCoreDataStorage dispatchQueue:dispatch_get_main_queue()];
//激活管理对象
[self.messageArchiving activate:self.stream];
//设置管理对象代理
[self.messageArchiving addDelegate:self delegateQueue:dispatch_get_main_queue()];
self.messageContext = messageArchivingCoreDataStorage.mainThreadManagedObjectContext;
上面.我们已经对本地化存储对象进行了设置,接下来,我们现在SDChatVC聊天界面中做一个准备工作,那就是获取聊天历史记录.我们需要通过SDXmppManager中的messageContext(上下文属性对象)来获取到对应的数据.
NSManagedObjectContext *context = [SDXmppManager defaulManager].messageContext;
然后我们需要调用CoreData中的NSFetchRequest(获取数据的请求,通过被管理数据的上下文来执行查询)这个类的相关方法查询对应的数据.这里需要使用到上述的上下文对象context.数据库中的数据很多,我们需要的只是与当前聊天对象的记录,所以我们还需要使用谓词来添加过滤条件.这样我们就可以获取到我们想要的数据了.如下代码所示,fetchedObjects
就是我们所需要的数组,数组中的对象类型为MPPMessageArchiving_ Message_CoreDataObject
.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//这里面要填的是XMPPARChiver的coreData实例类型
NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Specify criteria for filtering which objects to fetch
//对取到的数据进行过滤,传入过滤条件.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr == %@ AND bareJidStr == %@", [SDXmppManager defaulManager].stream.myJID.bare,self.chatToPeople.jid.bare];
[fetchRequest setPredicate:predicate];
// Specify how the fetched objects should be sorted
//设置排序的关键字
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp"
ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
//完成之后干什么?
NSLog(@"和此人的激情交谈");
}
上面关于CoreData数据库的查询看似很是麻烦,但是系统已经帮我们写好了代码块了,我们只需要输入"fetch"就可以找到如下的列表,我们选择第一个"fetch - Core Data Fetch"就把上面所的所有的代码输入了.如图所示.
那么,准备工作做好之后,我们就需要开始发送消息了.发送消息也是十分的简单,我们使用- (void)sendElement:(NSXMLElement *)element
这方法就可以发送我们的消息了,但是在此之前,我们需要组装我们所需要发送的XMPPMessage消息对象.我们设置类型为"chat"类型,然后设置需要接受者的JID,接受者的信息需要从联系人列表页面中传值过来.然后我们添加XMPPMessage的Body信息为我们的文本信息.代码如下.
XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToPeople.jid];
[message addBody:self.inputView.inputTextView.text];
那么接下来,我们就发送的我们的消息即可.
[[SDXmppManager defaulManager].stream sendElement:message];
发送如果成功(对方接收到消息)之后,我们就需要在-(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message
发送消息成功这个代理方法中做数据刷新的操作代码如下所示.
//消息发送成功
-(void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message{
[self reloadMessage];
}
作为消息的接受者也是要聊天记录的刷新的,这个需要在-(void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
这个方法中进行代码实现,具体如下所示.
//消息接收成功
-(void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
[self reloadMessage];
}
消息回执
上面的模块,我们好像把消息的一个流程做完了,但是其实不然,如果只有上面的几步,我们是实现了基本的即时通讯,但是会出现信息不同步的Bug,那就是我们同时发送两条消息的时候,我们的页面上只会出现上一条消息,最后发送的消息是不出现的,这是什么原因造成的呢?用户发送的消息,其实是没有回执.就是说A发送消息给B.而B又一致不回复.所以对于客户端A来讲压根不知道消息是否发送成功.所以我们需要在每一条消息的后面添加回执消息,这个需要在组装消息之后进行添加.如下所示.
XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToPeople.jid];
[message addBody:self.inputView.inputTextView.text];
NSXMLElement *receipt = [NSXMLElement elementWithName:@"request" xmlns:@"urn:xmpp:receipts"];
[message addChild:receipt];
那么当接受者接受到消息之后,我们需要往发送者发送回执消息,通知发送者消息已经接受成功了,代码如下所示.
NSXMLElement *request = [message elementForName:@"request"];
if (request)
{
if ([request.xmlns isEqualToString:@"urn:xmpp:receipts"])//消息回执
{
//组装消息回执
XMPPMessage *msg = [XMPPMessage messageWithType:[message attributeStringValueForName:@"type"] to:message.from elementID:[message attributeStringValueForName:@"id"]];
NSXMLElement *recieved = [NSXMLElement elementWithName:@"received" xmlns:@"urn:xmpp:receipts"];
[msg addChild:recieved];
//发送回执
[[SDXmppManager defaulManager].stream sendElement:msg];
}
}else
{
NSXMLElement *received = [message elementForName:@"received"];
if (received)
{
if ([received.xmlns isEqualToString:@"urn:xmpp:receipts"])//消息回执
{
//发送成功
NSLog(@"message send success!");
}
}
}
有了消息回执之后,但是如果接受者不在当前的聊天页面却是在线的,我们发送一条消息何如发送回执消息呢?这就需要我们在AppDelegate里面设置一个接收消息的代理方法用来发送回执消息,代码和上面的一样.这里就做截图了,不进行重复操作了.
聊天记录的展现
上面我们基本把聊天功能实现完成了,如何实现聊天记录的展现呢?页面的整体是一个tableView,根据文字消息的内容,我们计算出每一个气泡的高度,然后计算出每一个Cell的高度.那么逻辑上是这样的,实际上在SDChat中是如何实现的?这里我就一个一个功能点来说明.
对于每一个聊天记录的Cell,我们把文字Label添加到气泡的UIImageView对象上去.然后气泡的UIImageView对象再添加到每一个Cell上.Cell主要有两个功能点,一个就是文字的高度,另外一个就是气泡的拉伸问题.
首先,我们说一下计算文字高度的方法.我们使用的是NSString中的一个方法,我们输入文字的可展示范围以及文字的字号就可以.可以计算出一段文字的Rect.
- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(nullable NSDictionary<NSString *, id> *)attributes context:(nullable NSStringDrawingContext *)context;
那么计算出文字高度之后,我们如何让我们的气泡适应文字的尺寸呢?我们需要对图片进行拉伸操作,我们使用的是如下的方法.其中参数1代表从左侧到指定像素禁止拉伸,该像素之后拉伸,参数2代表从上面到指定像素禁止拉伸,该像素以下就拉伸.
- (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight;
在聊天页面的tableView的其实就没有什么好说的,最多就是一个去除Cell之间的间隔线问题,我们只需要下面一行代码即可.
self.chatTableView.separatorStyle = NO;
聊天历史记录获取Bug以及优化建议
其实这个Bug是关于中文的问题,在设置个人的电子名片和聊天记录的时候都会出现这个问题,那么就是如果我们输入了中文,那么下一次获取数据的时候可能只有"???"了,问题待解决,如果有解决的大神,希望能告诉骚栋,红包答谢~😄
结束
呼呼,写到这一篇,骚栋XMPPFramework的这个专题也进行了一大半了,下一篇骚栋将写一下关于好友上下线的状态的获取.希望大家能继续关注,如果有任何问题欢迎联系骚栋,谢谢.最后还把SDChat的传送门送给大家.大家可以对照着Demo来看本篇博客.
网友评论
if (request)
{
if ([request.xmlns isEqualToString:@"urn:xmpp:receipts"])//消息回执
{
//组装消息回执
XMPPMessage *msg = [XMPPMessage messageWithType:[message attributeStringValueForName:@"type"] to:message.from elementID:[message attributeStringValueForName:@"id"]];
NSXMLElement *recieved = [NSXMLElement elementWithName:@"received" xmlns:@"urn:xmpp:receipts"];
[msg addChild:recieved];
//发送回执
[[SDXmppManager defaulManager].stream sendElement:msg];
}
}else
{
NSXMLElement *received = [message elementForName:@"received"];
if (received)
{
if ([received.xmlns isEqualToString:@"urn:xmpp:receipts"])//消息回执
{
//发送成功
NSLog(@"message send success!");
}
}
}
[message addChild:receipt]; 这个加了,但是还是看不到