PureLayout 是一个不错的autolayout工具,直接写NSLayoutConstraint,无非只有两个API:
[NSLayoutConstraint constraintWithItem:<#(id)#> attribute:<#(NSLayoutAttribute)#> relatedBy:<#(NSLayoutRelation)#> toItem:<#(id)#> attribute:<#(NSLayoutAttribute)#> multiplier:<#(CGFloat)#> constant:<#(CGFloat)#>]
[NSLayoutConstraint constraintsWithVisualFormat:<#(NSString *)#> options:<#(NSLayoutFormatOptions)#> metrics:<#(NSDictionary *)#> views:<#(NSDictionary *)#>]
尽管Apple已经很努力的发明了Visual Format Language ,但是使用还是不是很方便(或许是因为我写的VFL太少了),所以辅助工具应运而生。目前比较出名的有两个:
Masonry
PureLayout
我下载了Masonry感觉比PureLayout略显复杂(文件多)。我个人倾向于简单的东西,所以先看了一下PureLayout的源代码。
PureLayout主要对ALView
(UIView for iOS/NSView for mac)、NSArray
(可以是ALView或者NSLayoutConstraint)、NSLayoutConstraint
进行扩展,核心是把自己的语法翻译成NSLayoutConstraint,然后addConstraint。除了翻译语法以外,还提供了autoInstall功能(声称Constraints当时可以不active),并且还可以设置Priority(避免冲突)和Identifier(方便调试)。
##翻译语法
Masonry的Layout设置确实非常方便:
对单个View:
+ autoCreateConstraintsWithoutInstalling : //(在block中)创建非active的Constraints,可以通过autoInstallConstraints/autoRemoveConstraints来toggle
+ autoSetPriority : forConstraints : //(在block中)创建带有Priority的Constraints
+ autoSetIdentifier : forConstraints : // iOS 7.0+, OS X 10.9+ only(在block中)创建带有Identifier的Constraints
- autoCenterInSuperview : //居中
- autoAlignAxisToSuperviewAxis : //水平、垂直居中
- autoCenterInSuperviewMargins : // iOS 8.0+ only
- autoAlignAxisToSuperviewMarginAxis : // iOS 8.0+ only
- autoPinEdgeToSuperviewEdge :( withInset : ) //某条边距离superview的距离(已经自动修正下、右、Trailing负数问题,同css margin-left)
- autoPinEdgesToSuperviewEdgesWithInsets :( excludingEdge : ) //除了某条边意外margin
- autoPinEdgeToSuperviewMargin : // iOS 8.0+ only
- autoPinEdgesToSuperviewMargins ( ExcludingEdge : ) // iOS 8.0+ only
- autoPinEdge : toEdge : ofView :( withOffset : ) //两个View的变的距离
- autoAlignAxis : toSameAxisOfView :( withOffset :| withMultiplier : ) //和某个view水平、垂直对齐
- autoMatchDimension : toDimension : ofView :( withOffset :| withMultiplier : ) //和某个view的长/宽 关系
- autoSetDimension ( s ) ToSize : //固定长x宽
- autoConstrainAttribute : toAttribute : ofView :( withOffset :| withMultiplier : ) //
- autoPinTo ( Top | Bottom ) LayoutGuideOfViewController : withInset : // iOS only//到上下LayoutGuide的距离
API大体分三类:设置长/宽(可以是具体数值或者相对于其他view);设置到父View的距离(同css margin);设置某条边到其他view的某条边的距离。
对于多个View(NSArray):
- autoAlignViewsToEdge : //类似于PS中的涂层对其按钮。某条边对其
- autoAlignViewsToAxis : //水平、垂直对其
- autoMatchViewsDimension : //设置等宽/等高
- autoSetViewsDimension : toSize : //某条边的大小
- autoSetViewsDimensionsToSize : //按照给定的size设置宽和高
- autoDistributeViewsAlongAxis : alignedTo : withFixedSpacing :( insetSpacing : )( matchedSizes : ) //在水平(垂直)方向按照给定的间距、大小设置
- autoDistributeViewsAlongAxis : alignedTo : withFixedSize :( insetSpacing : )
对于Constraint
,提供了autoInstall/Remove的API来应用(取消)这个(些)Constraint
##如何实现autoInstall/Remove
上面的API,比如
[ aView autoCenterInSuperview ]; //居中显示
可以直接添加约束,也可以生成一个约束但是不添加,通过:
NSArray * constraints = [ UIView autoCreateConstraintsWithoutInstalling : ^ {
[ aView autoCenterInSuperview ];
}];
//这样并没有应用这个约束,可以这样应用:
[ constraints autoInstallConstraints ];
另外Priority
和Identifier
也可以这样设置。实现的秘密在于:
+ ( NSArray * ) autoCreateConstraintsWithoutInstalling :( ALConstraintsBlock ) block
{
NSAssert ( block , @"The constraints block cannot be nil." );
NSArray * createdConstraints = nil ;
if ( block ) {
[[ self al_arraysOfCreatedConstraints ] addObject :[ NSMutableArray new ]]; //添加了一个元素,所以al_preventAutomaticConstraintInstallation返回YES
block (); //执行添加Constraints的方法,但是由于已经设置了al_preventAutomaticConstraintInstallation,所以并没有这的添加到view上
createdConstraints = [ self al_currentArrayOfCreatedConstraints ]; //把Constraints返回
[[ self al_arraysOfCreatedConstraints ] removeLastObject ]; //删除最后一个元素,清除标志位
}
return createdConstraints ;
}
//添加Constraint的方法
- ( void ) al_addConstraint :( NSLayoutConstraint * ) constraint
{
[ ALView al_applyGlobalStateToConstraint : constraint ]; //应用Priority和Identifier
if ([ ALView al_preventAutomaticConstraintInstallation ]) { //al_currentArrayOfCreatedConstraints长度大于1,
[[ ALView al_currentArrayOfCreatedConstraints ] addObject : constraint ]; //放到al_currentArrayOfCreatedConstraints中,不添加。
} else {
[ self addConstraint : constraint ]; //直接添加
}
}
+ ( void ) al_applyGlobalStateToConstraint :( NSLayoutConstraint * ) constraint
{
if ([ ALView al_isExecutingPriorityConstraintsBlock ]) { //是否应用priority
constraint . priority = [ ALView al_currentGlobalConstraintPriority ];
}
# if __PureLayout_MinBaseSDK_iOS_8_0
NSString * globalConstraintIdentifier = [ ALView al_currentGlobalConstraintIdentifier ];
if ( globalConstraintIdentifier ) { //是否应用Identifier
[ constraint autoIdentify : globalConstraintIdentifier ];
}
# endif /* __PureLayout_MinBaseSDK_iOS_8_0 */
}
##API内部/外部调用的方法
PureLayout有一个PureLayout+Internal.h
文件。这个文件都在.m
中引入,所以对于我们自己的项目只引入了PureLayout.h
,是不会有Internal中的方法的。实现了类似于private的功能。
##改进
使用过程中,对于UIView的初始化,可以用:
[ UIView newAutoLayoutView ];
[[ UIView alloc ] initForAutoLayout ];
但是对于UIButton
呢?只能是:
UIButton * btn = [ UIButton buttonWithType : UIButtonTypeInfoDark ];
btn . translatesAutoresizingMaskIntoConstraints = NO ;
所以我修改了initForAutoLayout
方法,可以用:
[[ UIButton buttonWithType : UIButtonTypeInfoDark ] initForAutoLayout ]
已经向作者提交了一个Pull requests ,最后@smileyborg增加了一个新的方法:
Add -[configureForAutoLayout] for initialized view …
This new API method allows you to set translatesAutoresizingMaskIntoConstraints to NO with a chainable method call ( as self is returned) . It is convenient when you want to use a specific factory method or initializer, and configure the view for use with auto layout on the same line.
For example:
UIButton * button = [[ UIButton buttonWithType:UIButtonTypeSystem] configureForAutoLayout];
Closes #75
##一个例子
使用UIScrollView,可以尝试一下,当屏幕旋转的时候,autolayout的威力就显示出来了。
# import "ALiOSDemo11ViewController.h"
# import <PureLayout/PureLayout.h>
@interface ALiOSDemo11ViewController ()
@property UIScrollView * sv ;
@property UIImageView * avatar ;
@property UILabel * userName ;
@property UILabel * time ;
@property UILabel * mainImage ;
@property UILabel * vote ;
@property UIButton * voteBtn ;
@property UIButton * commentBtn ;
@property UIButton * actionBtn ;
@property NSLayoutConstraint * svHeight ;
@property UIView * wrapper ;
@end
@interface UIView ( XDView )
- ( void ) addSubviews :( UIView * ) view ,...;
@end
@implementation UIView ( XDView )
- ( void ) addSubviews :( UIView * ) view , ...{ //注意调用的时候,以nil结尾,否则引用出去了。
va_list args ;
va_start ( args , view );
[ self addSubview : view ];
UIView * v ;
while (( v = va_arg ( args , UIView * ))) {
[ self addSubview : v ];
}
va_end ( args );
}
@end
@implementation ALiOSDemo11ViewController
@synthesize sv , avatar , userName , time , mainImage , vote , voteBtn , commentBtn , actionBtn , wrapper , svHeight ;
- ( void ) loadView {
[ super loadView ];
[ self . view addSubview : sv = [ UIScrollView newAutoLayoutView ]];
[ sv addSubviews : avatar = [ UIImageView newAutoLayoutView ], userName = [ UILabel newAutoLayoutView ], time = [ UILabel newAutoLayoutView ], mainImage = [ UILabel newAutoLayoutView ], vote = [ UILabel newAutoLayoutView ], voteBtn = [ UIButton newAutoLayoutView ], commentBtn = [ UIButton newAutoLayoutView ], actionBtn = [ UIButton newAutoLayoutView ], nil ];
//with color
self . view . backgroundColor = [ UIColor blueColor ];
sv . backgroundColor = [ UIColor yellowColor ];
avatar . backgroundColor = [ UIColor redColor ];
mainImage . backgroundColor = [ UIColor redColor ];
userName . textColor = [ UIColor blueColor ];
vote . textColor = [ UIColor blueColor ];
time . textColor = [ UIColor grayColor ];
for ( UIButton * btn in @[ voteBtn , commentBtn , actionBtn ]) {
btn . backgroundColor = [ UIColor lightGrayColor ];
btn . layer . cornerRadius = 2 ;
[ btn setTitleColor :[ UIColor darkGrayColor ] forState : UIControlStateNormal ];
btn . contentEdgeInsets = UIEdgeInsetsMake ( 5 , 10 , 5 , 10 );
}
//set size
[ sv autoPinEdgesToSuperviewEdgesWithInsets : ALEdgeInsetsZero ];
[ sv autoMatchDimension : ALDimensionWidth toDimension : ALDimensionWidth ofView : self . view withOffset : 0 ];
[ avatar autoSetDimensionsToSize : CGSizeMake ( 32 , 32 )];
avatar . layer . cornerRadius = 16 ;
[ avatar autoPinEdgeToSuperviewEdge : ALEdgeLeading withInset : 15 ];
[ avatar autoPinEdgeToSuperviewEdge : ALEdgeTop withInset : 15 ];
userName . text = @"Steve Jobs" ;
[ userName autoPinEdge : ALEdgeLeading toEdge : ALEdgeTrailing ofView : avatar withOffset : 10 ];
time . text = @"7小时" ;
[ time autoPinEdgeToSuperviewEdge : ALEdgeTrailing withInset : 15 ];
[@[ avatar , userName , time ] autoAlignViewsToAxis : ALAxisHorizontal ];
mainImage . numberOfLines = 0 ;
mainImage . text = @"In my example 'body' is a UITextView, but it could be anything else. If you happen to be using a UITextView as well note that in order for it to grow vertically it must have a height constraint that gets set in viewDidLayoutSubviews. So add the following constraint in viewDidLoad and keep a reference to it:In my example 'body' is a UITextView, but it could be anything else. If you happen to be using a UITextView as well note that in order for it to grow vertically it must have a height constraint that gets set in viewDidLayoutSubviews. So add the following constraint in viewDidLoad and keep a reference to it:In my example 'body' is a UITextView, but it could be anything else. If you happen to be using a UITextView as well note that in order for it to grow vertically it must have a height constraint that gets set in viewDidLayoutSubviews. So add the following constraint in viewDidLoad and keep a reference to it:In my example 'body' is a UITextView, but it could be anything else. If you happen to be using a UITextView as well note that in order for it to grow vertically it must have a height constraint that gets set in viewDidLayoutSubviews. So add the following constraint in viewDidLoad and keep a reference to it:In my example 'body' is a UITextView, but it could be anything else. If you happen to be using a UITextView as well note that in order for it to grow vertically it must have a height constraint that gets set in viewDidLayoutSubviews. So add the following constraint in viewDidLoad and keep a reference to it:In my example 'body' is a UITextView, but it could be anything else. If you happen to be using a UITextView as well note that in order for it to grow vertically it must have a height constraint that gets set in viewDidLayoutSubviews. So add the following constraint in viewDidLoad and keep a reference to it:" ;
[ mainImage autoPinEdgeToSuperviewEdge : ALEdgeLeading withInset : 5 ];
[ mainImage autoPinEdgeToSuperviewEdge : ALEdgeTrailing withInset : 5 ];
[ mainImage autoMatchDimension : ALDimensionWidth toDimension : ALDimensionWidth ofView : self . view withOffset :- 10 ];
[ mainImage autoPinEdge : ALEdgeTop toEdge : ALEdgeBottom ofView : avatar withOffset : 10 ];
vote . text = @"12次赞" ;
[ vote autoPinEdge : ALEdgeTop toEdge : ALEdgeBottom ofView : mainImage withOffset : 10 ];
[ vote autoPinEdgeToSuperviewEdge : ALEdgeLeading withInset : 15 ];
[ voteBtn setTitle : @"赞" forState : UIControlStateNormal ];
[ commentBtn setTitle : @"评论" forState : UIControlStateNormal ];
[ actionBtn setTitle : @"……" forState : UIControlStateNormal ];
[ voteBtn autoPinEdgeToSuperviewEdge : ALEdgeLeading withInset : 15 ];
[ commentBtn autoPinEdge : ALEdgeLeading toEdge : ALEdgeTrailing ofView : voteBtn withOffset : 4 ];
[ actionBtn autoPinEdgeToSuperviewEdge : ALEdgeTrailing withInset : 15 ];
[@[ voteBtn , commentBtn , actionBtn ] autoAlignViewsToAxis : ALAxisHorizontal ];
[ voteBtn autoPinEdge : ALEdgeTop toEdge : ALEdgeBottom ofView : vote withOffset : 10 ];
[ sv autoPinEdge : ALEdgeBottom toEdge : ALEdgeBottom ofView : voteBtn withOffset : 10 ];
}
- ( void ) viewDidLoad {
[ super viewDidLoad ];
// Do any additional setup after loading the view.
}
- ( void ) didReceiveMemoryWarning {
[ super didReceiveMemoryWarning ];
// Dispose of any resources that can be recreated.
}
/*
# pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end