學iOS也有幾個月了。一直都是純代碼開發,菜鳥入門,到今天還處在Frame時代。恰好近期項目在提審。有點時間可以學學傳說中的AutoLayout。事實上。就是android的相對佈局(RelativeLayout),沒了解以前一直認爲很是神奇,今天學習了一下,才發現AutoLayout也不是那麼神奇不可觸碰.html
在frame時代,一切數據在咱們手中都是一個個座標,咱們所要作的,就是用數據反推控件的大小,而後顯示出來。只是,自從i6出了以後,屏幕的寬度也再也不是固定的了。AutoLayout是大勢所趨。android
在AutoLayout時代,咱們不需要用數據去反推控件大小。而是咱們給控件加入約束,告訴控件。你該在大概哪一個地方,比方,距離SuperVIew的左邊20個點,距離SuperView的上邊10個點,接觸過android開發的人應該會對這個概念比較熟悉。而後系統幫咱們本身主動計算出frame。ios
近期也研究過用Storyboard爲控件加約束。相對於純代碼來講簡單很是多,只是,仍是怕多人開發出現故障,仍是用了純代碼來實現了。git
如題。此次我是使用了Masonry來完畢AutoLayout,這裏有一篇關於Masonry的介紹Masonry介紹與使用實踐:高速上手Autolayout,這個開源項目已經幫咱們將iOS比較複雜的AutoLayout封裝起來,使用起來也比較方便。github
FDTemplateLayoutCell可以幫助咱們計算cell的高度並且緩存起來,使用也是很方便。關於FDTemplateLayoutCell的介紹可以看這篇文章:優化UITableViewCell高度計算的那些事。json
本篇文章所用demo所用到的數據和圖片來自於FDTemplateLayoutCell的demo,本demo也是參考FDTemplateLayoutCell demo的Storyboard佈局,本身用純代碼加上了約束。緩存
ViewController.mless
// // ViewController.m // 結合Masonry和FDTemplateLayoutCell,本身第一個autolayout小demo,數據來自FDTemplateLayoutCell的demo,整個demo是參考FDTemplateLayoutCell demo的Storyboard佈局本身用Masonry加入約束 // // Created by crw on 15/8/13. // Copyright (c) 2015年 crw. All rights reserved. // 原文出處https://github.com/forkingdog/UITableView-FDTemplateLayoutCell #import "ViewController.h" #import "UITableView+FDTemplateLayoutCell.h" #import "FDFeedEntity.h" #import "AutoTableViewCell.h" @interface ViewController ()<UITableViewDataSource,UITableViewDelegate>{ UITableView *mTableView; } @property (nonatomic, strong) NSMutableArray *feedEntitySections; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; mTableView = [[UITableView alloc] initWithFrame:self.view.frame]; [self.view addSubview:mTableView]; mTableView.dataSource = self; mTableView.delegate = self; [mTableView registerClass:[AutoTableViewCell class] forCellReuseIdentifier:@"AutoTableViewCell"]; mTableView.estimatedRowHeight = 200;//預算行高 mTableView.fd_debugLogEnabled = YES;//開啓log打印高度 [self buildTestDataThen:^{ [mTableView reloadData]; }]; } - (void)buildTestDataThen:(void (^)(void))then{ // Simulate an async request dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Data from `data.json` NSString *dataFilePath = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"]; NSData *data = [NSData dataWithContentsOfFile:dataFilePath]; NSDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; NSArray *feedDicts = rootDict[@"feed"]; // Convert to `FDFeedEntity` NSMutableArray *entities = @[].mutableCopy; [feedDicts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [entities addObject:[[FDFeedEntity alloc] initWithDictionary:obj]]; }]; self.feedEntitySections = entities; // Callback dispatch_async(dispatch_get_main_queue(), ^{ !then ?: then(); }); }); } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ AutoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AutoTableViewCell" forIndexPath:indexPath]; [self configureCell:cell atIndexPath:indexPath]; return cell; } - (void)configureCell:(AutoTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath{ cell.fd_enforceFrameLayout = NO; // Enable to use "-sizeThatFits:" if (indexPath.row % 2 == 0) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } else { cell.accessoryType = UITableViewCellAccessoryCheckmark; } cell.entity = self.feedEntitySections[indexPath.row]; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ //高度計算並且緩存 return [tableView fd_heightForCellWithIdentifier:@"AutoTableViewCell" cacheByIndexPath:indexPath configuration:^(AutoTableViewCell *cell) { [self configureCell:cell atIndexPath:indexPath]; }]; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return self.feedEntitySections.count; } -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ [tableView deselectRowAtIndexPath:indexPath animated:YES]; FDFeedEntity *obj = self.feedEntitySections[indexPath.row]; obj.title = @"OH。NO,TITLE CLICK"; obj.content = @"Let our rock!async
。。"; [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end ide
mTableView.estimatedRowHeight = 200;//預算行高
依據FDTemplateLayoutCell,啓動估算行高可以加速高度的計算,下面是原文:
About estimatedRowHeight
estimatedRowHeight helps to delay all cells' height calculation from load time to scroll time.
Feel free to set it or not when you're using FDTemplateLayoutCell.If you use "cacheByIndexPath" API,
setting this estimatedRowHeight property is a better practice for imporve load time, and it DOES NO LONGER
affect scroll performance because of "precache".
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ //高度計算並且緩存 return [tableView fd_heightForCellWithIdentifier:@"AutoTableViewCell" cacheByIndexPath:indexPath configuration:^(AutoTableViewCell *cell) { [self configureCell:cell atIndexPath:indexPath]; }]; }
FDTemplateLayoutCell提供的計算cell高的代碼。輕鬆解決行高計算。並且緩存起來.
接下來。重頭戲都在咱們的AutoTableViewCell.m
// // AutoTableViewCell.m // TableViewAuto // // Created by crw on 15/8/13. // Copyright (c) 2015年 crw. All rights reserved. // #import "AutoTableViewCell.h" #import "Masonry.h" #define margin 10 #define WS(weakSelf) __weak __typeof(&*self)weakSelf = self; @interface AutoTableViewCell(){ MASConstraint *constraint_content;/**<內容上邊距爲5的約束,沒內容時將邊距設置爲0 */ MASConstraint *constraint_mainImageView; MASConstraint *constraint_userNameLabel; } @end @implementation AutoTableViewCell - (void)awakeFromNib { [super awakeFromNib]; // Initialization code self.contentView.bounds = [UIScreen mainScreen].bounds; } - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ if (self == [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { [self setAutoLayout]; } return self; } - (void)addView:(UIView *)view{ [self.contentView addSubview:view]; } - (void)setAutoLayout{ WS(ws); _titleLabel = [[UILabel alloc] init]; _titleLabel.numberOfLines = 0; //_titleLabel.backgroundColor = [UIColor redColor]; [self addView:_titleLabel]; _contentLabel = [[UILabel alloc] init]; _contentLabel.numberOfLines = 0; _contentLabel.font = [UIFont systemFontOfSize:14]; _contentLabel.textColor = [UIColor grayColor]; //_contentLabel.backgroundColor = [UIColor purpleColor]; [self addView:_contentLabel]; _mainImageView = [[UIImageView alloc] init]; _mainImageView.contentMode = UIViewContentModeScaleAspectFill; _mainImageView.clipsToBounds = YES; //_mainImageView.backgroundColor = [UIColor orangeColor]; [self addView:_mainImageView]; _userNameLabel = [[UILabel alloc] init]; //_userNameLabel.backgroundColor = [UIColor greenColor]; _userNameLabel.textColor = [UIColor orangeColor]; _userNameLabel.font = [UIFont systemFontOfSize:12]; [self addView:_userNameLabel]; _timeLabel = [[UILabel alloc] init]; _timeLabel.textColor = [UIColor blueColor]; _timeLabel.font = [UIFont systemFontOfSize:12]; //_timeLabel.backgroundColor = [UIColor blueColor]; [self addView:_timeLabel]; [_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.equalTo(ws.contentView).offset(margin); make.trailing.equalTo(ws.contentView.mas_trailing).offset(-margin); make.top.equalTo(ws.contentView).offset(margin); }]; [_contentLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.equalTo(_titleLabel.mas_left); make.right.equalTo(ws.contentView.mas_right).offset(-margin); //下面設置距離title的邊距,設置兩條優先度不一樣的約束,內容爲空時將優先度高的約束禁用 make.top.equalTo(_titleLabel.mas_bottom).priorityLow();//優先度低,會被優先度高覆蓋 constraint_content = make.top.equalTo(_titleLabel.mas_bottom).offset(5).priorityHigh(); }]; [_mainImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(_titleLabel.mas_left); make.height.greaterThanOrEqualTo(@0); make.right.lessThanOrEqualTo(ws.contentView.mas_right).offset(-margin); make.top.equalTo(_contentLabel.mas_bottom).priorityLow(); constraint_mainImageView = make.top.equalTo(_contentLabel.mas_bottom).offset(5).priorityHigh(); }]; [_userNameLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(_titleLabel.mas_left); make.top.equalTo(_mainImageView.mas_bottom).priorityLow(); constraint_userNameLabel = make.top.equalTo(_mainImageView.mas_bottom).offset(5).priorityHigh(); }]; [_timeLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(ws.contentView.mas_right).offset(-margin); make.top.equalTo(_userNameLabel.mas_top); make.bottom.equalTo(self.contentView.mas_bottom).offset(-margin); }]; } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; // Configure the view for the selected state } - (void)setEntity:(FDFeedEntity *)entity { _entity = entity; self.titleLabel.text = entity.title; self.contentLabel.text = entity.content; self.mainImageView.image = entity.imageName.length > 0 ? [UIImage imageNamed:entity.imageName] : nil; self.userNameLabel.text = entity.username; self.timeLabel.text = entity.time; self.contentLabel.text.length == 0 ?[constraint_content deactivate]:[constraint_content activate]; self.mainImageView.image == nil?[constraint_mainImageView deactivate]:[constraint_mainImageView activate]; self.userNameLabel.text.length== 0 ?在setAutoLayout裏面。是咱們AutoLayout的主要代碼,加需要的view加到contentView。用Masonry給每個view加入了約束。代碼和原生的相比,比較好理解。[constraint_userNameLabel deactivate]:[constraint_userNameLabel activate]; } #if 0 // If you are not using auto layout, override this method - (CGSize)sizeThatFits:(CGSize)size { CGFloat totalHeight = 0; totalHeight += [self.titleLabel sizeThatFits:size].height; totalHeight += [self.contentLabel sizeThatFits:size].height; totalHeight += [self.mainImageView sizeThatFits:size].height; totalHeight += [self.userNameLabel sizeThatFits:size].height; totalHeight += 40; // margins return CGSizeMake(size.width, totalHeight); } #endif @end
- (CGSize)sizeThatFits:(CGSize)sizeFDTemplateLayoutCell支持兩種模式的算高。AutoLayout和Frame.下面是官方原文:
FDTemplateLayoutCell
offers 2 modes for asking cell's height.
Generally, no need to care about modes, it will automatically choose a proper mode by whether you have set auto layout constrants on cell's content view. If you want to enforce frame layout mode, enable this property in your cell's configuration block:
cell.fd_enforceFrameLayout = YES;
And if you're using frame layout mode, you must override -sizeThatFits:
in your customized cell and return content view's height (separator excluded)
- (CGSize)sizeThatFits:(CGSize)size { return CGSizeMake(size.width, A+B+C+D+E+....); }
FDTemplateLayoutCell有兩種計算高度的模式
1.一種是AutoLayout使用的-systemLayoutSizeFittingSize:
2.還有一種是Frame使用的-sizeThatFits:
可以經過fd_enforceFrameLayout = YES 開啓Frame模式,注意。開啓Frame模式需要重寫- (CGSize)sizeThatFits,例如如下:
- (CGSize)sizeThatFits:(CGSize)size { return CGSizeMake(size.width, A+B+C+D+E+....); }