首页 > 代码库 > iOS_29仿微信聊天界面

iOS_29仿微信聊天界面

最终效果图:





自定义cell的封装

BeyondCell

//
//  BeyondCell.h
//  29_仿微信聊天
//
//  Created by beyond on 14-9-4.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import <UIKit/UIKit.h>
@class BeyondCellFrame;

@interface BeyondCell : UITableViewCell


// 一行自定义的cell,初始化的时候,全部生成各个控件并添加到contentView,然后通过cellWithCellFrame方法,将参数CellFrame(内含Msg对象)的所有成员frame和数据 设置到cell中的各个控件上面去



// 返回xib界面上写的重用cellID
+ (NSString *)cellID;


// 通过一个WeiboFrames模型对象(它本身就含有一个Weibo数据 模型),返回一个填充好数据的cell对象
- (BeyondCell *)cellWithCellFrame:(BeyondCellFrame *)cellFrame;

@end

//
//  BeyondCell.m
//  29_仿微信聊天
//
//  Created by beyond on 14-9-4.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import "BeyondCell.h"
#import "BeyondCellFrame.h"
#import "Msg.h"

// 类扩展,又叫匿名分类
@interface BeyondCell()
{
    // 1,头像
    UIImageView *_headImg;
    // 2,正文内容
    UILabel *_content;
    // 3,大图片
    UIImageView *_bgImg;
}
@end
@implementation BeyondCell
// 返回xib界面上写的重用cellID
+ (NSString *)cellID
{
    return @"BeyondCell";
}

// 当池中没有Cell的时候,创建出一个纯洁的Cell,一次性alloc 出所有的各个子控件 ,并加到contentView
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        
        //选中cell后背景无颜色
        self.selectionStyle = UITableViewCellSelectionStyleNone;
        self.backgroundColor = [UIColor clearColor];
        
        
        
        // 不管三七二十一,先把所有的控件实例化,并添加到contentView里面
        // 1,头像
        _headImg = [[UIImageView alloc]init];
        _headImg.layer.cornerRadius = 10;
        _headImg.layer.masksToBounds = YES;
        [self.contentView addSubview:_headImg];
        
       
        
        
        
        // 2,大图片
        _bgImg = [[UIImageView alloc]init];
        [self.contentView addSubview:_bgImg];
        
        
        // 3,正文内容,添加大背景图片里面
        _content = [[UILabel alloc]init];
        _content.backgroundColor = [UIColor clearColor];

        // 正文内容用的字体,宏定义在.h
        _content.font = kContentFnt;
        _content.numberOfLines = 0;
        _content.lineBreakMode = NSLineBreakByWordWrapping;
        [_bgImg addSubview:_content];
        
        
    }
    return self;
}


// 通过一个Frames模型对象(它本身就含有一个数据 模型),返回一个填充好数据的cell对象,将参数Frames(内含对象)的所有成员frames和数据 设置到cell中的各个控件上面去
- (BeyondCell *)cellWithCellFrame:(BeyondCellFrame *)cellFrame
{
    Msg *msg = cellFrame.msg;
    
    
    // 将模型对象中的所有属性值,全部赋值到cell对象中的成员控件上显示
    // 1,头像
    
    
    
    if ([msg.name isEqualToString:@"nana"]) {
        _headImg.image = [UIImage imageNamed:@"icon01.jpg"];
    } else {
        _headImg.image = [UIImage imageNamed:@"icon02.jpg"];
    }
    
    
    // 5,正文内容
    _content.text = msg.content;
    
    // 6,大图片
    if ([msg.name isEqualToString:@"nana"]) {
        _bgImg.image = [UIImage imageStretchedWithName:@"chatfrom_bg_normal.png" xPos:0.5 yPos:0.6];

    } else {
        _bgImg.image = [UIImage
                    imageStretchedWithName:@"chatto_bg_normal.png" xPos:0.5 yPos:0.6];
    }
    
    
    
    
    // 1,头像的frame
    _headImg.frame = cellFrame.headImgFrame;
    

    
    
    // 2,正文的frame
    _content.frame = cellFrame.contentFrame;
    
    // 3,bigImg的frame
    _bgImg.frame = cellFrame.contentBgImgFrame;
    
    
    

    
    
    return self;
}

@end


封装的数据源Model

//
//  Msg.h
//  29_仿微信聊天
//
//  Created by beyond on 14-9-4.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  模型,成员:icon,正文text

#import <Foundation/Foundation.h>

// 内容 用的字体
#define kContentFnt [UIFont fontWithName:@"HelveticaNeue" size:18.0f]

@interface Msg : NSObject
// 头像图片名
@property (nonatomic,copy) NSString *headImg;


// 消息内容
@property (nonatomic,copy) NSString *content;

@property (nonatomic,copy) NSString *name;

@property (nonatomic,strong) NSString *recordFileFath;

// 类方法,字典 转 对象 类似javaBean一次性填充
+ (Msg *)msgWithDict:(NSDictionary *)dict;
// 对象方法,设置对象的属性后,返回对象
- (Msg *)initWithDict:(NSDictionary *)dict;

+ (Msg*)msgWithName:(NSString *)name content:(NSString *)content recordFilePath:(NSString *)path;
@end


//
//  Msg.m
//  29_仿微信聊天
//
//  Created by beyond on 14-9-4.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import "Msg.h"

@implementation Msg

// 类方法,字典 转 对象 类似javaBean一次性填充
+ (Msg *)msgWithDict:(NSDictionary *)dict
{
    return [[self alloc]initWithDict:dict];
}

// 对象方法,设置对象的属性后,返回对象
- (Msg *)initWithDict:(NSDictionary *)dict
{
    // 必须先调用父类NSObject的init方法
    if (self = [super init]) {
        // 设置对象自己的属性
        // 通过遍历 将 字典 赋值为对象各个属性
        for (NSString *key in dict) {
            [self setValue:dict[key] forKeyPath:key];
        }
        // 一次性 将 字典 赋值为对象各个属性
        // [self setValuesForKeysWithDictionary:dict];
        
    }
    // 返回填充好的对象
    return self;
}

+ (Msg*)msgWithName:(NSString *)name content:(NSString *)content recordFilePath:(NSString *)path
{
    Msg *msg = [[self alloc]init];
    msg.name = name;
    msg.content = content;
    msg.recordFileFath = path;

    return msg;
}

@end


重点:根据数据源计算frame


//
//  BeyondCellFrame.h
//  29_仿微信聊天
//
//  Created by beyond on 14-9-4.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import <Foundation/Foundation.h>
@class Msg;
// 控件与控件之间的外边距
#define kMargin 7
// 头像的高宽
#define kHeadImgHW 85


@interface BeyondCellFrame : NSObject

// 最大的Y值,就是行高
@property (nonatomic,assign,readonly) CGFloat maxY;

// 重要,拥有一个成员:对象,目的是在控制器中,传递对象进来之后,可以通过此模型对象的数据,计算出所有的frames
@property (nonatomic,strong) Msg *msg;



// 头像 的frame
@property (nonatomic,assign,readonly) CGRect headImgFrame;

// 聊天正文的背景图片 的frame
@property (nonatomic,assign,readonly) CGRect contentBgImgFrame;

// 正文内容 的frame
@property (nonatomic,assign,readonly) CGRect contentFrame;




@end


//
//  BeyondCellFrame.m
//  29_仿微信聊天
//
//  Created by beyond on 14-9-4.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import "BeyondCellFrame.h"
#import "Msg.h"
@implementation BeyondCellFrame



// CellFrame类 唯一的一个方法:设置Msg的时候,可以通过其数据,计算出各个frame,以及最大的Y,也就是行高
- (void)setMsg:(Msg *)msg
{
    _msg = msg;
    
    // 具体的计算各个frame的代码,放在这儿~~~
    
    if ([msg.name isEqualToString:@"nana"]) {
        [self standLeft:msg];
    } else {
        [self standRight:msg];
    }
    
    
}
// 我说的放在右边
- (void)standRight:(Msg *)msg
{
    // 1,头像的frame
    // 头像的x
    CGFloat headImgX = 320 - kHeadImgHW - kMargin;
    // 头像的y
    CGFloat headImgY = 0;
    // 头像的H
    CGFloat headImgH = kHeadImgHW;
    // 头像的W
    CGFloat headImgW = kHeadImgHW;
    _headImgFrame = CGRectMake(headImgX, headImgY, headImgH, headImgW);
    
    
    //===============****************=======================
    
    // 2,bg的frame
    // 宽度W
    CGFloat bgW = 320 - kHeadImgHW - kMargin;
    // x
    CGFloat bgX = 320 - bgW - kHeadImgHW - kMargin;
    // y
    CGFloat bgY = 0;
    
    // CGFloat winWidth = [[UIApplication sharedApplication] statusBarFrame].size.width;
    
    // 高度先假设 H
    CGFloat bgH = 300;
    _contentBgImgFrame = CGRectMake(bgX, bgY, bgW,bgH);
    
    
    //===============****************=======================

    // 3,正文的frame 正文添加到图片里面,以图片的左上角为 0 0
    // x
    CGFloat contentX =  kMargin*1.5;
    // y
    CGFloat contentY = kMargin;
    
    
    // 宽度W 先假设大于一行
    CGFloat contentW = bgW - contentX - kMargin  ;
    CGFloat contentH = 0;
    
    // 判断 内容够不够一行...
    // 根据字体得到NSString的尺寸
    CGSize oneLineSize = [msg.content sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:kContentFnt,NSFontAttributeName, nil]];
    CGFloat oneLineW = oneLineSize.width;
    
    if (oneLineW < contentW) {
        // 如果不够一行
        CGFloat oneLineH = oneLineSize.height;
        contentX =  kMargin * 1.2;
        _contentFrame = CGRectMake(contentX, contentY, oneLineW,oneLineH);
        
        contentH = oneLineH;
        contentW = oneLineW;
        
        // 5,重新调整 contentBgImg的frame的高度
        // 以下三步为OC标准代码,因为OC中不允许直接修该对象中结构体属性的成员的值,要通过中间的临时结构体变量
        CGRect frame = _contentBgImgFrame;
        frame.size.width = contentW + kMargin *3.5;
        frame.size.height = contentH + kMargin * 3 ;
        frame.origin.x = 320 - contentW - headImgW - kMargin*4;
        _contentBgImgFrame = frame;
        
    } else {
        // 如果超过一行,按下面算法计算 高度
        // 根据内容动态设置 高度
        CGRect tmpRect = [msg.content boundingRectWithSize:CGSizeMake(contentW, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:[NSDictionary dictionaryWithObjectsAndKeys:kContentFnt,NSFontAttributeName, nil] context:nil];
        // 高度H
        contentH = tmpRect.size.height;
        _contentFrame = CGRectMake(contentX, contentY, contentW,contentH);
        
        // 5,重新调整 contentBgImg的frame的高度
        // 以下三步为OC标准代码,因为OC中不允许直接修该对象中结构体属性的成员的值,要通过中间的临时结构体变量
        CGRect frame = _contentBgImgFrame;
        frame.size.height = contentH + kMargin * 3 ;
        _contentBgImgFrame = frame;
    }
    
    
    // 8,这个时候就可以计算最大Y 即行高了
    if (headImgH > _contentBgImgFrame.size.height) {
        _maxY = CGRectGetMaxY(_headImgFrame) + kMargin;
    } else {
        _maxY = CGRectGetMaxY(_contentBgImgFrame) + kMargin;
    }
}

- (void)standLeft:(Msg *)msg
{
    // 1,头像的frame
    // 头像的x
    CGFloat headImgX = kMargin;
    // 头像的y
    CGFloat headImgY = kMargin;
    // 头像的H
    CGFloat headImgH = kHeadImgHW;
    // 头像的W
    CGFloat headImgW = kHeadImgHW;
    _headImgFrame = CGRectMake(headImgX, headImgY, headImgH, headImgW);
    
    
    //===============****************=======================
    
    // 4,bg的frame
    // x
    CGFloat bgX = _headImgFrame.size.width + kMargin;
    // y
    CGFloat bgY = _headImgFrame.origin.y;
    
    // CGFloat winWidth = [[UIApplication sharedApplication] statusBarFrame].size.width;
    
    // 宽度W
    CGFloat bgW = 320 - bgX - kMargin;
    
    
    
    // 高度H
    CGFloat bgH = 300;
    _contentBgImgFrame = CGRectMake(bgX, bgY, bgW,bgH);
    
    
    
    
    
    // 4,正文的frame 正文添加到图片里面,以图片的左上角为 0 0
    // x
    CGFloat contentX =  kMargin*3;
    // y
    CGFloat contentY = kMargin;
    
    // CGFloat winWidth = [[UIApplication sharedApplication] statusBarFrame].size.width;
    
    // 宽度W 先假设大于一行
    CGFloat contentW = bgW - contentX - kMargin  ;
    CGFloat contentH = 0;
    
    // 判断 内容够不够一行...
    // 根据字体得到NSString的尺寸
    CGSize oneLineSize = [msg.content sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:kContentFnt,NSFontAttributeName, nil]];
    CGFloat oneLineW = oneLineSize.width;
    
    if (oneLineW < contentW) {
        // 如果不够一行
        CGFloat oneLineH = oneLineSize.height;
        contentX =  kMargin * 2;
        _contentFrame = CGRectMake(contentX, contentY, oneLineW,oneLineH);
        
        contentH = oneLineH;
        contentW = oneLineW;
        
        // 5,重新调整 contentBgImg的frame的高度
        // 以下三步为OC标准代码,因为OC中不允许直接修该对象中结构体属性的成员的值,要通过中间的临时结构体变量
        CGRect frame = _contentBgImgFrame;
        frame.size.width = contentW + kMargin *3.5;
        frame.size.height = contentH + kMargin * 3 ;
        _contentBgImgFrame = frame;
        
    } else {
        // 如果超过一行,按下面算法计算 高度
        // 根据内容动态设置 高度
        CGRect tmpRect = [msg.content boundingRectWithSize:CGSizeMake(contentW, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:[NSDictionary dictionaryWithObjectsAndKeys:kContentFnt,NSFontAttributeName, nil] context:nil];
        // 高度H
        contentH = tmpRect.size.height;
        _contentFrame = CGRectMake(contentX, contentY, contentW,contentH);
        
        // 5,重新调整 contentBgImg的frame的高度
        // 以下三步为OC标准代码,因为OC中不允许直接修该对象中结构体属性的成员的值,要通过中间的临时结构体变量
        CGRect frame = _contentBgImgFrame;
        frame.size.height = contentH + kMargin * 3 ;
        _contentBgImgFrame = frame;
    }
    
    
    // 8,这个时候就可以计算最大Y 即行高了
    if (headImgH > _contentBgImgFrame.size.height) {
        _maxY = CGRectGetMaxY(_headImgFrame) + kMargin;
    } else {
        _maxY = CGRectGetMaxY(_contentBgImgFrame) + kMargin;
    }
}
@end


控制器

//
//  BeyondViewController.m
//  29_仿微信聊天
//
//  Created by beyond on 14-9-2.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import "BeyondViewController.h"
#import "Msg.h"
#import "BeyondCellFrame.h"
#import "BeyondCell.h"

@interface BeyondViewController ()
{
    // 从plist文件中加载的所有weiboFrames(因为它已经含有一个weibo成员),返回所有的对象组成的数组
    NSMutableArray *_msgFrames;
}
@end

@implementation BeyondViewController





- (void)viewDidLoad
{
    [super viewDidLoad];
    // 初始化 对象数组
    _msgFrames = [NSMutableArray array];
}

#pragma mark - UITextField代理,发送请求
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    
    if (textField.text.length == 0){
        return NO;
    }
    
    

    BeyondCellFrame *frame = [[BeyondCellFrame alloc]init];
    // ***********设置的WeiboFrames的成员weibo的同时,进行了复杂的计算,并填充了WeiboFrames各个frame成员
    frame.msg = [Msg msgWithName:@"jackey" content:textField.text recordFilePath:@"NoRecord"];
    // 添加到对象数组
    [_msgFrames addObject:frame];
    [self.tableView reloadData];
    return YES;
}

#pragma mark - UITableView代理方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    //去除cell间隔线
    tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    
    // 返回对象数组的长度
    return _msgFrames.count;
}
// 生成自定义的cell,并传递cellFrame给它,设置好后,返回cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 1,从池中取
    BeyondCell *cell = [tableView dequeueReusableCellWithIdentifier:[BeyondCell cellID]];
    // 2,取不到的时候,创建一个纯洁的WeiboCell
    if (cell == nil) {
        cell = [[BeyondCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[BeyondCell cellID]];
    }
    // 3,设置独一无二的数据
    BeyondCellFrame *frame = [_msgFrames objectAtIndex:indexPath.row];
    cell = [cell cellWithCellFrame:frame];
    return cell;
}
// cellFrame对象数组有每一行的行高,其内部已经计算好了
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    
    // cellFrame的成员有:Msg数据模型对象,以及根据数据模型计算出来的所有的frames,以及最大的Y即对应数据模型的行高
    BeyondCellFrame *frame = [_msgFrames objectAtIndex:indexPath.row];
    return frame.maxY;
}

// 恢复view全屏,并且让键盘退出
- (void)exitKeyboard
{
    [UIView animateWithDuration:0.2 animations:^{
        self.view.frame = CGRectMake(0, 0, self.view.frame.size.width, [[UIScreen mainScreen]bounds].size.height);
        
    }completion:^(BOOL finished){}];
    [_chatInput resignFirstResponder];
}
// 滚至表格最后一行
- (void)scrollToLastCell;
{
    if (_msgFrames.count >1) {
        [self.chatTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:_msgFrames.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
    }
}









iOS_29仿微信聊天界面