iOS XMPP Framework 07 - 收发消息 上

这一篇开始,我们实现消息的收发功能。

概述

在现有的实现的中,我们已经实现了xmppStream:didReceiveMessage方法,在收到聊天消息时,会使用UIAlertView显示消息的内容。

- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
    
	// A simple example of inbound message handling.
    
	if ([message isChatMessageWithBody])
        {
		XMPPUserCoreDataStorageObject *user = [self.xmppRosterStorage userForJID:[message from]
		                                                         xmppStream:self.xmppStream
		                                               managedObjectContext:[self managedObjectContext_roster]];
		
		NSString *body = [[message elementForName:@"body"] stringValue];
		NSString *displayName = [user displayName];
        
		if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive)
            {
			UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:displayName
                                                                message:body
                                                               delegate:nil
                                                      cancelButtonTitle:@"Ok"
                                                      otherButtonTitles:nil];
			[alertView show];
            }
		else
            {
			// We are not active, so use a local notification instead
			UILocalNotification *localNotification = [[UILocalNotification alloc] init];
			localNotification.alertAction = @"Ok";
			localNotification.alertBody = [NSString stringWithFormat:@"From: %@\n\n%@",displayName,body];
            
			[[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
            }
        }
}
但是,我们需要一个单独的ViewController来实现聊天页面。首先,我们看看这个聊天页面需要有哪些功能:

  • 发送文本消息
  • 发送文件
  • 发送消息到多人聊天室
  • 接收消息
  • 接收文件
  • 从多人聊天室接收消息

还需要考虑:

  • 是否显示好友正在输入状态?
  • 是否在文件接收完毕时显示缩略图?
  • 是否想合并多人聊天和普通聊天?
  • 是否想程序在后台运行时通知用户收到了新的消息?

接下来,我们实现聊天页面以支持消息的收发。


YDChatOverviewController

当我们在菜单选择Chats时,切换到YDChatOverviewController。它将显示最近的聊天会话。

YDConversationViewController

这个类用于显示聊天信息,发送出去的消息和接收到的消息分别位于界面的两侧。当发送或接收了图片后,通过点击显示在界面上的缩略图可以以实际大小查看图片。

数据存储

应用程序使用Core Data来存储聊天记录。

后台运行

真实的聊天程序需要可以在后台运行并接收消息,选择target的Capabilities标签,激活background模式并勾选Voice over IP:


实现Core Data模型

首先在YDAppDelegate.h中添加以下属性和方法声明:

// Core Data
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
在YDAppDelegate.m中synthesize这些属性并在application:didFinishLaunchingWithOptions:中对managedObjectContext进行初始化:

@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
<p class="p1"><span class="s1">    </span><span class="s2">// Setup CoreData System</span></p><p class="p2"><span class="s1">    </span><span class="s2">__managedObjectContext</span><span class="s1"> = </span><span class="s3">self</span><span class="s1">.</span><span class="s2">managedObjectContext</span><span class="s1">;</span></p>

实现CoreData相关的方法:


#pragma mark Chat COREDATA

- (void)saveContext
{
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil)
    {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
        {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

- (NSURL *)applicationDocumentsDirectory
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

// 创建Model
- (NSManagedObjectModel *)managedObjectModel
{
    if (__managedObjectModel != nil)
    {
        return __managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"ChatModel" withExtension:@"momd"];
    __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return __managedObjectModel;
}

// 创建PSC
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (__persistentStoreCoordinator != nil)
    {
        return __persistentStoreCoordinator;
    }
    
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"YDChat.sqlite"];   NSError *error = nil;
    
    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
        
    {
        
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    return __persistentStoreCoordinator;
}

// 创建Context
- (NSManagedObjectContext *)managedObjectContext
{
    if (__managedObjectContext != nil)
    {
        return __managedObjectContext;
    }
    
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil)
    {
        __managedObjectContext = [[NSManagedObjectContext alloc] init];
        [__managedObjectContext setPersistentStoreCoordinator:coordinator];
        [__managedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
        // subscribe to change notifications
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil];
    }
    return __managedObjectContext;
}

//
- (void)_mocDidSaveNotification:(NSNotification *)notification
{
    NSManagedObjectContext *savedContext = [notification object];
    
    // ignore change notifications for the main MOC
    if (__managedObjectContext == savedContext)
    {
        return;
    }
    
    if (__managedObjectContext.persistentStoreCoordinator != savedContext.persistentStoreCoordinator)
    {
        // that's another database
        return;
    }
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        
        [__managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    });
}

创建名为ChatModel的CoreData模型:

这些数据的作用如下表所示:

AttributePurpose
direction
标示消息消息的方向,是收到的消息还是发送出去的消息
filenameAsSent要发送的文件名
groupNumber消息所属的组号
hasMedia是否涉及多媒体(文件)
isGroupMessage消息是否为组消息
isNew是否为新收到的消息
jidString发送方的jid
localFileName设备存储的文件名
mediaType媒体类型
messageBody消息的正文
messageDate消息的日期
messageStatus消息的状态
MimeType发送文件的Mime类型
roomJID聊天室的JID
roomName聊天室的名字

创建继承于NSMangedObject的Chat类:

修改YDChat-Prefix.pch,import Chat.h

#import <Availability.h>

#ifndef __IPHONE_5_0
#warning "This project uses features only available in iOS SDK 5.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
#import "YDDefinitions.h"
#import "YDHelper.h"
#import "Macros.h"
#import "Chat.h"
#endif

XMPPFramework在收到XMPP 消息时,会调用代理的xmppStream:didReceiveMessage:方法,此时,我们就将收到的消息存入到Core Data中。

updateCoreDataWithIncomingMessage:方法的实现如下,通过JID查询发送方的信息,构造Chat对象并将构造好的对象存入到Core Data中,最后发送Notification通知应用程序的其它关心此信息的模块。

-(void)updateCoreDataWithIncomingMessage:(XMPPMessage *)message
{
    //determine the sender
    XMPPUserCoreDataStorageObject *user = [self.xmppRosterStorage userForJID:[message from]
                                                                  xmppStream:self.xmppStream
                                                        managedObjectContext:[self managedObjectContext_roster]];
    
    Chat *chat = [NSEntityDescription
                  insertNewObjectForEntityForName:@"Chat"
                  inManagedObjectContext:self.managedObjectContext];
    chat.messageBody = [[message elementForName:@"body"] stringValue];
    chat.messageDate = [NSDate date];
    chat.messageStatus=@"received";
    chat.direction = @"IN";
    chat.groupNumber=@"";
    chat.isNew = [NSNumber numberWithBool:YES];
    chat.hasMedia=[NSNumber numberWithBool:NO];
    chat.isGroupMessage=[NSNumber numberWithBool:NO];
    chat.jidString = user.jidStr;
    NSError *error = nil;
    if (![self.managedObjectContext save:&error])
    {
        NSLog(@"error saving");
    }
    [[NSNotificationCenter defaultCenter] postNotificationName:kNewMessage object:self userInfo:nil];
}
消息类型分为两大类,一类是完整的消息拥有消息正文,另一类没有消息正文,但是从这种消息我们可以知道,对方是否正在输入,是否处于离开状态等等。

当用户在输入信息时,xmppStream会发送一个状态消息,标示自己正处于录入状态,这样我们就可以在应用程序上显示类似“Peter is typing”的状态信息。

修改YDDefinitions.h在其中添加两个通知的定义:

//Notifications
#define kChatStatus                 @"kChatStatus"
#define kNewMessage                 @"kNewMessage"

修改didReceiveMessage方法,根据消息的种类,生成不同类型的通知
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
    DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
    // A simple example of inbound message handling.
    if ([message isChatMessageWithBody])
    {
        DDLogInfo(@"Save message in CoreData: %@", message);
        [self updateCoreDataWithIncomingMessage:message];
    }
    else if ([message isChatMessage])
    {
        NSArray *elements = [message elementsForXmlns:@"http://jabber.org/protocol/chatstates"];
        if ([elements count] >0)
        {
            for (NSXMLElement *element in elements)
            {
                NSString *statusString = @" ";
                NSString *cleanStatus = [element.name stringByReplacingOccurrencesOfString:@"cha:" withString:@""];
                if ([cleanStatus isEqualToString:@"composing"])
                    statusString = @" is typing";
                else if ([cleanStatus isEqualToString:@"active"])
                    statusString = @" is ready";
                else  if ([cleanStatus isEqualToString:@"paused"])
                    statusString = @" is pausing";
                else  if ([cleanStatus isEqualToString:@"inactive"])
                    statusString = @" is not active";
                else  if ([cleanStatus isEqualToString:@"gone"])
                    statusString = @" left this chat";
                NSMutableDictionary *m = [[NSMutableDictionary alloc] init];
                [m setObject:statusString forKey:@"msg"];
                [[NSNotificationCenter defaultCenter] postNotificationName:@"kChatStatus" object:self userInfo:m];
                DDLogInfo(@"PETER %@", statusString);
            }
        }
    }
}

下一节继续实现YDChatOverviewController



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值