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组件滚动到指定的位置。

相关推荐
<p> <b><span style="font-size:14px;"></span><span style="font-size:14px;background-color:#FFE500;">【Java面试宝典】</span></b><br /> <span style="font-size:14px;">1、68讲视频课,500道大厂Java常见面试题+100个Java面试技巧与答题公式+10万字核心知识解析+授课老师1对1面试指导+无限次回放</span><br /> <span style="font-size:14px;">2、这门课程基于胡书敏老师8年Java面试经验,调研近百家互联网公司及面试官的问题打造而成,从筛选简历和面试官角度,给出能帮助候选人能面试成功的面试技巧。</span><br /> <span style="font-size:14px;">3、通过学习这门课程,你能系统掌握Java核心、数据库、Java框架、分布式组件、Java简历准备、面试实战技巧等面试必考知识点。</span><br /> <span style="font-size:14px;">4、知识点+项目经验案例,每一个都能做为面试的作品展现。</span><br /> <span style="font-size:14px;">5、本课程已经在线下的培训课程中经过实际检验,老师每次培训结束后,都能帮助同学们运用面试技巧,成功找到更好的工作。</span><br /> <br /> <span style="font-size:14px;background-color:#FFE500;"><b>【超人气讲师】</b></span><br /> <span style="font-size:14px;">胡书敏 | 10年大厂工作经验,8年Java面试官经验,5年线下Java职业培训经验,5年架构师经验</span><br /> <br /> <span style="font-size:14px;background-color:#FFE500;"><b>【报名须知】</b></span><br /> <span style="font-size:14px;">上课模式是什么?</span><br /> <span style="font-size:14px;">课程采取录播模式,课程永久有效,可无限次观看</span><br /> <span style="font-size:14px;">课件、课程案例代码完全开放给你,你可以根据所学知识,自行修改、优化</span><br /> <br /> <br /> <span style="font-size:14px;background-color:#FFE500;"><strong>如何开始学习?</strong></span><br /> <span style="font-size:14px;">PC端:报名成功后可以直接进入课程学习</span><br /> <span style="font-size:14px;">移动端:<span style="font-family:Helvetica;font-size:14px;background-color:#FFFFFF;">CSDN 学院APP(注意不是CSDN APP哦)</span></span> </p>
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页