UI进阶 即时通讯之XMPP好友列表、添加好友、获取会话内容、简单聊天

时间:2023-03-09 09:13:53
UI进阶 即时通讯之XMPP好友列表、添加好友、获取会话内容、简单聊天

这篇博客的代码是直接在上篇博客的基础上增加的,先给出部分代码,最后会给出能实现简单功能的完整代码。

UI进阶 即时通讯之XMPP登录、注册

1、好友列表

初始化好友花名册

  #pragma mark - 管理好友
          // 获取管理好友的单例对象
          XMPPRosterCoreDataStorage *rosterStorage = [XMPPRosterCoreDataStorage sharedInstance];
          // 用管理好友的单例对象初始化Roster花名册
          // 好友操作是耗时操作
          self.xmppRoster = [[XMPPRoster alloc] initWithRosterStorage:rosterStorage dispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )];
          // 在通道中激活xmppRoster
          [self.xmppRoster activate:self.xmppStream];
          // 设置代理
         [self.xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];

XMPPRoster代理方法

  好友列表

UI进阶 即时通讯之XMPP好友列表、添加好友、获取会话内容、简单聊天

添加好友

UI进阶 即时通讯之XMPP好友列表、添加好友、获取会话内容、简单聊天

删除好友

UI进阶 即时通讯之XMPP好友列表、添加好友、获取会话内容、简单聊天

XMPPManager.h 新增代码

 #pragma mark - 添加好友
 - (void)addFriend;

 #pragma mark - 删除好友
 - (void)removeFriendWithName:(NSString *)userName;

 #pragma mark - 手动断开连接(注销)
 - (void)disconnectionToServer;

XMPPManager.m 新增代码

   /// 接收要添加好友的名字
   @property (nonatomic, strong) UITextField *addText;

   #pragma mark - 重写初始化方法
   - (instancetype)init {
       if (self = [super init]) {
   #pragma mark - 创建通道
           // 初始化通道对象
           self.xmppStream = [[XMPPStream alloc] init];
          // 设置Openfire服务器主机名
          self.xmppStream.hostName = kHostName;
          // 设置服务器端口号
          self.xmppStream.hostPort = kHostPort;
          // 设置代理
          [self.xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];

  #warning -----------------以下是该方法中新增加的代码-----------------------
  #pragma mark - 管理好友
          // 获取管理好友的单例对象
          XMPPRosterCoreDataStorage *rosterStorage = [XMPPRosterCoreDataStorage sharedInstance];
          // 用管理好友的单例对象初始化Roster花名册
          // 好友操作是耗时操作
          self.xmppRoster = [[XMPPRoster alloc] initWithRosterStorage:rosterStorage dispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )];
          // 在通道中激活xmppRoster
          [self.xmppRoster activate:self.xmppStream];
          // 设置代理
          [self.xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];

  #warning -----------------以上是该方法中新增加的代码-----------------------

      }
      return self;
  }

  #pragma mark -----------------以下是管理好友列表----------------
  #pragma mark - 添加好友
  - (void)addFriend {
      NSLog(@"manager - 添加好友 %d", __LINE__);
      UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"添加好友" message:@"请输入要添加好友的名字" preferredStyle:UIAlertControllerStyleAlert];
      __weak typeof(self)weakSelf = self;
      [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
          // 接收输入的好友名字
          weakSelf.addText = textField;
      }];
      UIAlertAction *cancleAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
      UIAlertAction *sureAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
          NSLog(@"======%@", weakSelf.addText.text);
          // 使用JID记录
          XMPPJID *addJID = [XMPPJID jidWithUser:weakSelf.addText.text domain:kDomin resource:kResource];
          // 监听好友的动作
          [weakSelf.xmppRoster subscribePresenceToUser:addJID];
          // 添加好友
          [weakSelf.xmppRoster addUser:addJID withNickname:weakSelf.addText.text];
      }];
      [alertController addAction:sureAction];
      [alertController addAction:cancleAction];
      [[self getCurrentVC ]presentViewController:alertController animated:YES completion:nil];
  }

  #pragma mark - 删除好友
  - (void)removeFriendWithName:(NSString *)userName {
      NSLog(@"manager删除好友 %d", __LINE__);
      // 使用JID记录要删除的用户名
      XMPPJID *removeJID = [XMPPJID jidWithUser:userName domain:kDomin resource:kResource];
      // 停止监听好友
      [self.xmppRoster unsubscribePresenceFromUser:removeJID];
      // 删除好友
      [self.xmppRoster removeUser:removeJID];
  }
  #pragma mark - 获取当前屏幕显示的viewcontroller
  - (UIViewController *)getCurrentVC
  {
      UIViewController *result = nil;

      UIWindow * window = [[UIApplication sharedApplication] keyWindow];
      if (window.windowLevel != UIWindowLevelNormal)
      {
          NSArray *windows = [[UIApplication sharedApplication] windows];
          for(UIWindow * tmpWin in windows)
          {
              if (tmpWin.windowLevel == UIWindowLevelNormal)
              {
                  window = tmpWin;
                  break;
              }
          }
      }

      UIView *frontView = [[window subviews] objectAtIndex:];
      id nextResponder = [frontView nextResponder];

      if ([nextResponder isKindOfClass:[UIViewController class]])
          result = nextResponder;
      else
          result = window.rootViewController;

      return result;
  }

RosterListTableViewController.m 好友列表显示页面

   #import "RosterListTableViewController.h"
   #import "XMPPManager.h"
   #import "ChatTableViewController.h"

   @interface RosterListTableViewController ()<XMPPRosterDelegate, XMPPStreamDelegate>

   /// 好友列表
   @property (nonatomic, strong) NSMutableArray *allRosterArray;
   /// 用来存储发送好友请求者的JID
  @property (nonatomic, strong) XMPPJID *fromJID;

  @end

  @implementation RosterListTableViewController

  - (void)viewDidLoad {
      [super viewDidLoad];
      // 初始化数组
      self.allRosterArray = [NSMutableArray array];

      [[XMPPManager sharedXMPPManager].xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
      [[XMPPManager sharedXMPPManager].xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];
      self.title = @"好友列表";

      // 添加按钮
      self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addFriendAction)];
      // 返回按钮
      self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"注销" style:UIBarButtonItemStylePlain target:self action:@selector(cancleAction)];

      [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"RosterCell"];

  }

  #pragma mark - 添加好友按钮点击事件
  - (void)addFriendAction {

      [[XMPPManager sharedXMPPManager] addFriend];
  }

  #pragma mark - 注销按钮点击事件
  - (void)cancleAction {
      // 注销
      [[XMPPManager sharedXMPPManager] disconnectionToServer];
      [self.navigationController popViewControllerAnimated:YES];
  }

  #pragma mark - Table view data source

  - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
      ;
  }

  - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
      return self.allRosterArray.count;
  }

  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
      UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"RosterCell" forIndexPath:indexPath];

      // 根据项目情况分析,合理添加判断
      ) {
          // 获取用户
          XMPPJID *jid = [self.allRosterArray objectAtIndex:indexPath.row];
          cell.textLabel.text = jid.user;
          NSLog(@"bare %@", jid.bare);
      }

      return cell;
  }

  - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
      return YES;
  }

  - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
      if (editingStyle == UITableViewCellEditingStyleDelete) {
          // 删除一个好友
          XMPPJID *jid = self.allRosterArray[indexPath.row];
          // 根据名字删除好友
          [[XMPPManager sharedXMPPManager] removeFriendWithName:jid.user];
          // 从数组中移除
          [self.allRosterArray removeObjectAtIndex:indexPath.row];
          [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
      } else if (editingStyle == UITableViewCellEditingStyleInsert) {

      }
  }

  #pragma mark - 点击cell进入聊天界面
  - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
      ChatTableViewController *chatTVC = [[ChatTableViewController alloc] initWithStyle:UITableViewStylePlain];
      // 将当前好友的JID传到聊天界面
      chatTVC.chatWithJID = self.allRosterArray[indexPath.row];
      [self.navigationController pushViewController:chatTVC animated:YES];
  }

 #pragma mark - ----------------XMPPRosterDelegate代理方法----------------
 #pragma mark - 开始获取好友
 - (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender {
     NSLog(@"listTVC 开始获取好友 %d", __LINE__);
 }

 #pragma mark - 结束获取好友
 - (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender {
     NSLog(@"listTVC 获取好友结束 %d", __LINE__);
     // 当前页面是用于显示好友列表的,所以在结束获取好友的代理方法中要进行页面刷新页面,然后将数据显示。
     // 刷新UI
     [self.tableView reloadData];
 }

 #pragma mark - 接收好友信息

 // 获取好友列表时会执行多次,每次获取一个好友信息并将该好友信息添加到数组中
 // 发送完添加好友请求会执行
 // 同意别人的好友请求会执行
 // 删除好友时会执行
 - (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item {
     NSLog(@"listTVC 接收好友信息 %d", __LINE__);
     /**
123      *  好友信息状态有5种
124         both - 互为好友
125         none - 互不为好友
126         to - 请求添加对方为好友,对方还没有同意
127         from - 对方添加我为好友,自己还没有同意
128         remove - 曾经删除的好友
129      */

     // 自己和对方之间的关系
     NSString *description = [[item attributeForName:@"subscription"] stringValue];
     NSLog(@"关系%@", description);

     // 显示我的好友
     if ([description isEqualToString:@"both"]) {
         // 添加好友
         // 获取好友的JID
         NSString *friendJID = [[item attributeForName:@"jid"] stringValue];
         XMPPJID *jid = [XMPPJID jidWithString:friendJID];
         // 如果数组中含有这个用户,那么不添加进数组
         if ([self.allRosterArray containsObject:jid]) {
             NSLog(@"已经有该好友");
             return;
         }
         // 添加好友到数组中
         [self.allRosterArray addObject:jid];

         // 在TableView的最后一个cell下面添加这条数据
         NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.allRosterArray.count -  inSection:];
         [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
     }
 }

 #pragma mark - 接收到添加好友的请求,选择接受or拒绝
 - (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence {
     NSLog(@"listTVC 接收添加好友的请求 %d", __LINE__);
     self.fromJID = presence.from;
     // 需要相关的提醒框去确定是否接受好友请求
     UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"请求添加好友" message:@"是否同意" preferredStyle:UIAlertControllerStyleAlert];
     __weak typeof(self)weakSelf = self;
     UIAlertAction *acceptAction = [UIAlertAction actionWithTitle:@"同意" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
         // 添加到花名册
         [[XMPPManager sharedXMPPManager].xmppRoster acceptPresenceSubscriptionRequestFrom:weakSelf.fromJID andAddToRoster:YES];
     }];
     UIAlertAction *rejectAction = [UIAlertAction actionWithTitle:@"拒绝" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
         [[XMPPManager sharedXMPPManager].xmppRoster rejectPresenceSubscriptionRequestFrom:weakSelf.fromJID];
     }];
     [alertController addAction:acceptAction];
     [alertController addAction:rejectAction];
     [self presentViewController:alertController animated:YES completion:nil];
 }

 #pragma mark - ----------------XMPPStreamDelegate代理方法----------------
 #pragma mark - 判断好友是否处于上线状态
 - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence {
     NSLog(@"listTVC 判断好友是否处于上线状态 %@ %d", presence.status, __LINE__);
     NSString *type = presence.type;
     NSString *presenceUser = presence.to.user;
     // 判断当前用户是否为好友
     if ([presenceUser isEqualToString:[sender myJID].user]) {
         if ([type isEqualToString:@"available"]) {
             NSLog(@"该用户处于上线状态");
         } else if ([type isEqualToString:@"unavailable"]) {
             NSLog(@"该用户处于下线状态");
         }
     }
 }

 @end

复制代码

2、聊天

聊天的规则:

1、从服务器获取聊天记录

2、根据消息类XMPPMessageArchiving_Message_CoreDataObject的对象的属性isOutgoing来判断该消息是不是对方发送过来的消息 YES - 对方发送的消息, NO - 自己发送给对方的消息

3、发送消息

4、接收消息

  初始化消息归档

 #pragma mark - 初始化消息归档
          // 获取管理消息的存储对象
          XMPPMessageArchivingCoreDataStorage *storage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
          // 进行消息管理器的初始化
          self.xmppMessageArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:storage dispatchQueue:dispatch_get_main_queue()];
          // 在通道中激活xmppMessageArchiving
          [self.xmppMessageArchiving activate:self.xmppStream];
          // 设置代理
          [self.xmppMessageArchiving addDelegate:self delegateQueue:dispatch_get_main_queue()];
         // 设置管理上下文 (此时不再从AppDelegate获取)
         self.context = storage.mainThreadManagedObjectContext;

获取聊天记录(使用CoreData的方式)

1、创建请求

2、创建实体描述,实体名:   XMPPMessageArchiving_Message_CoreDataObject

3、创建谓词查询条件,条件:streamBareJidStr == 本人Jid AND bareJidStr == 好友Jid

4、创建排序对象,排序条件:timestamp

5、执行请求

接受、发送消息用到的代理方法

UI进阶 即时通讯之XMPP好友列表、添加好友、获取会话内容、简单聊天

消息气泡

UI进阶 即时通讯之XMPP好友列表、添加好友、获取会话内容、简单聊天

XMPPManager.h 新增代码

 /// 和聊天相关的属性,消息归档
 @property (nonatomic, strong) XMPPMessageArchiving *xmppMessageArchiving;
 /// 管理数据库上下文
 @property (nonatomic, strong) NSManagedObjectContext *context;

XMPPManager.m 新增代码

#pragma mark - 重写初始化方法
- (instancetype)init {
    if (self = [super init]) {
#pragma mark - 创建通道
        // 初始化通道对象
        self.xmppStream = [[XMPPStream alloc] init];
        // 设置Openfire服务器主机名
        self.xmppStream.hostName = kHostName;
        // 设置服务器端口号
        self.xmppStream.hostPort = kHostPort;
        // 设置代理
        [self.xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];

#pragma mark - 管理好友
        // 获取管理好友的单例对象
        XMPPRosterCoreDataStorage *rosterStorage = [XMPPRosterCoreDataStorage sharedInstance];
        // 用管理好友的单例对象初始化Roster花名册
        // 好友操作是耗时操作
        self.xmppRoster = [[XMPPRoster alloc] initWithRosterStorage:rosterStorage dispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )];
        // 在通道中激活xmppRoster
        [self.xmppRoster activate:self.xmppStream];
        // 设置代理
        [self.xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];

#warning -----------------以下是该方法中新增加的代码-----------------------
#pragma mark - 初始化管理归档
        // 获取管理消息的存储对象
        XMPPMessageArchivingCoreDataStorage *storage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
        // 进行消息管理器的初始化
        self.xmppMessageArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:storage dispatchQueue:dispatch_get_main_queue()];
        // 在通道中激活xmppMessageArchiving
        [self.xmppMessageArchiving activate:self.xmppStream];
        // 设置代理
        [self.xmppMessageArchiving addDelegate:self delegateQueue:dispatch_get_main_queue()];
        // 设置管理上下文 (此时不再从AppDelegate获取)
        self.context = storage.mainThreadManagedObjectContext;
#warning -----------------以上是该方法中新增加的代码-----------------------
    }
    return self;
}

从好友列表页面点击cell进入聊天界面

  RosterListTableViewController.m 好友列表界面新增代码

 #pragma mark - 点击cell进入聊天界面
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     ChatTableViewController *chatTVC = [[ChatTableViewController alloc] initWithStyle:UITableViewStylePlain];
     // 将当前好友的JID传到聊天界面
     chatTVC.chatWithJID = self.allRosterArray[indexPath.row];
     [self.navigationController pushViewController:chatTVC animated:YES];
 }

复制代码

 聊天界面

  ChatTableViewController.h

1 #import <UIKit/UIKit.h>
2 #import "XMPPManager.h"
3
4 @interface ChatTableViewController : UITableViewController
5 /// 当前和谁在聊天
6 @property (nonatomic, strong) XMPPJID *chatWithJID;
7 @end

ChatTableViewController.m

  #import "ChatTableViewController.h"
   #import "ChatTableViewCell.h"

   @interface ChatTableViewController ()<XMPPStreamDelegate>
   @property (nonatomic, strong) NSMutableArray *allMessageArray;
   @end

   @implementation ChatTableViewController

  - (void)viewDidLoad {
      [super viewDidLoad];
      self.allMessageArray = [NSMutableArray array];
      // 隐藏分割线
      self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
      // 注册cell
      [self.tableView registerClass:[ChatTableViewCell class] forCellReuseIdentifier:@"chatCell"];
      // 发送按钮
      self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"取消" style:UIBarButtonItemStylePlain target:self action:@selector(cancelAction)];
      // 取消按钮 20     self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"发送" style:UIBarButtonItemStylePlain target:self action:@selector(sendMessageAction)];
      [[XMPPManager sharedXMPPManager].xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];

      // 获取显示消息的方法
      [self showMessage];

  }

  #pragma mark - 取消按钮点击事件
  - (void)cancelAction {
      // 返回上一界面
      [self.navigationController popViewControllerAnimated:YES];
  }
  #pragma mark - 发送消息按钮点击事件
  - (void)sendMessageAction {
      XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatWithJID];
      // 设置message的body为固定值 (没有实现发送自定义消息)
      [message addBody:@"我爱你"];
      // 通过通道进行消息发送
      [[XMPPManager sharedXMPPManager].xmppStream sendElement:message];
  }

  #pragma mark - 显示消息
  - (void)showMessage {
      // 获取管理对象上下文
      NSManagedObjectContext *context = [XMPPManager sharedXMPPManager].context;
      // 初始化请求对象
      NSFetchRequest *request = [[NSFetchRequest alloc] init];
      // 获取实体
      NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:context];
      // 设置查询请求的实体
      [request setEntity:entity];
      // 设置谓词查询 (当前用户的jid,对方用户的jid)  (根据项目需求而定)
      request.predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr == %@ AND bareJidStr == %@",[XMPPManager sharedXMPPManager].xmppStream.myJID.bare,self.chatWithJID.bare];
      // 按照时间顺序排列
      NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"timestamp" ascending:YES];
      [request setSortDescriptors:@[sort]];
      // 获取到存储在数据库中的聊天记录
      NSArray *resultArray = [context executeFetchRequest:request error:nil];
      // 先清空消息数组 (根据项目需求而定)
      [self.allMessageArray removeAllObjects];
      // 将结果数组赋值给消息数组
      self.allMessageArray = [resultArray mutableCopy];
      // 刷新UI
      [self.tableView reloadData];
      // 当前聊天记录跳到最后一行
      ) {
          NSIndexPath * indexPath = [NSIndexPath indexPathForRow:self.allMessageArray.count -  inSection:];
          // 跳到最后一行
          [self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];
      }
      [context save:nil];
  }

  #pragma mark - Table view data source
  - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
      ;
  }

  - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
      return self.allMessageArray.count;
  }

  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
      ChatTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"chatCell" forIndexPath:indexPath];
      // 数组里存储的是XMPPMessageArchiving_Message_CoreDataObject对象
      XMPPMessageArchiving_Message_CoreDataObject *message = [self.allMessageArray objectAtIndex:indexPath.row];
      // 设置cell中的相关数据
      // 根据isOutgoing判断是不是对方发送过来的消息 YES - 对方发送的消息, NO - 自己发送给对方的消息
      cell.isOut = message.isOutgoing;
      cell.message = message.body;

      return cell;
  }

  - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
      // cell的高度并没有自适应
      ;
  }

 #pragma mark - ------------XMPPStreamDelegate相关代理------------
 #pragma mark - 已经发送消息
 - (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message {
     // 重新对消息进行操作
     [self showMessage];
 }
 #pragma mark - 已经接收消息
 - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
     // 重新对消息进行操作
     [self showMessage];
 }

 #pragma mark - 消息发送失败
 - (void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error {
     NSLog(@"消息发送失败");
 }

 @end

自定义cell

  ChatTableViewCell.h

#import <UIKit/UIKit.h>

@interface ChatTableViewCell : UITableViewCell
/// 判断是不是对方发送过来的消息 YES - 对方发送的消息, NO - 自己发送给对方的消息
@property (nonatomic, assign) BOOL isOut;
/// 消息内容
@property (nonatomic, copy) NSString *message;
@end

ChatTableViewCell.m

 #import "ChatTableViewCell.h"

  @interface ChatTableViewCell ()
  /// 头像
  @property(nonatomic,strong)UIImageView * headerImageView;
  /// 消息框背景
  @property(nonatomic,strong)UIImageView * backgroundImageView;
  /// 显示每一条聊天内容
  @property(nonatomic,strong)UILabel * contentLabel;
 @end

 @implementation ChatTableViewCell

 //使用懒加载创建并初始化所有UI控件
 - (UILabel *)contentLabel{
     if (_contentLabel == nil) {
         _contentLabel = [[UILabel alloc] init];
     }
     return _contentLabel;
 }
 - (UIImageView *)backgroundImageView
 {
     if (_backgroundImageView == nil) {
         _backgroundImageView = [[UIImageView alloc] init];
     }
     return _backgroundImageView;
 }
 - (UIImageView *)headerImageView
 {
     if (_headerImageView == nil) {
         _headerImageView = [[UIImageView alloc] init];
     }
     return _headerImageView;
 }

 - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
     self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
     if (self) {
         //设置cell不能选中
         self.selectionStyle = UITableViewCellSelectionStyleNone;

         [self.contentView addSubview:self.backgroundImageView];
         [self.contentView addSubview:self.headerImageView];
         [self.backgroundImageView addSubview:self.contentLabel];

     }
     return self;
 }

 - (void)awakeFromNib {
     // Initialization code

 }

 //重写isOut的setter方法,来设定cell上的不同布局
 - (void)setIsOut:(BOOL)isOut
 {
     _isOut = isOut;
     CGRect rect = self.frame;
     if (_isOut) {
         self.headerImageView.frame = CGRectMake(rect.size.width-, , , );
         self.headerImageView.image = [UIImage imageNamed:@"nike"];
     }else{
         self.headerImageView.frame = CGRectMake(, , , );
         self.headerImageView.image = [UIImage imageNamed:@"keji"];
     }
 }
 //重写message方法,在cell上显示聊天记录
 - (void)setMessage:(NSString *)message
 {
     if (_message != message) {
         _message = message;
         self.contentLabel.text = _message;
         //        self.contentLabel.numberOfLines = 0;
         [self.contentLabel sizeToFit];

         CGRect rect = self.frame;
         if (self.isOut) {//发出去的
             // 消息气泡
             self.backgroundImageView.image = [[UIImage imageNamed: topCapHeight:];
             self.backgroundImageView.frame = CGRectMake(rect.size.width - self.contentLabel.frame.size.width - -, , self.contentLabel.frame.size.width+, rect.size.height-);
         }else{//接收的
             self.backgroundImageView.image = [[UIImage imageNamed: topCapHeight:];
             self.backgroundImageView.frame = CGRectMake(, ,self.contentLabel.frame.size.width+, rect.size.height-);
         }
         //因为contentLabel已经自适应文字大小,故不用设置宽高,但需要设置位置
         self.contentLabel.center = CGPointMake(self.backgroundImageView.frame.size.width/2.0, self.backgroundImageView.frame.size.height/2.0);

     }
 }

 @end

以上,一个超级简单的即时通讯工具就已经完成,有些逻辑思维是不固定的,可以根据自己的项目需求去更改代码

还有没有实现到的 图片/语音传输,在这里就只说一下原理

1、将图片/语音上传到服务器

2、根据和服务器的约定,拼好文件在服务器的地址(即图片/语音的URL)

3、调用XMPP发送消息方法,将地址发送出去

4、在接收端接收到的为一条文本信息,里面仅仅是一个指向资源文件的URL地址

5、在拿到URL后进行需要的操作(即请求图片/语音显示到页面上)