我写的源文件整个工程会再第二季中发上来~,存在百度网盘, 感兴趣的童鞋, 可以关注我的博客更新,到时自己去下载~。喵~~~
1.将假数据messages.plist和icon图片文件导入工程中。
#import <UIKit/UIKit.h>
typedef enum {
MessageWhoIsMe,
MessageWHoIsAnother
}MessageWho;
@interface Message : NSObject
@PRoperty (nonatomic, strong) NSString * text;
@property (nonatomic, strong) NSString * time;
@property (nonatomic, assign) MessageWho type;
@property (nonatomic, assign) CGFloat height;
+ (instancetype)messageWithDict:(NSDictionary *)dict;
@end
喵喵~~~~~,好鸟.所有准备工作都做好了。接下去要开始编码了, 啥?这就好了。是滴,接下去可要认真听我分析鸟, 交你为什么要这么写代码。大神请绕道~。
第一步:因为我们使用的是假数据,也就是plist文件中的数据进行模拟。所以我们省略掉从网络中获取数据这一步。我们只需要把plist的数据先加载进来就好了。那么怎么加载数据呢,为了提高程序的效率,我们使用懒加载了加载数据, 也就是在要用到数据的地方, 数据才会自动加载并缓存起来。喵~~~,好像很神奇的样子,吓得我都做地上了。
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
/**
* 存放所有对话的缓存数组
*/
@property (nonatomic, strong) NSArray * messages;
@end
// 懒加载
- (NSArray *)messages {
if (_messages == nil) {
NSString * path = [[NSBundle mainBundle] pathForResource:@"messages" ofType:@"plist"];
NSArray * dictArray = [NSArray arrayWithContentsOfFile: path];
NSMutableArray * messages = [NSMutableArray array];
for (NSDictionary * dict in dictArray) {
Message * message = [Message messageWithDict: dict];
[messages addObject: message];
}
_messages = messages;
}
return _messages;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.messages.count;
}
estimatedRowHeight
,我们只需统一的设置预估属性的大小,或者是为每一行(也就是每个cell)设置预估计的cell高度, 那么就会先调用cellForRowAtPath进行cell对象的创建,并且使用预估计的高度创建初始化的cell,但是当要显示到界面上时候,还会再调用heightForRowAtPath.为了方便, 我直接使用的是提供统一的预估算高度,然后实现这两个方法,如下代码。- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 120;
// Do any additional setup after loading the view, typically from a nib.
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * ID = @"messageCell";
MessageCellTableViewCell * cell = [self.tableView dequeueReusableCellWithIdentifier:ID];
cell.message = self.messages[indexPath.row];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return ((Message *)self.messages[indexPath.row]).height;
}
cell.message = self.messages[indexPath.row];
。我想实现的是将数据模型直接传递给cell对象,应为我们没必要将view层的东西暴露在controller层中,view层的东西都是和数据相关的, 这样我们就能做到逻辑和数据的分离,好处是,打个比方,如果你项目中有10几个controller也需要用到这个cell,那么他们就要重复的在controller中写10多行的操作数据的操作,这是不明智的,也是不好的封装,所以我们应该将这些代码封装进cell对象所属于的类中,cell的事情,cell自己去处理.这样封装,controller就不必关心cell如何处理这些数据,只需要将数据交给cell处理就行。于是我们现在就可以来到UIMessageCell类中,给其加上Message类的message属性,并且重写setter,再setter中,我们需要做3件事情,第一件事情保留message对,第二件事情是进行控件上数据的展示,第三件事是在数据填充完后计算cell的高度,并将其更行到数据模型上。(计算高度的时候我们需要两次使用layoutIfNeeded来强制布局,具体原因看下面注释)@implementation MessageCellTableViewCell
// 取得对应头像的图片名
- (NSString *)getPicture:(MessageWho)who {
return who == MessageWhoIsMe? @"me" : @"other";
}
- (void)setMessage:(Message *)message {
// 1.给控件装数据
_message = message;
_icon.image = [UIImage imageNamed:[self getPicture: message.type]];
[_text setTitle:message.text forState:UIControlStateNormal];
_timeLabel.text = message.time;
// 2.装完数据强制布局, 使得设置按钮高度的值准确, 并且更新约束
[_text layoutIfNeeded];
// 要先强制布局, 这时候更新约束才准确
// 更新约束, 使得按钮的高度此时等于文本的高度。
/*
When creating a custom button—that is a button with the type UIButtonTypeCustom—the frame of the button is set to (0, 0, 0, 0) initially. Before adding the button to your interface, you should update the frame to a more appropriate value.
当按钮是custom样式的时候, 其frmae的值都是零。
按钮里面的label默认是不自动换行的。
*/
[_text updateConstraints:^(MASConstraintMaker *make) {
CGFloat textH = CGRectGetHeight(_text.titleLabel.frame);
make.height.equalTo(textH);
}];
// 3.再次强制布局, 使得约束生效, 这样获取到的按钮高度才准确
[_text layoutIfNeeded];
CGFloat textH = CGRectGetMaxY(_text.frame);
CGFloat iconH = CGRectGetMaxY(_icon.frame);
CGFloat cellH = MAX(textH, iconH) + 10;
// 4.更新cell的高度到模型中
_message.height = cellH;
}
- (void)awakeFromNib {
// 设置自动换行
_text.titleLabel.numberOfLines = 0;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}