現在IOS的屏幕尺寸也有很多,為了方便屏幕適配,autolayout能在我們在UI布局方面節省很多工作量,今天就講一下在UITableView中如何使用AutoLayout,具體怎么實現我就不講了,網上也有很多資料,我下面也會給出一個實現Demo,今天就一些細節方面,做一些知識的整理。
Demo gitHub:https://github.com/lerpo/UITableView-Autolayout/tree/master
為了方便講解,我先把一些主要的代碼貼出來:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,copy) NSString *name;
@property(nonatomic,copy) NSString *summery;
@property(nonatomic,copy) NSString *imgUrl;
@property(nonatomic,copy) NSString *like;
@property(nonatomic,copy) NSString *date;
@property(nonatomic,copy) NSString *hot;
-(id)initWithName:(NSString *)name summary:(NSString *)summary imgUrl:(NSString *)imgUrl like:(NSString *)like hot:(NSString *)hot date:(NSString *)date;
@end
#import "Person.h"
@implementation Person
-(id)initWithName:(NSString *)name summary:(NSString *)summary imgUrl:(NSString *)imgUrl like:(NSString *)like hot:(NSString *)hot date:(NSString *)date{
if(self = [super init])
{
_name = name;
_summery = summary;
_imgUrl = imgUrl;
_like = like;
_date = date;
_hot = hot;
}
return self;
}
@end
知識
- 定義一個構造函數方便,對象初始化賦值
#import <UIKit/UIKit.h>
@class Person;
@interface PersonTableCellTableViewCell : UITableViewCell
@property(nonatomic,strong) UILabel *name;
@property(nonatomic,strong) UILabel *summary;
@property(nonatomic,strong) UIImageView *headimg;
@property(nonatomic,strong) UIImageView *img;
@property(nonatomic,strong) UILabel *like;
@property(nonatomic,strong) UILabel *hot;
@property(nonatomic,strong) UILabel *date;
@property(nonatomic,strong) UIView *bottomView;
@property(nonatomic,assign) BOOL didSetupConstraints;
-(void)setData:(Person *)person;
@end
#import "PersonTableCellTableViewCell.h"
#import <Masonry.h>
#import "Person.h"
@implementation PersonTableCellTableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if(self){
self.name = [[UILabel alloc] init];
self.name.numberOfLines = 1;
self.name.textAlignment = NSTextAlignmentLeft;
self.name.textColor = [UIColor blackColor];
self.summary = [[UILabel alloc] init];
self.summary.numberOfLines = 0;
[self.summary setLineBreakMode:NSLineBreakByTruncatingTail];
self.summary.textAlignment = NSTextAlignmentCenter;
self.summary.textColor = [UIColor blackColor];
self.headimg = [[UIImageView alloc] init];
self.img = [[UIImageView alloc] init];
self.bottomView = [[UIView alloc] init];
self.like = [[UILabel alloc] init];
self.like.textAlignment = NSTextAlignmentRight;
self.hot = [[UILabel alloc] init];
self.hot.textAlignment = NSTextAlignmentRight;
self.date = [[UILabel alloc] init];
self.date.textAlignment = NSTextAlignmentLeft;
self.contentView.backgroundColor = [UIColor lightTextColor];
[self.contentView addSubview:self.name];
[self.contentView addSubview:self.summary];
[self.contentView addSubview:self.headimg];
[self.contentView addSubview:self.img];
[self.bottomView addSubview:self.date];
[self.bottomView addSubview:self.hot];
[self.bottomView addSubview:self.like];
[self.contentView addSubview:self.bottomView];
}
return self;
}
-(void)updateConstraints
{
if(!self.didSetupConstraints)
{
[self.headimg mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView.mas_left).with.offset(10);
make.top.equalTo(self.contentView.mas_top).with.offset(10);
make.width.mas_lessThanOrEqualTo(@50);
make.height.mas_lessThanOrEqualTo(@50);
}];
[self.name mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.headimg.mas_right).with.offset(10);
make.top.equalTo(self.contentView.mas_top).with.offset(5);
make.height.equalTo(@40);
make.right.equalTo(self.contentView.mas_right).with.offset(-5);
}];
[self.summary mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView.mas_left).with.offset(10);
make.top.equalTo(self.headimg.mas_bottom).with.offset(5);
make.right.equalTo(self.contentView.mas_right).with.offset(-10);
make.bottom.equalTo(self.img.mas_top).with.offset(0);
}];
[self.img mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView.mas_left).with.offset(10);
make.top.equalTo(self.summary.mas_bottom).with.offset(0);
make.right.equalTo(self.contentView.mas_right).with.offset(-10);
make.height.mas_lessThanOrEqualTo(@200);
make.bottom.equalTo(self.bottomView.mas_top).with.offset(10);
}];
[self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView.mas_left).with.offset(10);
make.right.equalTo(self.contentView.mas_right).with.offset(-10);
make.bottom.equalTo(self.contentView.mas_bottom).with.offset(-5);
}];
[self.date mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.bottomView.mas_left).with.offset(10);
make.top.equalTo(self.bottomView.mas_top).with.offset(5);
make.bottom.equalTo(self.bottomView.mas_bottom).with.offset(-5);
make.right.equalTo(self.hot.mas_left).with.offset(-5);
}];
[self.date setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
self.date.backgroundColor = [UIColor blueColor];
[self.hot mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.bottomView.mas_top).with.offset(5);
make.right.equalTo(self.like.mas_left).with.offset(-5);
make.bottom.equalTo(self.bottomView.mas_bottom).with.offset(-5);
}];
[self.hot setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
self.hot.backgroundColor = [UIColor yellowColor];
[self.like mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.bottomView.mas_top).with.offset(5);
make.right.equalTo(self.bottomView.mas_right).with.offset(-5);
make.bottom.equalTo(self.bottomView.mas_bottom).with.offset(-5);
}];
self.like.backgroundColor = [UIColor redColor];
[self.like setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
}
self.didSetupConstraints = YES;
[super updateConstraints];
}
-(void)layoutSubviews{
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
self.summary.preferredMaxLayoutWidth = CGRectGetWidth(self.summary.frame);
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
-(void)setData:(Person *)person{
self.name.text = person.name;
self.headimg.image = [UIImage imageNamed:@"bee_00_coughing"];
self.summary.text = person.summery;
self.img.image = [UIImage imageNamed:person.imgUrl];
self.date.text = person.date;
self.like.text = person.like;
self.hot.text = person.hot;
NSLog(@"date****%lf",self.date.intrinsicContentSize.width);
NSLog(@"like****%lf",self.like.intrinsicContentSize.width);
NSLog(@"hot****%lf",self.hot.intrinsicContentSize.width);
}
@end
知識
-
self.summary.numberOfLines = 0;
UILabel自動換行,在不同的iOS版本下表現不一致導致的UI問題
表現為在iOS7以上的系統中,UILabel能夠自動換行,多行顯示的字符串,而在iOS6上面則不會自動換行,直接打省略號。
正常情況下,numberOfLines設置為0,UILabel就會自動換行了。
但是在iOS6下面需要設置preferredMaxLayoutWidth,autolayout才會判斷到折行的位置,才能正確的顯示多行的UILabel
但是 preferredMaxLayoutWidth設置為多少才是正確的呢?
如果你知道一個確切的width當然是最好的,那么直接設置即可,
但是如果UILabel的寬度是自適應的,不確定,那么可以使用如下的代碼設置
self.summary.preferredMaxLayoutWidth = CGRectGetWidth(self.summary.frame);
-
if(!self.didSetupConstraints)
如果你用代碼來設置布局約束,你應該在UITableViewCell子類的updateConstraints方法里面一次性完成。注意,updateConstraints可能不止被調用一次,因此要避免重復添加相同的布局約束。在updateConstraints中,可以將添加布局約束的代碼包在一個if條件語句中(比如用一個叫didSetupConstraints的布爾屬性,運行一次添加布局約束的代碼后就將其設置為YES),以確保不重復添加相同的布局約束。另外,更新已有布局約束的代碼(比如調整布局約束的constant屬性),也應該將它們放置在updateConstraints 中,但是要在didSetupConstraints條件語句的外面,這樣才可以確保每次調用的時候都會被執行。
-
[self.contentView setNeedsLayout];
UIView的setNeedsDisplay和setNeedsLayout方法
-setNeedsLayout方法: 標記為需要重新布局,異步調用layoutIfNeeded刷新布局,不立即刷新,但layoutSubviews一定會被調用
-layoutIfNeeded方法:如果,有需要刷新的標記,立即調用layoutSubviews進行布局(如果沒有標記,不會調用layoutSubviews)
兩個方法都是異步執行的。而setNeedsDisplay會調用自動調用drawRect方法,這樣可以拿到 UIGraphicsGetCurrentContext,就可以畫畫了。而setNeedsLayout會默認調用layoutSubViews,
就可以 處理子視圖中的一些數據。
綜上所訴,setNeedsDisplay方便繪圖,而layoutSubViews方便出來數據。
layoutSubviews在以下情況下會被調用:
1、init初始化不會觸發layoutSubviews。
2、addSubview會觸發layoutSubviews。
3、設置view的Frame會觸發layoutSubviews,當然前提是frame的值設置前后發生了變化。
4、滾動一個UIScrollView會觸發layoutSubviews。
5、旋轉Screen會觸發父UIView上的layoutSubviews事件。
6、改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件。
7、直接調用setLayoutSubviews。
drawRect在以下情況下會被調用:
1、如果在UIView初始化時沒有設置rect大小,將直接導致drawRect不被自動調用。drawRect調用是在Controller->loadView, Controller->viewDidLoad 兩方法之后掉用的.所以不用擔心在控制器中,這些View的drawRect就開始畫了.這樣可以在控制器中設置一些值給View(如果這些View draw的時候需要用到某些變量值).
2、該方法在調用sizeToFit后被調用,所以可以先調用sizeToFit計算出size。然后系統自動調用drawRect:方法。
3、通過設置contentMode屬性值為UIViewContentModeRedraw。那么將在每次設置或更改frame的時候自動調用drawRect:。
4、直接調用setNeedsDisplay,或者setNeedsDisplayInRect:觸發drawRect:,但是有個前提條件是rect不能為0。
以上1,2推薦;而3,4不提倡
drawRect方法使用注意點:
1、若使用UIView繪圖,只能在drawRect:方法中獲取相應的contextRef并繪圖。如果在其他方法中獲取將獲取到一個invalidate的ref并且不能用于畫圖。drawRect:方法不能手動顯示調用,必須通過調用setNeedsDisplay 或者 setNeedsDisplayInRect,讓系統自動調該方法。
2、若使用calayer繪圖,只能在drawInContext: 中(類似于drawRect)繪制,或者在delegate中的相應方法繪制。同樣也是調用setNeedDisplay等間接調用以上方法
3、若要實時畫圖,不能使用gestureRecognizer,只能使用touchbegan等方法來掉用setNeedsDisplay實時刷新屏幕
-
[self.likesetContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
Content Hugging Priority代表控件拒絕拉伸的優先級。優先級越高,控件會越不容易被拉伸。
Content Compression Resistance Priority代表控件拒絕壓縮內置空間的優先級。優先級越高,控件的內置空間會越不容易被壓縮。而這里的內置空間,就是上面講的UIView的intrinsicContentSize。
可以直接通過UIView的setContentHuggingPriority:forAxis方法來設置控件的Content Hugging Priority,其中forAxis參數代表橫向和縱向,本例中只需要設置縱向,所以傳入UILayoutConstraintAxisVertical.
Priority 有以下幾種類型:
static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000; // A required constraint. Do not exceed this.
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750; // This is the priority level with which a button resists compressing its content.
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; // This is the priority level at which a button hugs its contents horizontally.
static const UILayoutPriority UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50; // When you send -[UIView systemLayoutSizeFittingSize:], the size fitting most closely to the target size (the argument) is computed. UILayoutPriorityFittingSizeLevel is the priority level with which the view wants to conform to the target size in that computation. It's quite low. It is generally not appropriate to make a constraint at exactly this priority. You want to be higher or lower.
本例是:
[self.date mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.bottomView.mas_left).with.offset(10);
make.top.equalTo(self.bottomView.mas_top).with.offset(5);
make.bottom.equalTo(self.bottomView.mas_bottom).with.offset(-5);
make.right.equalTo(self.hot.mas_left).with.offset(-5);
}];
[self.date setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
self.date.backgroundColor = [UIColor blueColor];
[self.hot mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.bottomView.mas_top).with.offset(5);
make.right.equalTo(self.like.mas_left).with.offset(-5);
make.bottom.equalTo(self.bottomView.mas_bottom).with.offset(-5);
}];
[self.hot setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
self.hot.backgroundColor = [UIColor yellowColor];
[self.like mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.bottomView.mas_top).with.offset(5);
make.right.equalTo(self.bottomView.mas_right).with.offset(-5);
make.bottom.equalTo(self.bottomView.mas_bottom).with.offset(-5);
}];
self.like.backgroundColor = [UIColor redColor];
[self.like setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
及date的優先級最低,也就是說它最先會被壓縮,hot其次 like最高,也就是說當一行文本顯示這三個控件時,當文本放不下時,首先會壓縮date里面的內容,如果其他兩項的內容太長,以至于date一點都顯示不了,那么就會壓縮hot里面的文本內容。最后才是like.
#import "ViewController.h"
#import "Person.h"
#import "PersonTableCellTableViewCell.h"
static NSString *CellIdentifier = @"CellIdentifier";
@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
@property(nonatomic ,strong) UITableView *tableView;
@property(nonatomic ,copy) NSMutableArray *dataSource;
@property (strong, nonatomic) NSMutableDictionary *offscreenCells;
@end
@implementation ViewController
-(UITableView *)tableView
{
if(_tableView == nil)
{
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
_tableView.estimatedRowHeight = UITableViewAutomaticDimension;
_tableView.estimatedRowHeight = 44.0;
_tableView.delegate = self;
_tableView.dataSource = self;
}
return _tableView;
}
-(NSMutableDictionary *)offscreenCells{
if(_offscreenCells==nil){
_offscreenCells = [NSMutableDictionary dictionary];
}
return _offscreenCells;
}
-(NSMutableArray *)dataSource{
if(_dataSource == nil){
_dataSource = [[NSMutableArray alloc] initWithCapacity:10];
}
return _dataSource;
}
-(void)dataSorces{
Person *person = [[Person alloc] initWithName:@"天ttan"
summary:@"我是7個月大的寶寶,我喜歡聽音樂看電視"
imgUrl:@"bee_16_lunch"
like:@"點贊:100000000000"
hot:@"人氣:100"
date:@"2015-06-09 2015-06-09 2015-06-09"
];
[self.dataSource addObject:person];
Person *person1 = [[Person alloc] initWithName:@"天ttan"
summary:@"我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視我是7個月大的寶寶,我喜歡聽音樂看電視"
imgUrl:@"bee_16_lunch"
like:@"點贊:100"
hot:@"人氣:100000000000"
date:@"2015-06-09"];
[self.dataSource addObject:person1];
Person *person2 = [[Person alloc] initWithName:@"天ttan"
summary:@"我是7個月大的寶寶,我喜歡聽音樂看電視"
imgUrl:@""
like:@"點贊:2300000000000000000"
hot:@"人氣:100000000000000000"
date:@"2015-06-09"];
[self.dataSource addObject:person2];
Person *person3 = [[Person alloc] initWithName:@"天ttan"
summary:@""
imgUrl:@"bee_16_lunch"
like:@"點贊:10"
hot:@"人氣:10"
date:@"2015-06-09"];
[self.dataSource addObject:person3];
[self.dataSource addObject:person];
[self.dataSource addObject:person1];
[self.dataSource addObject:person2];
[self.dataSource addObject:person3];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.tableView];
[self dataSorces];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.dataSource.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
PersonTableCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil){
cell = [[PersonTableCellTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Person *person = self.dataSource[indexPath.row];
[cell setData:person];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
// [cell setNeedsUpdateConstraints]; //
[cell updateConstraintsIfNeeded];//立即觸發約束更新,自動更新布局。
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
PersonTableCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil){
cell = [[PersonTableCellTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Person *person = self.dataSource[indexPath.row];
[cell setData:person];
// [cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
// [cell setNeedsLayout]; //而setNeedsLayout會默認調用layoutSubViews
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
height += 1;
return height;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
知識
- _tableView.estimatedRowHeight = UITableViewAutomaticDimension; _tableView.estimatedRowHeight = 44.0;
在iOS8上,蘋果將許多在iOS8之前比較難實現的東西都內置實現了。為了讓cell實現self-sizing的機制,必須先將tableView的rowHeight
屬性設置為常量UITableViewAutomaticDimension
。然后,只需將tableView的estimatedRowHeight
屬性設置為非零值即可開啟行高估算功能,
這樣做就為tableView上還沒有顯示在屏幕上的cell提供了一個臨時的估算的行高。然后,當cell即將滾入屏幕范圍內的時候,會計算出實際的高度。為了確定每一行的實際高度,tableView會自動讓每個cell基于其contentView的已知固定寬度(tableView的寬度,減去其他額外的,像section index或accessoryView這些寬度)和被加到contentView及其子視圖上的自動布局約束規則來計算contentView
的高度。一旦真正的行高被計算出來后,舊的估算的行高會被更新為這個真實的行高(并且其他任何需要對tableView的contentSize或contentOffset的更改都自動替你完成了)。
一般來說,行高估算值不需要太精確——它只是被用來修正tableView中滾動條的大小的,當你在屏幕上滑動cell的時候,即便估算值不準確,tableView還是能很好地調節滾動條。將tableView的estimatedRowHeight
屬性設置成(在viewDidLoad
或類似的方法中)一個接近于“平均”行高的常量值即可。*只有行高變化很極端的時候(比如相差一個數量級),才會在滾動時產生滾動條“跳躍”的現象。這個時候,你才應當實現tableView:estimatedHeightForRowAtIndexPath:
方法,為每一行返回一個更精確的估算值。
iOS7支持(需要自己實現cell尺寸自適應功能)
- 完成一個完整的布局過程 & 計算cell的高度
首先,為每一個cell都初始化一個離屏(offscreen)實例,為每個重用標示符實例化一個與之對應的cell實例,這些cell完全用于高度計算。(離屏表示cell的引用被存儲在view controller的一個屬性或實例變量之中,并且這個cell絕對不會被用作tableView:cellForRowAtIndexPath:方法的返回值以實際呈現在屏幕上。)接著,這個cell的內容(例如,文本、圖片等等)還必須和會被顯示在table view中的內容完全一致。
然后,強制cell立即更新子視圖的布局,再用cell的contentView調用systemLayoutSizeFittingSize:方法計算出cell所需的高度是多少。使用UILayoutFittingCompressedSize參數可以得到適合cell中所有內容所需的最小尺寸。然后其高度就可以作為tableView:heightForRowAtIndexPath:方法的返回值。
-
height += 1.0f;
要為cell的分割線加上額外的1pt高度。因為分隔線是被加在cell底邊和contentView底邊之間的。