Auto Layout是一個基於constraint(約束)的佈局系統,它根據UI元素之間約束關係來調整UI元素的位置和大小。 html
Auto Layout中約束的類對應是NSLayoutConstraint, 而建立NSLayoutConstraint對象主要有兩種方式,第一種是 ios
+ (id)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attribute1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attribute2 multiplier:(CGFloat)multiplier constant:(CGFloat)constant;
上面方法主要意思是,某個view1的attribute1等於(小於或等於/大於或等於)某個view2的attribute2的multiplier倍加上constant。而attribute主要由表示位置(上/下/左/右)和大小(寬/高)的如下幾個值: git
typedef enum: NSInteger { NSLayoutAttributeLeft = 1, NSLayoutAttributeRight, NSLayoutAttributeTop, NSLayoutAttributeBottom, NSLayoutAttributeLeading, NSLayoutAttributeTrailing, NSLayoutAttributeWidth, NSLayoutAttributeHeight, NSLayoutAttributeCenterX, NSLayoutAttributeCenterY, NSLayoutAttributeBaseline, NSLayoutAttributeNotAnAttribute = 0 } NSLayoutAttribute;
簡化一下,使用公式能夠表達爲: github
view1.attribute1 = view2.attribute2 * multiplier + constant
第二種方式是: objective-c
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;
這種方式主要是採用Visual Format Language(可視化格式語言)來描述約束佈局,雖然語法比較簡潔,可是可讀性比較差和容易出錯。 編程
雖然Auto Layout在佈局view方面是很是強大和靈活,可是建立constraint的語法過於繁雜,引用Masonry一個例子: 數組
UIView *superview = self; UIView *view1 = [[UIView alloc] init]; view1.translatesAutoresizingMaskIntoConstraints = NO; view1.backgroundColor = [UIColor greenColor]; [superview addSubview:view1]; UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10); [superview addConstraints:@[ //view1 constraints [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:padding.top], [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeLeft multiplier:1.0 constant:padding.left], [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-padding.bottom], [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeRight multiplier:1 constant:-padding.right], ]];
如此簡單的一個例子都要編寫這麼多行代碼,想象一下若是建立多個view的constraint時會多麼痛苦啊。另外一個方式是採用Visual Format Language (VFL),雖然語法比較簡潔,可是可讀性比較差和容易出錯。 app
Masonry是採用鏈式DSL(Domain-specific language)來封裝NSLayoutConstraint,經過這種方式編寫Auto Layout佈局代碼更加易讀和簡潔。
使用Masonry的MASConstraintMaker來表達相同constraint less
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10); [view1 mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler make.left.equalTo(superview.mas_left).with.offset(padding.left); make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom); make.right.equalTo(superview.mas_right).with.offset(-padding.right); }];
甚至能夠更短 ide
[view1 mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(superview).with.insets(padding); }];
使用Masonry建立constraint來定義佈局的方式有三種:mas_makeConstraints,mas_updateConstraints,mas_remakeConstraints。
使用mas_makeConstraints建立constraint後,你可使用局部變量或屬性來保存以便下次引用它;若是建立多個constraints,你能夠採用數組來保存它們。
// in public/private interface @property (nonatomic, strong) MASConstraint *topConstraint; ... // when making constraints [view1 mas_makeConstraints:^(MASConstraintMaker *make) { self.topConstraint = make.top.equalTo(superview.mas_top).with.offset(padding.top); make.left.equalTo(superview.mas_left).with.offset(padding.left); }]; ... // then later you can call [self.topConstraint uninstall];
有時你須要更新constraint(例如,動畫和調試)而不是建立固定constraint,可使用mas_updateConstraints方法
// this is Apple's recommended place for adding/updating constraints // this method can get called multiple times in response to setNeedsUpdateConstraints // which can be called by UIKit internally or in your code if you need to trigger an update to your constraints - (void)updateConstraints { [self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) { make.center.equalTo(self); make.width.equalTo(@(self.buttonSize.width)).priorityLow(); make.height.equalTo(@(self.buttonSize.height)).priorityLow(); make.width.lessThanOrEqualTo(self); make.height.lessThanOrEqualTo(self); }]; //according to apple super should be called at end of method [super updateConstraints]; }
mas_remakeConstraints與mas_updateConstraints比較類似,都是更新constraint。不過,mas_remakeConstraints是刪除以前constraint,而後再添加新的constraint(適用於移動動畫);而mas_updateConstraints只是更新constraint的值。
- (void)changeButtonPosition { [self.button mas_remakeConstraints:^(MASConstraintMaker *make) { make.size.equalTo(self.buttonSize); if (topLeft) { make.top.and.left.offset(10); } else { make.bottom.and.right.offset(-10); } }]; }
想了解以上三個代碼片斷的更多細節,能夠下載Masonry iOS Examples工程查閱。
Classy是一個能與UIKit無縫結合stylesheet(樣式)系統。它借鑑CSS的思想,但引入新的語法和命名規則。
靈活內嵌的語法
{ } : ; 這些語法符號是可選的,你能夠選擇適合本身的風格來表達stylesheet。
你可使用{ } : ; 來限定stylesheet
$main-color = #e1e1e1; MYCustomView { background-color: $main-color; title-insets: 5, 10, 5, 10; > UIProgressView.tinted { progress-tint-color: black; track-tint-color: yellow; } } ^UIButton.warning, UIView.warning ^UIButton { title-color[state:highlighted]: #e3e3e3; }
或者你使用空格來限定stylesheet
$main-color = #e1e1e1 MYCustomView background-color $main-color title-insets 5, 10, 5, 10 > UIProgressView.tinted progress-tint-color black track-tint-color yellow ^UIButton.warning, UIView.warning ^UIButton title-color[state:highlighted] #e3e3e3
Classy在應用程序Bundle默認查找文件名爲stylesheet.cas的樣式文件。若是你採用這個文件名,你能夠不用作任何東西就能加載樣式文件。
但若是你想指定其餘file path(樣式文件名),你能夠建立[CASStyler defaultStyler]
[CASStyler defaultStyler].filePath = [[NSBundle mainBundle] pathForResource:@"myStyles.cas" ofType:nil];
若是你還想當發生錯誤時,獲取錯誤信息以便於調試,可使用-(void)setFilePath:error:
NSError *error = nil; NSString filePath = [[NSBundle mainBundle] pathForResource:@"myStyles.cas" ofType:nil]; [[CASStyler defaultStyler] setFilePath:filePath error:&error];
若是你是使用Storyboard/Xib組織UI界面,那就須要在main.m的int main(int argc, char * argv[])方法設置 filePath,這樣能夠確保在建立UIWindow以前加載stylesheet。不然(採用手寫UI代碼),你在 AppDelegate.m的- (BOOL)application:didFinishLaunchingWithOptions:方法設置filePath
Live Reload是實時顯示編寫UI代碼效果的關鍵特性,它可以實時檢查stylesheet文件變化,無需從新編譯、構建和運行模擬器,從而極大提升開發速度。
爲了啓用Live Reload,你須要指定stylesheet路徑,而且只運行在模擬器上。
#if TARGET_IPHONE_SIMULATOR NSString *absoluteFilePath = CASAbsoluteFilePath(@"../Styles/stylesheet.cas"); [CASStyler defaultStyler].watchFilePath = absoluteFilePath; #endif
Style Selectors是指定哪一個view使用哪一種樣式的方式。主要有三種方法來指定目標view:
你能夠混合使用三種方法,例子以下:
/* match views * where class is UIButton or UIButton subclass * and styleClass is "large" * and superview class is UITabBar */ UITabBar > ^UIButton.large { }
想了解具體如何使用,請查閱官網Selectors章節
爲了不與Objective-C的message selectors混淆,術語style selectors表示Classy stylesheets的selectors
Classy支持全部UIAppearance的屬性和方法,也支持與UIAppearance無關的不少屬性。Classy使用與UIKit相同屬性命名,因此你沒必要考慮如何將style property映射到Objective-C的property。
UIPageControl類的屬性以下:
@property (nonatomic,retain) UIColor *pageIndicatorTintColor; @property (nonatomic,retain) UIColor *currentPageIndicatorTintColor;
style property的名字採用與objective-c同樣的名字
UIPageControl { pageIndicatorTintColor black currentPageIndicatorTintColor purple }
style property的命名規則採用kebab case
UIPageControl { page-indicator-tint-color black current-page-indicator-tint-color purple }
想了解具體如何使用,請查閱官網Properties章節
在編程中一個很重要的原則就是避免重複,這不只能夠大量減小重複代碼,而且使得代碼更加容易複用和維護。Classy提供三種方式避免代碼重複:grouping,nesting,variables
若是有兩個以上的style selectors共用相同的屬性時
UISlider.info { minimum-track-tint-color black maximum-track-tint-color purple } UISlider.error { minimum-track-tint-color black maximum-track-tint-color purple thumb-tint-color red }
咱們能夠提取相同的屬性到分組style selector中
UISlider.info, UISlider.error { minimum-track-tint-color black maximum-track-tint-color purple } UISlider.error { thumb-tint-color red }
若是兩個以上style selectors共用相同的view hierarchy時
UICollectionView { background-color #a2a2a2 } UICollectionView > UICollectionViewCell { clips-to-bounds NO } UICollectionView > UICollectionViewCell UILabel { text-color purple } UICollectionView > UICollectionViewCell UILabel.title { font 20 }
咱們經過nesting方式將view hierarchies表達成這樣方式
UICollectionView { background-color #a2a2a2 > UICollectionViewCell { clips-to-bounds NO UILabel { text-color purple &.title { font 20 } } } }
Classy讓你經過定義variables來將多個相同的style property值存儲以便共享。Variable命名規則以下:
能夠包含_,-或任何字母數字
// prefix with ' $ ' to help distinguish variables $brand-color = #e1e1e1 // OR not insets = 5, 10, 5, 10 UIButton { background-color $brand-color contentEdgeInsets insets background-image[state:selected] bg_button insets }
最後官方還提供一個實例來解釋具體如何使用:Custom Views Example
ClassyLiveLayout經過結合Classy stylesheets與Masonry一塊兒使用,可以在運行的模擬器中微調Auto Layout約束實時顯示效果的工具。
ClassyLiveLayout一個核心category:UIView+ClassyLayoutProperties,在UIView定義如下屬性:
@property(nonatomic, assign) UIEdgeInsets cas_margin; @property(nonatomic, assign) CGSize cas_size; // shorthand properties for setting only a single constant value @property(nonatomic, assign) CGFloat cas_sizeWidth; @property(nonatomic, assign) CGFloat cas_sizeHeight; @property(nonatomic, assign) CGFloat cas_marginTop; @property(nonatomic, assign) CGFloat cas_marginLeft; @property(nonatomic, assign) CGFloat cas_marginBottom; @property(nonatomic, assign) CGFloat cas_marginRight;
cas_margin和cas_size分別表示UI元素的位置和大小,而其他的屬性都是對兩個屬性進一步細分。咱們能夠從stylesheets中訪問style properties來定義constraints佈局,作到將數據與代碼分離,有利於修改和複用代碼。
UIView.blue-box { cas_size: 80 100 cas_margin-top: 60 cas_margin-left: 50 } UIView.red-box { cas_size-width: 120 cas_margin-left: 20 }
咱們能夠在updateConstraints或updateViewConstrains定義佈局時引用style properties
- (void)updateViewConstraints { [super updateViewConstraints]; [_blueBoxView mas_updateConstraints:^(MASConstraintMaker *make) { make.width.equalTo(@(_blueBoxView.cas_size.width)); make.height.equalTo(@(_blueBoxView.cas_size.height)); make.top.equalTo(@(_blueBoxView.cas_margin.top)); make.left.equalTo(@(_blueBoxView.cas_margin.left)); }]; [_redBoxView mas_updateConstraints:^(MASConstraintMaker *make) { make.width.equalTo(@(_redBoxView.cas_size.width)); make.height.equalTo(_blueBoxView); make.top.equalTo(_blueBoxView); make.left.equalTo(_blueBoxView.mas_right).with.offset(_redBoxView.cas_margin.left); }]; }
當定義view layouts時,將Auto Layout的constraints都放在stylesheets中實時加載(Live reload)。若是你修改constraints,無需從新編譯、構建和運行模擬器便能實時看到修改後的效果。
因爲須要引用Masonry,Classy和ClassyLiveLayout,Podfile配置以下:
pod 'Masonry', '~> 0.6.1' pod 'Classy', '~> 0.2.4' pod 'ClassyLiveLayout', '~> 0.6.0'
當安裝好Masonry,Classy和ClassyLiveLayout後,第一次運行項目會出現沒有stylesheet.cas文件錯誤:
只要向工程添加空的stylesheet.cas文件便可。
在ViewController建立LiveView對象,而後被self.view引用。
當編譯運行時,在SHPAbstractView.h因爲找不到UIView出現編譯錯誤。
只需引入UIKit即可以解決,但運行一下應用程序,出現一下錯誤:
主要緣由是任何自定義UIView繼承SHPAbstractView都須要override兩個方法:- (void)addSubviews和- (void)defineLayout,咱們能夠查看SHPAbstractView的源碼可知:
因此只要在LiveView.m文件覆蓋兩個方法便可
#pragma mark - Add subviews and define layout - (void)addSubviews { } - (void)defineLayout { }
LiveView主要由包含redBoxView和blueBoxView兩個屬性,redBoxView表示紅色方塊,blueBoxView表示藍色方塊。
#import "SHPAbstractView.h" @interface LiveView : SHPAbstractView @property (strong, nonatomic) UIView *redBoxView; @property (strong, nonatomic) UIView *blueBoxView; @end
因爲SHPAbstractView類如何初始化View已經作了處理,暴露兩個接口- (void)addSubviews和-(void)defineLayout分別處理構建view hierarchy和定義佈局,子類只要覆蓋SHPAbstractView這兩個方法就能夠建立LiveView了。
可是咱們將Auto Layout的constraints都放在stylesheets中實時加載(Live reload),即放在本工程的stylesheet.cas文件,將佈局數據和佈局代碼分離。
UIView.redBox { cas_marginTop 50 cas_marginLeft 20 cas_size 100 100 } UIView.blueBox { cas_marginTop 50 cas_marginRight -20 cas_size 100 100 }
有了constraints數據後,即可以在代碼佈局:
@implementation LiveView #pragma mark - Add subviews and define layout - (void)addSubviews { self.backgroundColor = [UIColor whiteColor]; [self addSubview:self.redBoxView]; [self addSubview:self.blueBoxView]; } - (void)defineLayout { [self.redBoxView mas_updateConstraints:^(MASConstraintMaker* make){ make.top.equalTo(@(self.redBoxView.cas_marginTop)); make.left.equalTo(@(self.redBoxView.cas_marginLeft)); make.width.equalTo(@(self.redBoxView.cas_sizeWidth)); make.height.equalTo(@(self.redBoxView.cas_sizeHeight)); }]; [self.blueBoxView mas_updateConstraints:^(MASConstraintMaker *make){ make.top.equalTo(@(self.blueBoxView.cas_marginTop)); make.right.equalTo(@(self.blueBoxView.cas_marginRight)); make.width.equalTo(@(self.blueBoxView.cas_sizeWidth)); make.height.equalTo(@(self.blueBoxView.cas_sizeHeight)); }]; } #pragma mark - Lazy initialization - (UIView*)redBoxView { if (!_redBoxView) { _redBoxView = [UIView new]; _redBoxView.cas_styleClass = @"redBox"; _redBoxView.backgroundColor = [UIColor redColor]; } return _redBoxView; } - (UIView*)blueBoxView { if (!_blueBoxView) { _blueBoxView = [UIView new]; _blueBoxView.cas_styleClass = @"blueBox"; _blueBoxView.backgroundColor = [UIColor blueColor]; } return _blueBoxView; }
爲了啓用Live Reload,你須要指定stylesheet路徑,而且只運行在模擬器上。
示例代碼存放地址:LiveAutoLayout
以前手寫UI代碼每次更改通常都要從新編譯、構建和運行模擬器才能看到效果,但結合使用Masonry,Classy和ClassLiveLayout以後,告別這個費時過程,極大地提升開發速度;不只如此,咱們將Auto Layout的constraints都放在stylesheets中實時加載(Live reload),將佈局數據和佈局代碼分離,使得代碼更加複用和維護。Classy還提供三種避免重複方法:Grouping, Nestting和Variable,儘量複用樣式數據。 這是本人第一次編寫技術博客,可能有不少錯誤和漏洞,但願你們多多指點,也但願這篇文章可以幫助到你們。