您的位置:首页 > 编程语言

纯代码实现QQ聊天界面---TableView使用详解

2015-09-16 19:45 696 查看

纯代码实现QQ聊天界面---TableView使用详解

今天模仿QQ的聊天界面写了一个消息界面,希望对学习UITableView的同学可以有帮助

首先上一张所有工程文件的图

上面的左边图片是全部工程文件,其中所有素材图片是从QQ界面截出来的,保证原版QQ界面

.

上面的图片是运行起来的聊天界面,是使用QQ的匿名聊天的头像.

开始写代码之前我们分析一下整个界面的布局,界面上面是一个TableView的界面,在最下面的输入框是个单独的view,上面添加了三个按钮和一个TextField.这是整个界面布局的大框架.

整个工程实现起来最复杂的地方就是每个消息界面,即TableView的每个单元格的内容.仔细想想单元格的将组成可以猜到,单元格是由头像的ImageView和背景图片的ImageView以及一个显示消息的Lable组成,背景泡泡图片在最里面一层,外面的头像和消息根据发送者和接受者不同有不同的左右排布.上一张爆炸图

搞明白这个界面排布后我们开始上代码

1.首先是APPDelegate里的内容,新建一个视图控制器和导航控制器,并把导航控制器作为window的根视图

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];

    // Override point for customization after application launch.

    self.window.backgroundColor = [UIColor whiteColor];

    [self.window makeKeyAndVisible];

    ChatListViewController *chatListVC = [[ChatListViewController alloc] init];

    UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:chatListVC];

    self.window.rootViewController = navi;

    [chatListVC release];

    [navi release];

    return YES;

}

2.接下来新建一个视图控制器,里面的三个属性就是上面的消息显示界面tableview和下面的输入框界面bottombar,和tableview的datasource

@interface ChatListViewController ()<UITextFieldDelegate,UITableViewDataSource,UITableViewDelegate>

@property(nonatomic, retain)NSMutableArray *datasource;//数据源

@property(nonatomic, retain)UITableView *tableView;//显示消息界面

@property(nonatomic, retain)UIImageView *bottomBar;//发送文本的工具条

@end

@implementation ChatListViewController

- (void)dealloc

{

    [_bottomBar release];

    [_datasource release];

    [_tableView release];

    [super dealloc];

}

-(UIImageView *)bottomBar

{

    if (!_bottomBar) {

        self.bottomBar = [[[UIImageView alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.tableView.bounds), CGRectGetWidth(self.tableView.bounds), 44)] autorelease];

        //打开用户交互

        _bottomBar.userInteractionEnabled = YES;

        _bottomBar.image = [UIImage imageNamed:@"chat_bottom_bg"];

        //添加子视图

        //添加voice按钮

        UIButton *voiceButton = [self buttonWithFrame:CGRectMake(5, 5, 34, 34) imageName:@"chat_bottom_voice_nor" highLightedImageName:@"chat_bottom_voice_press"];

        [_bottomBar addSubview:voiceButton];

        //添加moreinformation按钮

        UIButton *addButton = [self buttonWithFrame:CGRectMake(CGRectGetWidth(self.view.bounds) - 39, 5, 34, 34) imageName:@"chat_bottom_up_nor" highLightedImageName:@"chat_bottom_up_press"];

        [_bottomBar addSubview:addButton];

        //添加smile按钮

        UIButton *smileButton = [self buttonWithFrame:CGRectMake(CGRectGetWidth(self.view.bounds) - 78, 5, 34, 34) imageName:@"chat_bottom_smile_nor" highLightedImageName:@"chat_bottom_smile_press"];

        [_bottomBar addSubview:smileButton];

        //添加输入的textField

        UITextField *inputTextField = [[UITextField alloc] initWithFrame:CGRectMake(44, 5, CGRectGetWidth(self.view.bounds) - 5*5 - 34*3, 34)];

        inputTextField.textAlignment = NSTextAlignmentLeft;

        inputTextField.font = [UIFont boldSystemFontOfSize:15];

        inputTextField.borderStyle = UITextBorderStyleRoundedRect;

        inputTextField.delegate =self;

        inputTextField.returnKeyType = UIReturnKeySend;

        [_bottomBar addSubview:inputTextField];

        [inputTextField release];

    }

    return _bottomBar;

}

- (UIButton *)buttonWithFrame:(CGRect)frame imageName:(NSString *)imageName highLightedImageName:(NSString *)hightLightedImageName

{

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

    button.frame = frame;

    [button setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];

    [button setImage:[UIImage imageNamed:hightLightedImageName] forState:UIControlStateHighlighted];

    return button;

}

-(UITableView *)tableView

{

    if (!_tableView) {

        self.tableView = [[[UITableView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds)- 44) style:UITableViewStylePlain] autorelease];

        _tableView.delegate = self;

        _tableView.dataSource = self;

        _tableView.separatorStyle = UITableViewCellSelectionStyleNone;

        _tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;

        [self.view addSubview:_tableView];

    }

    return _tableView;

}

-(NSMutableArray *)datasource

{

    if (!_datasource) {

        self.datasource = [NSMutableArray array];

    }

    return _datasource;

}

- (void)viewDidLoad {

    [super viewDidLoad];

    self.tableView.backgroundView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"login_bg.jpg"]] autorelease];

    // Uncomment the following line to preserve selection between presentations.

    // self.clearsSelectionOnViewWillAppear = NO;

    

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.

    // self.navigationItem.rightBarButtonItem = self.editButtonItem;

    [self.tableView registerClass:[ReceivedMessage class] forCellReuseIdentifier:@"RECE"];

    [self.tableView registerClass:[SendedMessage class] forCellReuseIdentifier:@"SEND"];

    [self.view addSubview:self.bottomBar];

    //注册观察者,监听键盘frame改变事件

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleKeyboardFrameChanged:) name:UIKeyboardDidChangeFrameNotification object:nil];

}

-(BOOL)textFieldShouldReturn:(UITextField *)textField

{

    if (textField.text.length == 0 || !textField.text) {

        return NO;

    }

    //创建发送model

    MessageModel *sendModel = [MessageModel message];

    sendModel.received = NO;

    sendModel.content = textField.text;

    [self.datasource addObject:sendModel];

    //创建接受model

    MessageModel *receivedModle = [MessageModel message];

    receivedModle.received = YES;

    receivedModle.content = textField.text;

    [self.datasource addObject:receivedModle];

    //刷新数据

    [self.tableView reloadData];

    textField.text = @"";

    //滑动到底部

    [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.datasource.count - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];

    return YES;

}

-(void)handleKeyboardFrameChanged:(NSNotification *)notification

{

    CGRect keyboardFrame = [notification.userInfo[@"UIKeyboardFrameEndUserInfoKey"] CGRectValue];

//    CGFloat delta_y = keyboardFrame.origin.y - self.view.bounds.size.height;

    CGRect keyboardBeginFrame = [notification.userInfo[@"UIKeyboardFrameBeginUserInfoKey"] CGRectValue];

    CGFloat delta_y = keyboardFrame.origin.y - keyboardBeginFrame.origin.y;

    //尝试更改tableview的高和bottombar的Y来避免键盘弹出后盖住输入框

    self.tableView.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), self.tableView.frame.size.height + delta_y);

    self.bottomBar.frame = CGRectMake(0, self.bottomBar.frame.origin.y +delta_y, CGRectGetWidth(self.tableView.bounds), 44);

    if (self.datasource.count != 0) {

        //滑动到可见部分的底部

        NSIndexPath *path = [self.tableView.indexPathsForVisibleRows lastObject];//可见的最后一个单元格的path

        if (path.row < self.datasource.count - 2) {

            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:path.row +2 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];

        }

        

    }

    //原来使用transform来移动view视图,会造成数据显示不全

    //self.view.transform = CGAffineTransformMakeTranslation(0, delta_y);

}

- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

    // Return the number of sections.

    return 1;

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    // Return the number of rows in the section.

    return self.datasource.count;

}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

{

    MessageModel *model = self.datasource[indexPath.row];

    return model.finalRowHeight;

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    MessageModel *model = self.datasource[indexPath.row];

    NSString *identifier = model.received ? @"RECE" : @"SEND";

    BaseMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];

    

    // Configure the cell...

    [cell configureCellWithModle:model];

    return cell;

}

3.接下来我们需要一个model来计算每个单元格的行高和记录里面的文字,新建一个Message类,声明两个属性,content记录文字,contentSize记录单元格大小

@implementation MessageModel

- (void)dealloc

{

    [_content release];

    [super dealloc];

}

+(id)message

{

    return [[[self alloc] init] autorelease];

}

-(CGSize)contentSize

{

    CGRect rect = [self.content boundingRectWithSize:CGSizeMake(200, 10000) options:NSStringDrawingUsesFontLeading |NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont boldSystemFontOfSize:15]} context:nil];

    return rect.size;

}

-(CGFloat)finalRowHeight

{

    return self.contentSize.height + 70;

}

4.因为有两种消息类型,发出的和接收得,而且两种消息显示界面非常相似,为了减少代码量,我们建一个基类,实现消息单元格的每个部分

@implementation BaseMessageCell

- (void)dealloc

{

    [_contentLable release];

    [_userAvator release];

    [_backgroundBubble release];

    [super dealloc];

}

-(UIImage *)resizeImageWithName:(NSString *)name

{

    UIImage *image = [UIImage imageNamed:name];

    //设定拉伸范围,如下设置的效果是把中间四个像素拉伸,这样图像不会发生形变

    CGFloat x_value = image.size.width/2 - 1;

    CGFloat y_value = image.size.height/2 - 1;

    return [image resizableImageWithCapInsets:UIEdgeInsetsMake(y_value, x_value, y_value, x_value)];

}

-(void)configureCellWithModle:(MessageModel *)model

{

    //这个方法是要子类继承重写的,这里写个空方法是为了避免警告

}

-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier

{

    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];

    if (self) {

        self.backgroundColor = [UIColor clearColor];//清除单元格背景颜色

        self.selectionStyle = UITableViewCellAccessoryNone;//设定单元格点击效果为什么都不做

    }

    return self;

}

-(UIImageView *)userAvator

{

    if (!_userAvator) {

        self.userAvator = [[[UIImageView alloc] initWithFrame:CGRectZero] autorelease];

        [self.contentView addSubview:_userAvator];

    }

    return _userAvator;

}

-(UIImageView *)backgroundBubble

{

    if (!_backgroundBubble) {

        self.backgroundBubble = [[[UIImageView alloc] initWithFrame:CGRectZero] autorelease];

        [self.contentView insertSubview:_backgroundBubble atIndex:0];

    }

    return _backgroundBubble;

}

-(UILabel *)contentLable

{

    if (!_contentLable) {

        self.contentLable = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease];

        _contentLable.textAlignment = NSTextAlignmentLeft;

        _contentLable.font = [UIFont boldSystemFontOfSize:15];

        _contentLable.numberOfLines = 0;

        [self.contentView addSubview:_contentLable];

    }

    return _contentLable;

}

5.有了消息的基类,我们就可以新建接收消息的创建类了.在接收消息的类内部只需要实现一个方法,就是对消息基类定义的三个子视图确定最终的frame,content.image属性.

@implementation ReceivedMessage

-(void)configureCellWithModle:(MessageModel *)model

{

    self.userAvator.frame = CGRectMake(5, 5, 40, 40);

    self.userAvator.image = [self resizeImageWithName:@"sec_chat_default_avatar"];

    self.backgroundBubble.image = [self resizeImageWithName:@"chat_recive_nor"];

    //设置lable和背景泡泡的frame

    CGRect lableFrame = CGRectMake(70, 40, [model contentSize].width, [model contentSize].height);

    //CGRectInset可以对第一个参数的大小进行后面两个数在X,Y上做相应调整

    CGRect bubbleFrame = CGRectInset(lableFrame, -20, -20);

    self.backgroundBubble.frame = bubbleFrame;

    self.contentLable.frame = lableFrame;

    self.contentLable.text = model.content;

}

6.同样的方法我们新建一个发送消息单元格的类

@implementation SendedMessage

-(void)configureCellWithModle:(MessageModel *)model

{

    self.userAvator.frame = CGRectMake(CGRectGetWidth(self.bounds) - 45, 5, 40, 40);

    self.userAvator.image = [UIImage imageNamed:@"anon_group_chat_forbid"];

    self.backgroundBubble.image = [self resizeImageWithName:@"chat_send_nor"];

    CGRect lableFrame = CGRectMake(CGRectGetWidth(self.bounds) - 70 - [model contentSize].width, 40, [model contentSize].width, [model contentSize].height);

    CGRect bubbleFrame = CGRectInset(lableFrame, -20, -20);

    self.backgroundBubble.frame = bubbleFrame;

    self.contentLable.frame = lableFrame;

    self.contentLable.text = model.content;

    self.contentLable.textColor = [UIColor whiteColor];

}

至此,我们就完成了整个界面的全部设计,赶快调试运行一下吧!

!!转载请注明出处!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息