测试已测试 | ✗ |
语言语言 | Objective C++Objective C++ |
许可协议 | MIT |
发布最后发布 | 2014年12月 |
由David Whetstone维护。
依赖 | |
BlocksKit | ~> 2.0 |
libextobjc/EXTScope | ~> 0.4 |
一个简洁的C++ DSL,用于更简洁地定义布局约束。
定义布局约束的标准语法很冗长。例如,为了指定一个按钮相对于另一个按钮的x偏移量,并且它们之间有5点间隙,可以这样写:
self.view addConstraint:[NSLayoutConstraint constraintWithItem:_button2
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:_button1
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:5.0]];
使用布局DSL,这将简单地写成
View(_button2).left == View(_button1).right + 5.0;
或者更自然地写成
View(_button1).right + 5.0 == View(_button2).left;
就是这样。当执行这个表达式时,将创建一个NSLayoutConstraint
对象,并将其自动安装在与_button1
和_button2
最近的共同祖先中。
如果愿意将代码编译为Objective-C++,可以访问更加简洁的约束定义。只需#import "UIView+AutoLayoutDSLSugar.h"
,上述约束可以这样定义:
_button1.right + 5.0 == _button2.left;
现在,这已经相当完美了。
如前所述,仅声明约束表达式就足以在引用视图的最近的共同祖先中安装该约束,但如果你希望自己安装约束,只需在addConstraint:
调用中使用约束表达式代替实际的NSLayoutConstraint
对象即可。将表达式传递给该方法将阻止自动安装约束。
[self.view addConstraint:View(_button1).left == View(_button2).right + 5.0];
基本上,约束表达式可以在需要NSLayoutConstraint
对象的地方使用。例如,可以将多个约束安装如下:
[self.view addConstraints:@[View(_button1).right + 5.0 == View(_button2).left,
View(_button2).right + 5.0 == View(_button3).left]];
但是,为什么这么麻烦,这样不是更简洁吗?
View(_button1).right + 5.0 == View(_button2).left;
View(_button2).right + 5.0 == View(_button3).left;
要指定相对于父视图的视图的约束,只需从View
规范中省略UIView*
参数即可
View().left + 5.0 == View(_button1).left;
可以使用以^
运算符为操作符的约束表达式来指定优先级
View(_button1).left == View(_button2).right + 5.0 ^ 999.0;
可以在必要时通过添加标识符来简化单个约束或一组约束的删除和替换。有几种方法可以完成此操作。
就像添加优先级一样,可以通过使用 ^
运算符将标识符添加到约束表达式中,如下所示
View(_image).width == View(_image).height * aspectRatio ^ @"maintainAspect";
通过为它们分配相同的标识符来分组约束
View(_avatar).left == View().left + 5.0 ^ @"A group of constraints";
View(_label).left == View(_avatar).right + StandardHorizontalGap ^ @"A group of constraints";
但更干净的方法是使用约束分组宏
BeginConstraintGroup(@"A group of constraints")
View(_avatar).left == View().left + 5.0;
View(_label).left == View(_avatar).right + StandardHorizontalGap;
EndConstraintGroup;
要查找具有特定标识符的单个约束
NSLayoutConstraint *constraint = [self.view constraintWithID:@"identifier"];
要查找具有特定标识符的所有约束
NSArray *constraint = [self.view constraintsWithID:@"identifier"];
要查找引用特定视图的所有约束
NSArray *constraints = [self.view constraintsReferencingView:_button1];
要删除约束,只需调用 remove
NSLayoutConstraint *constraint = [self.view constraintWithID:@"identifier"];
[constraint remove];
此DSL要求将源文件编译为Objective-C++(只需更改其扩展名为 .mm)。这可能会引起一些人的担忧。如果您不想处理这些可能引起的问题,您仍然可以通过使用类别在单独的文件中定义所有约束来使用DSL。
我使用了一些非常规的方法来实现我想要的结果。通过从内部 ConstraintBuilder
对象的析构函数中安装新构建的 NSLayoutConstraint
对象来实现约束自动安装的功能。通过在内部 ConstraintBuilder
类上重载转换运算符来实现使用约束表达式替换 NSLayoutConstraint
对象的功能。为了防止多次安装约束,对 ConstraintBuilder
对象的任何转换都将将新构建的 NSLayoutConstraint
对象的所有权转移到调用者,因此当 ConstraintBuilder
对象被销毁时,就没有 NSLayoutConstraint
对象要安装了。遗憾的是,这阻止了直接获取一个指向 自动安装 约束的指针
// The following constraint has not been automatically installed
NSLayoutConstraint *constraint = View(_avatar).left == View().left + 5.0;
这是一个小小的代价,因为自动安装行为可以很容易地被引发
[constraint install];
无论如何,这种 魔幻 的行为可能会被一些人不喜欢
遗憾的是,编译器/静态分析器很可能会尝试警告您,您的独立约束表达式未使用。
防止这种情况的唯一方法是不启用该警告。您可以将约束表达式这样包裹
_Pragma( "clang diagnostic push" )
_Pragma( "clang diagnostic ignored \"-Wunused-value\" " )
View(_avatar).left == View().left + 5.0;
_Pragma( "clang diagnostic pop")
但这很丑陋。为了整理一下,我添加了几个用于包装约束的宏
BeginConstraints
View(_avatar).left == View().left + 5.0;
EndConstraints;
约束分组宏 BeginConstraintGroup()
和 EndConstraintGroup
也会禁用这些警告。
主要表达式语法相当固定,但添加标识符和优先级的方法可能发生变化。目前的代码允许使用 ^
运算符和 ,
运算符来添加这些内容。我对建议的改进持开放态度。
有很多尝试改进默认约束语法的方法。以下是一些引起我注意的方法
AutoLayoutDSL可通过CocoaPods[https://cocoapods.org.cn]获得,要安装,只需将以下行添加到Podfile中
pod AutoLayoutDSL
大卫·惠斯顿 [email protected]
约束表达式是我自己的设计,但是约束识别、分组和自动安装的理念都要归功于艾丽卡·桑德和她优秀著作《iOS 自动布局揭秘》。这些特性相关的代码大多是对这本书中包含样例代码的改编,具体可在此处找到[链接地址]。
AutoLayoutDSL 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。