简介
当年苹果放出VFL时,着实花了好几天来试验,熟悉它
对于它的利弊已经有很多朋友的文章讨论过了,不在赘述
我关心的点在于它的不完备性,即没法用VFL来完成所有的约束布局
One useful constraint that cannot be expressed is a fixed aspect ratio (for example,
imageView.width = 2 * imageView.height
). To create such a constraint, you can use
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
.(来源:Auto Layout Guide)
这时候我们就要用
[NSLayoutConstraint constraintWithItem:self.button1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.button2 attribute:NSLayoutAttributeLeft multiplier:1.0 constant:-12.0];
说实话,这个表达形式太丑陋了,所以这篇文章的目的就是扩展VFL的语法,使它能表达 “
fixed aspect ratio”。
然后我们可以使用 @“button1.Right=button2.Left*1.0-12” 来代替那么长一串代码了
不多说先看结果:
界面描述:
- 上图所有空白处的间距都是10
- right1 固定宽高
- left1 高度为right1高度的一半减10,宽度自适应
- left2 高度比left1大20,与left1水平对齐,宽度相等
- right2 left3 高宽相等,高度固定
- right3 填充剩下的部分
对应添加约束代码:
-(
void
)installConstraintsByExtension
{
NSDictionary * viewsDictionary= NSDictionaryOfVariableBindings ( _rightView1 , _rightView2 , _rightView3 , _leftView1 , _leftView2 , _leftView3 );
NSArray * constraintStrings= @[
@"V:|-74-[_rightView1]-10-[_rightView2]-10-[_rightView3]-10-|" ,
@"H:|-10-[_rightView1]-10-[_leftView1]-10-|" ,
@"H:[_rightView1(100@1000)]" ,
@"V:[_rightView1(200@1000)]" ,
@"_leftView1.Top=_rightView1.Top" ,
@"V:[_leftView1][_leftView2]" ,
@"_leftView1.Left=_leftView2.Left" ,
@"_leftView1.Height=_rightView1.Height*0.5-10" ,
@"_leftView2.Height=_leftView1.Height+20.0f" ,
@"[_leftView1(==_leftView2)]" ,
@"H:|-10-[_rightView2]-10-[_leftView3]-10-|" ,
@"H:[_rightView2(==_leftView3)]" ,
@"V:[_rightView2(==_leftView3)]" ,
@"V:[_rightView2(200@1000)]" ,
@"_leftView3.Top=_rightView2.Top" ,
@"H:|-10-[_rightView3]-10-|" ,
] ;
[ NSLayoutConstraint activateConstraints : VFL (constraintStrings, viewsDictionary)];
}
{
NSDictionary * viewsDictionary= NSDictionaryOfVariableBindings ( _rightView1 , _rightView2 , _rightView3 , _leftView1 , _leftView2 , _leftView3 );
NSArray * constraintStrings= @[
@"V:|-74-[_rightView1]-10-[_rightView2]-10-[_rightView3]-10-|" ,
@"H:|-10-[_rightView1]-10-[_leftView1]-10-|" ,
@"H:[_rightView1(100@1000)]" ,
@"V:[_rightView1(200@1000)]" ,
@"_leftView1.Top=_rightView1.Top" ,
@"V:[_leftView1][_leftView2]" ,
@"_leftView1.Left=_leftView2.Left" ,
@"_leftView1.Height=_rightView1.Height*0.5-10" ,
@"_leftView2.Height=_leftView1.Height+20.0f" ,
@"[_leftView1(==_leftView2)]" ,
@"H:|-10-[_rightView2]-10-[_leftView3]-10-|" ,
@"H:[_rightView2(==_leftView3)]" ,
@"V:[_rightView2(==_leftView3)]" ,
@"V:[_rightView2(200@1000)]" ,
@"_leftView3.Top=_rightView2.Top" ,
@"H:|-10-[_rightView3]-10-|" ,
] ;
[ NSLayoutConstraint activateConstraints : VFL (constraintStrings, viewsDictionary)];
}
思路如下:
- 确定语法形式
其实就是一个简单的 objectA.attributeA=objectB.attributeB*multiplier+constant 表达式
为了不和已有的VFL混淆,我们需要识别这种模式,这时候我们要用到正则表达式了,其遵循的模式(以后有机会再解释)为:
#define Pattern @
"[a-zA-Z_][a-zA-Z0-9_]*\\.[a-zA-Z_][a-zA-Z0-9_]*[><=][a-zA-Z_][a-zA-Z0-9_]*\\.[a-zA-Z_][a-zA-Z0-9_]*(\\*[0-9]+(\\.[0-9]+f?)?)?([\\+-][0-9]+(\\.[0-9]+f?)?)?"
- 将字符串解析为约束
这时候,我们需要从字符串中获取信息,构造出一个
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:约束来
所以,我们需要从表达式中
- 获取对象objectA objectB 与实际对象的对应关系
- 在解析时传入对象的键值对字典即可
- 在解析时传入对象的键值对字典即可
- 获取attributeA,attributeB与 NSLayoutAttribute的对应关系
- 一个全局的属性名称对应字典
- 一个全局的属性名称对应字典
- 获取倍率和常数
- 获取属性间的关系
- = 代表NSLayoutRelationEqual
- < 代表 NSLayoutRelationLessThanOrEqual
- > 代表NSLayoutRelationGreaterThanOrEqual
具体实现见源码:
https://github.com/sunuo/Tests.git
语法说明:
objectA.attributeA=objectB.attributeB*multiplier+constant
- 对象和属性的名字由数字字母下划线组成,而且以字母开头,所以要想正确解析出对象的键,不能用self.objectA.attributeA的形式
- multiplier和constant可以不写,此事默认multiplier=1 constant=0
- 当表达式中包含multiplier、constant时,它们必须出现在objectB.attributeB之后
正确语法举例:
- view1.Top=view2.Top
- view1.Top>view2.Top+1
- view1.Top<view2.Top*3
- view1.Top<view2.Top*3+1.0f