對於 intrinsic content size 不熟悉的童鞋,建議先看一下下面這篇文章,再開始本文的閱讀
只有 20% 的 iOS 程序員能看懂:詳解 intrinsicContentSize 及 約束優先級/content Hugging/content Compression Resistance
iOS 開發中經常需要使用 xib/storyboard 配合 AutoLayout 來做一些界面的佈局適配工作,其中 UILabel、UIButton、UIImageView 等系統控件,在使用相對佈局時候只指定位置不指定大小也可以正常工作,原因就是其藉助 intrinsic content size 功能加了幾條隱式的約束,輔助確定其大小。
那麼我們不禁發問,我們自定義的視圖類能否也能做到像這幾個系統控件一樣,在使用 xib/storyboard 時簡化我們設置佈局,特別是簡化對於“出現多個視圖放不下需要考慮優先壓縮那個視圖”的處理,只需要通過控制 content compression resistance 優先級就可以呢?
答案是肯定的,下面介紹具體怎麼操作。
現在假設系統沒有 UILabel,我自己實現一個具有 Intrinsic Content Size 功能的自定義視圖,命名爲 SmartLabel。
首先,創建繼承自 UIView 的視圖類 SmartLabel,爲了簡化,只支持設置 title(字體、顏色、高度暫時寫死)
@interface SmartLabel : UIView @property (nonatomic, copy) NSString *title; @end
接着,來看下 .m 中的實現
@interface SmartLabel () @property (nonatomic, strong) UILabel *titleLabel; @property (nonatomic, strong) UILabel *tipLabel; @end @implementation SmartLabel - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { [self commonInit]; } return self; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self commonInit]; } return self; } - (void)commonInit { self.backgroundColor = [UIColor redColor]; UILabel *titleLabel = [UILabel new]; titleLabel.font = [self labelsFont]; titleLabel.textColor = [UIColor greenColor]; titleLabel.text = @""; self.titleLabel = titleLabel; [self addSubview:self.titleLabel]; [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.top.trailing.equalTo(self); make.height.equalTo(@25); }]; [self.titleLabel setContentCompressionResistancePriority:1 forAxis:UILayoutConstraintAxisHorizontal]; UILabel *tipLabel = [UILabel new]; tipLabel.font = [self labelsFont]; tipLabel.textColor = [UIColor blueColor]; tipLabel.text = @""; self.tipLabel = tipLabel; [self addSubview:self.tipLabel]; [self.tipLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.trailing.bottom.equalTo(self); make.top.equalTo(self.titleLabel.mas_bottom); }]; [self.tipLabel setContentCompressionResistancePriority:0 forAxis:UILayoutConstraintAxisHorizontal]; } - (void)layoutSubviews { [super layoutSubviews]; self.tipLabel.text = [NSString stringWithFormat:@"width: %.0f", self.size.width]; } - (void)setTitle:(NSString *)title { _title = [title copy]; self.titleLabel.text = _title; [self invalidateIntrinsicContentSize]; } - (CGSize)intrinsicContentSize { CGFloat titleLabelWidth = [_title textSizeForOneLineWithFont:[self labelsFont]].width; return CGSizeMake(titleLabelWidth, 50); } - (UIFont *)labelsFont { static UIFont *s_labelsFont = nil; if (!s_labelsFont) { s_labelsFont = [UIFont systemFontOfSize:12.f]; } return s_labelsFont; } @end
其中重寫 initWithCoder: 是爲了支持 xib/storyboard 中使用
添加一個 tipLabel 方便查看當前視圖的寬度(方便測試效果而已)
其中 titleLabel 和 tipLabel 本身也有 intrinsic content size 生成的隱式約束約束,爲了不影響自定義視圖水平方向上的隱式約束,將其水平方向的 content compression resistance 優先級設置爲 0。
其中字體、顏色、高度各種寫死,只是爲了簡化,自己實現自定義視圖時最好根據需要可配置。
當一些可能影響 intrinsic content size 的屬性發生變化,需要調用 [self invalidateIntrinsicContentSize]; 觸發重新根據 -(CGSize)intrinsicContentSize; 設置隱式約束。最後,我們來測試一下這個視圖在 xib 中的使用
注意圖中標註的 6 個地方,由於系統控件可以直接使用 Intrinsic Size - Default (System Defined),但是自定義視圖需要選擇 Placeholder,裏面的寬高可以先隨便設置,正如其名字所屬,只是先保證 xib/storyboard 中的相對佈局不報錯,具體可以看下 IB 中說明如下圖。
接着看下測試代碼:
@interface SmartLabelTestVC ()
@property (weak, nonatomic) IBOutlet SmartLabel *smartView;
@property (nonatomic, strong) NSTimer *repeatTimer;
@end
@implementation SmartLabelTestVC
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.view.backgroundColor = [UIColor whiteColor];
if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
@weakify(self);
self.repeatTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 repeats:YES block:^(NSTimer *timer) {
@strongify(self);
[self appendTextToSmartView];
if (self.smartView.title.length >= 60) {
[timer invalidate];
timer = nil;
}
}];
[[NSRunLoop mainRunLoop] addTimer:self.repeatTimer forMode:NSRunLoopCommonModes];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:NO animated:animated];
}
- (void)appendTextToSmartView {
static int s_num = 0;
s_num++;
if (s_num >= 10) {
s_num = 0;
}
self.smartView.title = [NSString stringWithFormat:@"%@%d", self.smartView.title ? : @"", s_num];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)dealloc {
NSLog (@"%@ dealloc", [self class]);
}
@end
再來看下運行後的效果:
再修改一下兩者的水平方向上的 content compression resistance 優先級,看下運行效果:
總結下來,自定義視圖支持 intrinsic content size 後也可以像系統控件一樣在 xib/storyboard 中簡化約束的設置,通過控制 content hugging/content compression resistance 的優先級就可以很方便的控制特殊場景下的視圖佈局。這樣可以將原本需要寫在 ViewController 中的繁雜適配邏輯解耦到各個 View 內部,避免了每個使用到的 ViewController 中都要寫繁雜的適配邏輯,所以沒有使用的小夥伴可以考慮大刀耍起來了,珍愛生命,點滴做起~