你的位置:首页 > 操作系统

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

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, 0)];    // 在通道中激活xmppRoster    [self.xmppRoster activate:self.xmppStream];    // 设置代理    [self.xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];

XMPPRoster代理方法

  好友列表

 

  删除好友

 


RosterListTableViewController.m 好友列表显示页面

#import "RosterListTableViewController.h"#import "XMPPManager.h"#import "ChatTableViewController.h"@interface RosterListTableViewController ()<XMPPRosterDelegate, XMPPStreamDelegate>/// [email protected] (nonatomic, strong) NSMutableArray *allRosterArray;/// [email protected] (nonatomic, strong) XMPPJID *fromJID;@[email protected] 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 {  return 1;}- (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];    // 根据项目情况分析,合理添加判断  if (self.allRosterArray.count > 0) {    // 获取用户    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:(DD)item {  NSLog(@"listTVC 接收好友信息 %d", __LINE__);  /**   * 好友信息状态有5种    both - 互为好友    none - 互不为好友    to - 请求添加对方为好友,对方还没有同意    from - 对方添加我为好友,自己还没有同意    remove - 曾经删除的好友   */    // 自己和对方之间的关系  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 - 1 inSection:0];    [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(@"该用户处于下线状态");    }  [email protected]

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、执行请求

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

 

消息气泡

 

 

 

 

ChatTableViewController.m

#import "ChatTableViewController.h"#import "ChatTableViewCell.h"@interface ChatTableViewController ()<XMPPStreamDelegate>@property (nonatomic, strong) NSMutableArray *allMessageArray;@[email protected] 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];  // 当前聊天记录跳到最后一行  if (self.allMessageArray.count > 0) {    NSIndexPath * indexPath = [NSIndexPath indexPathForRow:self.allMessageArray.count - 1 inSection:0];    // 跳到最后一行    [self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];  }  [context save:nil];}#pragma mark - Table view data source- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {  return 1;}- (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的高度并没有自适应  return 70;}#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(@"消息发送失败");[email protected]

自定义cell

  ChatTableViewCell.h

 

#import <UIKit/UIKit.h>@interface ChatTableViewCell : UITableViewCell/// 判断是不是对方发送过来的消息 YES - 对方发送的消息, NO - [email protected] (nonatomic, assign) BOOL isOut;/// [email protected] (nonatomic, copy) NSString *message;@end

 

ChatTableViewCell.m

 

#import "ChatTableViewCell.h"@interface ChatTableViewCell ()/// [email protected](nonatomic,strong)UIImageView * headerImageView;/// [email protected](nonatomic,strong)UIImageView * backgroundImageView;/// [email protected](nonatomic,strong)UILabel * contentLabel;@[email protected] [email protected]- (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-50, 10, 50, 50);    self.headerImageView.image = [UIImage imageNamed:@"nike"];  }else{    self.headerImageView.frame = CGRectMake(0, 10, 50, 50);    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:@"chat_to"] stretchableImageWithLeftCapWidth:45 topCapHeight:40];      self.backgroundImageView.frame = CGRectMake(rect.size.width - self.contentLabel.frame.size.width - 50-20, 10, self.contentLabel.frame.size.width+20, rect.size.height-20);    }else{//接收的      self.backgroundImageView.image = [[UIImage imageNamed:@"chat_from"] stretchableImageWithLeftCapWidth:45 topCapHeight:40];      self.backgroundImageView.frame = CGRectMake(50, 10,self.contentLabel.frame.size.width+40, rect.size.height-20);    }    //因为contentLabel已经自适应文字大小,故不用设置宽高,但需要设置位置    self.contentLabel.center = CGPointMake(self.backgroundImageView.frame.size.width/2.0, self.backgroundImageView.frame.size.height/2.0);      [email protected]