纯代码实现QQ聊天界面---TableView使用详解
2015-09-16 19:45
696 查看
纯代码实现QQ聊天界面---TableView使用详解
今天模仿QQ的聊天界面写了一个消息界面,希望对学习UITableView的同学可以有帮助首先上一张所有工程文件的图
上面的左边图片是全部工程文件,其中所有素材图片是从QQ界面截出来的,保证原版QQ界面
![](http://static.blog.csdn.net/xheditor/xheditor_emot/default/laugh.gif)
.
上面的图片是运行起来的聊天界面,是使用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];
}
至此,我们就完成了整个界面的全部设计,赶快调试运行一下吧!
!!转载请注明出处!!!
相关文章推荐
- android 代码实现控件之间的间距
- [Android]在代码里运行另一个程序的方法
- 肯特·贝克:改变人生的代码整理魔法
- 网页恶意代码的预防
- 高手写的Tracer-Flash代码调试类代码下载
- CSS代码缩写技巧
- 非主流Q-zOne代码代码搜集第1/2页
- CreateWeb.vbs 代码
- Lua中编译执行代码相关的函数详解
- 更有效率的css代码编写第1/3页
- 代码中到底应不应当写注释?
- SQL语言查询基础:连接查询 联合查询 代码
- 论坛头像随机变换代码
- .NET 常用功能和代码小结
- C#实现压缩HTML代码的方法
- asp编程中常用的javascript辅助代码第1/2页
- C#超实用代码段合集
- Javascript代码在页面加载时的执行顺序介绍
- JS实现图片无间断滚动代码汇总
- 我的论坛源代码(二)