XMPP框架 微信项目开发之Socket聊天室发送数据——获取键盘高度,修改控件的约束值,代码滚动UITabView到指定位置

 在上篇中已经建立了基本的登录和服务器的连接,接下来在此基础之上实现聊天室数据的发送:

新建工程,实现步骤具体如下:

0


1


2


3


4


5


6


7


8


9


10


11


12


13


具体代码如下所示:

<span style="font-size:18px;"><span style="font-size:18px;">//
//  ViewController.m
//  聊天室
//
//  Created by apple on 15/11/4.
//  Copyright (c) 2015年 LiuXun. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<NSStreamDelegate,UITextFieldDelegate,UITableViewDataSource,UITableViewDelegate>
{
    NSInputStream *_inputStream;  // 对应输入流
    NSOutputStream *_outputStream; // 对应输出流
}
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *inputViewConstraint;

@property (weak, nonatomic) IBOutlet UITableView *tableView;

@property(nonatomic, strong) NSMutableArray *chatMsgs; // 聊天消息数组

@end

@implementation ViewController

-(NSMutableArray *)chatMsgs
{
    if (_chatMsgs == nil) {
        
        _chatMsgs = [NSMutableArray array];
    }
    return _chatMsgs;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    //  收发数据
    // 做一个聊天
    // 1. 用户登录
    // 2. 登录成功后即可收发数据
    
    // 监听键盘
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kbFrmWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil];
}

-(void)kbFrmWillChange:(NSNotification *)notification
{
    NSLog(@"%@", notification.userInfo);
    
    // 获取窗口的高度
    CGFloat windowH = [UIScreen mainScreen] .bounds.size.height;
    
    // 键盘结束时的Frame
    CGRect kbEndFrm = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    
    // 获取键盘结束的y值
    CGFloat kbEndY = kbEndFrm.origin.y;
    
    
    self.inputViewConstraint.constant = windowH - kbEndY; // 键盘的高度
}

#pragma mark OC输入输出流协议中的方法
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    NSLog(@"%@", [NSThread currentThread]);
    /*
     NSStreamEventOpenCompleted = 1UL << 0,     // 输入输出流打开完成
     NSStreamEventHasBytesAvailable = 1UL << 1, // 表示有字节可读(说明服务器已经返回数据到客户端)
     NSStreamEventHasSpaceAvailable = 1UL << 2, // 可以发送字节
     NSStreamEventErrorOccurred = 1UL << 3,        // 连接出现错误
     NSStreamEventEndEncountered = 1UL << 4     // 连接结束
     */
    switch (eventCode) {
        case NSStreamEventOpenCompleted:
            NSLog(@"输入输出流打开完成");
            break;
        case NSStreamEventHasBytesAvailable:
            NSLog(@"有字节可读");
            [self readData];
            break;
        case NSStreamEventHasSpaceAvailable:
            NSLog(@"可以发送字节");
            break;
        case NSStreamEventErrorOccurred:
            NSLog(@"连接出现错误");
            break;
        case NSStreamEventEndEncountered:
            NSLog(@"连接结束");
            
            // 关闭输入输出流
            [_inputStream close];
            [_outputStream close];
            
            // 从主运行循环移除
            [_inputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
            [_outputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
            break;
        default :
            break;
    }
}
#pragma mark 连接服务器主机
- (IBAction)connectToHost:(id)sender {
    // 1. 第一步建立连接
    /*  点击参数——>在编辑界面右侧就有此参数的描述
     第一个参数CFAllocatorRef alloc:用于为输入输出流分配空间,如果传为NULL就会使用默认的大小分配
     第二个参数CFStringRef host:所要连接到主机的IP地址。即要连接到哪台主机。
     第三个参数port:所要连接到主机的具体端口号。
     */
    NSString *host = @"127.0.0.1"; // 因为是C语言函数所以要进行桥接转换为C语言字符串
    int port = 12345;
    // 定义输入输出流
    CFReadStreamRef  readStream;
    CFWriteStreamRef  writeStream;
    CFStreamCreatePairWithSocketToHost(NULL,  (__bridge CFStringRef)(host), port, &readStream, &writeStream);
    
    // 将C语言的输入输出流转化为OC对象
    // 如果要想知道连接成功还是失败,可以通过OC中的代理进行实现。所以需要将C语言输入输出流转化为OC对象。
    _inputStream = (__bridge NSInputStream *)(readStream);
    _outputStream = (__bridge NSOutputStream *)(writeStream);
    
    // 设置代理
    _inputStream.delegate = self;
    _outputStream.delegate = self;
    
    // 把输入输出流添加到主运行循环
    // 不添加到主运行循环,代理有可能不工作
    /**
     以下情况需要添加到主运行循环:一是在子线程开启计时器。二是设置网络代理。
     */
    [_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    
    // 打开输入输出流——必须打开输入输出流,才可以建立连接,双方才可以发送数据
    [_inputStream open];
    [_outputStream open];
    
}

#pragma mark 用户登录
- (IBAction)loginBtnClick:(id)sender {
    
    // 登录
    // 发送用户名和密码
    // 在这里做的时候,只发用户名,密码就不用发送
    
    // 如果要登录,发送的数据格式为 "iam:zhangsan"
    // 如果要发送聊天消息,数据格式为"msg:did you have dinner"
    //    uint8_t typedef —> unsigned char uint8_t;
    //   (const uint8_t *) 意味着要传入一个字节数组
    //  maxLength: 表示发送字节数组中的前多少个字节
    
    // 登录的指令
    NSString *loginStr = @"iam:zhangsanXXXXXXX";
    
    // 把Str转化为NSData
    NSData  *data = [loginStr dataUsingEncoding:NSUTF8StringEncoding];
    
    [_outputStream write:data.bytes maxLength:data.length];
    
}

#pragma mark - 读取服服务器返回的数据
-(void)readData
{
    // 建立缓冲区,可以存放1024个字节
    uint8_t buf[1024];
    
    // 返回实际装的字节
    NSInteger  len =  [_inputStream read:buf maxLength:sizeof(buf)];
    
    // 把字节数组转化为字符串
    NSData *data = [NSData dataWithBytes:buf length:len];
    
    // 从服务器接收到的数据
    NSString *reciveStr =  [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
    NSLog(@"%@", reciveStr);
    
    [self reloadDataWithText:reciveStr];
}

#pragma mark 实现TextField的Return键的方法
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
    NSString *text = textField.text;
    NSLog(@"%@", text);
    // 已知:如果要发送聊天消息,数据格式为"msg:did you have dinner"
    
    // 按服务器指定的发送内容的格式拼接字符串
    // 聊天信息
    NSString *msgStr = [NSString stringWithFormat:@"msg:%@", text];
    
    // 将msgStr转化为NSData
    NSData *data = [msgStr dataUsingEncoding:NSUTF8StringEncoding];
    
    // 刷新表格
    [self reloadDataWithText:msgStr];
    
    // 发送数据
    [_outputStream write:data.bytes maxLength:data.length];
    
    // 发送完数据后,清空textField
    textField.text = nil;
    return YES;
}

#pragma mark 表格刷新数据
-(void)reloadDataWithText:(NSString *)text
{
    [self.chatMsgs addObject:text];
    [self.tableView reloadData];
    
    // 数据多的时候表格应该向上滚动
    // 获取表格的最后一行
    NSIndexPath *lastPath = [NSIndexPath indexPathForRow:self.chatMsgs.count-1 inSection:0];
    
    //  让表格滚动到最后一行底部
    [self.tableView scrollToRowAtIndexPath:lastPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}

#pragma mark 表格的数据源
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.chatMsgs.count;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
    }
    
    cell.textLabel.text  = self.chatMsgs[indexPath.row];
    return cell;
}

#pragma mark - 表格滚动时让输入控件收回
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    [self.view endEditing:YES];
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end</span></span>
运行结果如下:



开发步骤如下:

步骤一:建立网络连接。连接服务器主机,需要服务器主机的IP地址、服务器文件内指定的连接端口号、C语言格式的输入输出流。C语言格式的输入输出流为CFReadStreamRef和CFWriteStreamRef,需要注意的是服务器的IP地址为NSString类型需要桥接转化为C语言类型。可以点击错误圆点—>点击错误提示的第一项自动bridge转换。要使用C语言的CFStreamCreatePairWithSocketTOHost语句进行连接。具体如下:

 // 1.第一步建立连接

    /* 点击参数——>在编辑界面右侧就有此参数的描述

     第一个参数CFAllocatorRef alloc:用于为输入输出流分配空间,如果传为NULL就会使用默认的大小分配

     第二个参数CFStringRef host:所要连接到主机的IP地址。即要连接到哪台主机。

     第三个参数port:所要连接到主机的具体端口号。

     */

    NSString *host =@"127.0.0.1"; //因为是C语言函数所以要进行桥接转换为C语言字符串

    int port =12345;

    //定义输入输出流

    CFReadStreamRef  readStream;

    CFWriteStreamRef  writeStream;

    CFStreamCreatePairWithSocketToHost(NULL,  (__bridgeCFStringRef)(host), port, &readStream, &writeStream);

步骤二:将C语言的输入输出流转化为OC的输入输出流对象。因为要使用到OC中NSStreamDelegate协议中的方法。

具体实现如下:

 // C语言的输入输出流转化为OC对象

    // 如果要想知道连接成功还是失败,可以通过OC中的代理进行实现。所以需要将C语言输入输出流转化为OC对象。

    _inputStream = (__bridgeNSInputStream *)(readStream);

    _outputStream = (__bridgeNSOutputStream *)(writeStream);

步骤三:设置为全局的输入输出流设置代理,并让当前控制器遵守NSStreamDelegate协议。如下所示:

......步骤比较多不过都在代码中添加了注释,在此不再奥数。

不过应当掌握的几个其它知识点还是很重要的:

知识点一:监听键盘,修改容器的约束值。

知识点二:让UItabView组件滚动到指定的位置。

PHP写得及时聊天工具,结构设计的很好,有源码。是学习PHP的好资料 JeCat-Jabber(简称:JJ) 是一款完全由 PHP 开发的即时通讯软件。JJ 采用 Gtk2 图像界面库来实现用户界面。顾名思义 JJ 使用 Jabber 协议(XMPP) ,因此可以 和 包括 GTalk 在内的 其它任何 XMPP 即时通讯软件 聊天。 JJ 项目的初衷在于 演示 桌面窗口 和 PHP 多任务处理,这两大 在长期在 PHP程序员 眼中 几乎是“不可能”的任务。 同时 也是 PHP框架 JCAT 的演示项目,JCAT 是一款同时 支持 Web 和 桌面 的 PHP框架,按照计划 将在稍后 发布。 JJ 的特点 > 跨平台。JJ 在Linxu 环境下 开发,在 Windows 平台上也一样可以正常使用。 > 实用 标准的 XMPP协议,能够与其它的 Jabber 软件互相通讯,例如 Google GTalk、Pidgin、PSI、Spark、Pandion,以及其它的 Jabber网页聊天窗口 > 可更换界面皮肤,皮肤样式文件 采用 类似 CSS 的语法,便于美工独立工作。 > 纯 PHP 实现,从 通讯协议 到 图形界面,全部都由 PHP 开发,如果你正好是一名 PHP 程序员,你可以驾轻就熟地在 JJ 之上进行二次开发。 > 可整合到你的网站中。JJ 近期的完善 会使 JJ 更容易地 为你的网站所有,让你的网站 也可以有像 淘宝旺旺 那样的 专用聊天工具 [ PHP 图像界面 ] JJ 采用 Gtk图像界面库,Gtk库 中 提供了丰富的 图形界面窗体(Widget),以及灵活方便的 窗体布局方式。 Gtk 本身 被广泛应用在 Linux 平台上,Linux 最著名 的桌面环境之一 Gnome 即是众多 Gtk软件 的集中展示。 PHP-Gtk2 将 Gtk 窗体绑定到 PHP 语言中,允许程序员 通过 PHP语言 来创建、显示、销毁 Gtk 窗体。PHP-Gtk 项目由 PHP创始人 亲自负责,并且自项目创立一来 一直持续发展,目前已经支持 最新的 Gtk2。 [ PHP 多任务处理 ] PHP 直到 5.2都没有提供 稳定可靠的 多线程特性。 但 PHP 仍然有能力进行多任务处理。 多线程 因为涉及到 线程安全、线程同步 等 困难,在 支持线程的语言中,也常常建议尽量避免使用。 Linux 的多路复用 是 更稳定和安全的 多任务模式,从 PHP5.0 开始 多路复用 支持 Windows 平台。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值