QQ聊天界面實現
效果如下:
實現過程:
1、首先實現基本界面
頭像使用 UIImageView :
文字消息使用 UIButton
標簽使用 UILable :水平居中
所有元素在一個cell中,在加載cell時進行判斷顯示和隱藏。
合理設置各個控件之間的約束關系。主要是UIIimageVIew和UIButton頂部對齊,間距為10。UIButton的寬度設置一個約束范圍,比如說 (>=60 && <=300);
底部添加一個UIView ,添加輸入框等。
2、創建模型文件
所有元素在一個cell中,在加載cell時進行判斷顯示和隱藏。
按照message.plist文件內容添加需要的屬性,然后添加一個cellHeight屬性計算cell高度,和一個決定是否顯示時間到cell得屬性hideTime。
#import <UIKit/UIKit.h>
// 枚舉類型,
typedefenum{
SLQMessageTypeMe = 0,
SLQMessageTypeOther = 1
}SLQMessageType;
@interface SLQMessage : NSObject
/*內容*/
@property (strong, nonatomic) NSString *text;
/*時間*/
@property (strong, nonatomic) NSString *time;
/*類型*/
@property (assign, nonatomic) SLQMessageType type;
/*cellHeight*/
@property (assign, nonatomic) CGFloat cellHeight;
/*是否隱藏時間*/
@property (assign, nonatomic,getter=isHideTime) BOOL hideTime;
+ (instancetype)MessageWithDict:(NSDictionary *)dict;
@end
實現文件
#import "SLQMessage.h"
@implementation SLQMessage
+(instancetype)MessageWithDict:(NSDictionary *)dict
{
SLQMessage *message = [[SLQMessage alloc] init];
[message setValuesForKeysWithDictionary:dict];
return message;
}
@end
這里需要注意的就是枚舉類型的使用,如果在一個類中要定義枚舉類型,那么命名規則就是:
以類名開頭后面直接跟操作標識;如 SLQMessage + Type;
3、實現對cell操作的封裝
#import <UIKit/UIKit.h>
@classSLQMessage;
@interface SLQMessageCell : UITableViewCell
/*模型對象*/
@property (strong, nonatomic) SLQMessage *message;
+ (instancetype)cellWithTableView:(UITableView *)tableView;
@end
對tableView的每一個控件拖線建立關聯。然后重寫setter方法,對控件進行賦值。
#import "SLQMessageCell.h"
#import "SLQMessage.h"
//define this constant if you want to use Masonry without the 'mas_' prefix
#define MAS_SHORTHAND
//define this constant if you want to enable auto-boxing for default syntax
#define MAS_SHORTHAND_GLOBALS
#import "Masonry.h"
@interfaceSLQMessageCell ()
@property (weak, nonatomic) IBOutletUILabel *timeLable;
@property (weak, nonatomic) IBOutletUIButton *meBtn;
@property (weak, nonatomic) IBOutletUIImageView *meImage;
@property (weak, nonatomic) IBOutletUIButton *otherBtn;
@property (weak, nonatomic) IBOutletUIImageView *otherImage;
@end
@implementation SLQMessageCell
// 重寫setter方法
- (void)setMessage:(SLQMessage *)message
{
_message = message;
self.backgroundColor = [UIColorbrownColor];
if(message.isHideTime) // 隱藏時間
{
self.timeLable.hidden = YES;
[self.timeLableupdateConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(0); // 高度為0
}];
}
else
{
self.timeLable.text = message.time;
self.timeLable.hidden = NO;
[self.timeLableupdateConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(22);
}];
}
if (message.type == SLQMessageTypeMe)
{
[selfsetShowBtn:self.meBtnWithShowImage:self.meImageWithHideBtn:self.otherBtnWithHideImage:self.otherImage];
}
if (message.type == SLQMessageTypeOther)
{
[selfsetShowBtn:self.otherBtnWithShowImage:self.otherImageWithHideBtn:self.meBtnWithHideImage:self.meImage];
}
}
因為每次顯示cell都要進行計算,將cell的顯示封裝到方法中。
// 顯示隱藏控件並計算控件的高度
- (void)setShowBtn:(UIButton *)showBtn WithShowImage:(UIImageView *)showImage WithHideBtn:(UIButton *)hideBtn WithHideImage:(UIImageView *)hideImage
{
[showBtn setTitle:self.message.textforState:UIControlStateNormal];
// 隱藏其他
hideBtn.hidden = YES;
hideImage.hidden = YES;
// 顯示自己
showBtn.hidden = NO;
showImage.hidden = NO;
// 強制更新
[selflayoutIfNeeded];
// 更新約束,設置按鈕的高度就是textLable的高度
[showBtn updateConstraints:^(MASConstraintMaker *make) {
CGFloat buttonH = showBtn.titleLabel.frame.size.height;//
make.height.equalTo(buttonH);
}];
// 強制更新
[selflayoutIfNeeded];
CGFloat btnMaxY = CGRectGetMaxY(showBtn.frame);
CGFloat imageMaxY = CGRectGetMaxY(showImage.frame);
// 設置cell高度
self.message.cellHeight = MAX(btnMaxY, imageMaxY) + 10;
}
其他方法和以往一樣
+ (instancetype)cellWithTableView:(UITableView *)tableView
{
SLQMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"message"];
return cell;
}
- (void)awakeFromNib {
// Initialization code
// 多行顯示
self.meBtn.titleLabel.numberOfLines=0;
self.otherBtn.titleLabel.numberOfLines=0;
}
4、接下來說說按鈕背景的問題
按鈕背景默認填充整個按鈕,但是默認情況下的填充效果不是很好。
如下代碼:
UIImageView *imageView = [[UIImageView alloc] init];
imageView.frame = CGRectMake(10, 10, 300, 200);
UIImage *image = [UIImage imageNamed:@"chat_send_nor"];
// 方法1 ,設置拉伸間距,默認拉伸中心1*1像素
//image = [image stretchableImageWithLeftCapWidth:image.size.width * 0.5 topCapHeight:image.size.height * 0.5];
// 方法2 設置邊界
UIEdgeInsets edge = UIEdgeInsetsMake(50, 40, 40, 40);
//image = [image resizableImageWithCapInsets:edge ];
// UIImageResizingModeStretch 拉伸模式
// UIImageResizingModeTile 填充模式
image = [image resizableImageWithCapInsets:edge resizingMode:UIImageResizingModeStretch];
// 方法3
// 在images.xcassets中對圖片進行設置
imageView.image = image;
[self.view addSubview:imageView];
// 對比圖片
UIImageView *imageView1 = [[UIImageView alloc] init];
imageView1.frame = CGRectMake(10, 210, 300, 200);
UIImage *image1 = [UIImage imageNamed:@"chat_send_nor"];
imageView1.image = image1;
[self.view addSubview:imageView1];
會出現以下效果,默認是下邊的圖片,所以有必要對圖片進行拉伸。

其中方法3的設置是將圖片導入Image.xcassets中后選中圖片設置。

可以通過代碼設置按鈕的內間距
// 可以這樣設置內間距
UIEdgeInsets edge = UIEdgeInsetsMake(15, 15, 15, 15);
[showBtn setTitleEdgeInsets:edge];
或者直接在按鈕的屬性里設置

設置過間距后,就可以計算btn的高度時,因為textlable的高度不固定,所以讓btn的高度等於textLable 的高度。但是又因為按鈕背景圖片的邊緣有一部分是透明的,如下:紅色是按鈕,藍色是圖片。
所以顯示文字高度會,這里對其按鈕高度 + 30,而textLable默認會水平垂直居中。

5、在控制器中得實現方法和以往的一樣
只需要在這里判斷以下消息顯示的時間是否一致,如果一致就隱藏。
- (NSMutableArray *)messages
{
if (_messages == nil)
{
NSArray *dictArray = [NSArrayarrayWithContentsOfFile:[[NSBundlemainBundle] pathForResource:@"messages.plist"ofType:nil]];
NSMutableArray *tempArray = [NSMutableArrayarray];
// 記錄上一個message,判斷是否顯示時間
SLQMessage *lastMessage = nil;
for (NSDictionary *dict in dictArray)
{
SLQMessage *message = [SLQMessage MessageWithDict:dict];
message.hideTime = [message.time isEqualToString:lastMessage.time];
[tempArray addObject:message];
// 重新賦值
lastMessage = message;
}
_messages = tempArray;
}
return_messages;
}
- (void)viewDidLoad {
[superviewDidLoad];
}
/**
* tableView 行數
*/
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//NSLog(@"%zd",self.messages.count);
returnself.messages.count;
}
/**
* 設置每一個cell
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SLQMessageCell *cell = [SLQMessageCellcellWithTableView:tableView];
cell.message = self.messages[indexPath.row];
return cell;
}
/**
* 設置cell高度
*/
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
SLQMessage *message = self.messages[indexPath.row];
return message.cellHeight;
}
/**
* 給出預估高度
*/
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 200;
}
@end
5、用兩個cell實現界面

// 重寫setter方法
- (void)setMessage:(SLQMessage *)message
{
_message = message;
self.backgroundColor = [UIColorbrownColor];
if(message.isHideTime) // 隱藏時間
{
self.timeLable.hidden = YES;
[self.timeLableupdateConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(0);
}];
}
else
{
self.timeLable.text = message.time; // 顯示時間
self.timeLable.hidden = NO;
[self.timeLableupdateConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(22);
}];
}
//
[self.contentBtnsetTitle:message.textforState:UIControlStateNormal];
// 強制布局
[selflayoutIfNeeded];
// 添加約束
[self.contentBtnupdateConstraints:^(MASConstraintMaker *make) {
CGFloat textLableHeight = self.contentBtn.titleLabel.frame.size.height + 30;
make.height.equalTo(textLableHeight);
}];
[selflayoutIfNeeded];
CGFloat btnMaxY = CGRectGetMaxY(self.contentBtn.frame);
CGFloat iconMaxY = CGRectGetMaxY(self.iconImage.frame);
message.cellHeight = MAX(btnMaxY, iconMaxY);
}
/**
* 返回cell對象
*/
+ (instancetype)cellWithTableView:(UITableView *)tableView andMessage:(SLQMessage *)message
{
NSString *ID = (message.type == SLQMessageTypeMe)?@"me":@"other";
SLQMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
return cell;
}
/**
* 設置每一個cell
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 獲取一個cell,根據類型
SLQMessageCell *cell = [SLQMessageCell cellWithTableView:tableView andMessage:self.messages[indexPath.row]];
cell.message = self.messages[indexPath.row];
return cell;
}
更新: 滾動到最新的一行
/**
* 發送信息
*/
- (IBAction)sendMessage:(id)sender
{
// 獲取文字內容
NSString *message = self.textField.text;
SLQMessageType type = (arc4random_uniform(2));
// 更新模型數據
SLQMessage *mess = [[SLQMessage alloc] init];
mess.time = @"2015-11-23";
mess.text = message;
mess.type = type;
// 設置模型數據,添加到數組
[self.messages addObject:mess];
// 刷新表格
[self.tableViewreloadData];
// 滾動到底部方法
[selfscrollToBottom];
}
// 滾動到底部
- (void)scrollToBottom
{
CGFloat yOffset = 0;
// 如果tableView的高度大於tableView的自有有高度,y軸偏移量就等於contentSize - bounds
if (self.tableView.contentSize.height > self.tableView.bounds.size.height) {
yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height;
}
// 設置偏移量為最底部
[self.tableViewsetContentOffset:CGPointMake(0, yOffset) animated:NO];
}
