该文章阅读的 Masonry 的版本为 1.1.0。
这个类继承自 MASConstraint 类,是 MASCompositeConstraint 类的兄弟类,并且对 NSLayoutConstraint 类进行了封装。
1.公共属性
@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
复制代码
这个属性是目标约束视图及其属性类对象,也就是 view1
+ attr1
。
@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;
复制代码
这个属性是参考约束视图及其属性类对象,也就是 view2
+ attr2
。
2.公共方法
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute;
复制代码
以指定目标约束视图及其属性类对象初始化该类对象。
+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view;
复制代码
返回指定视图作为 view1
上所有安装的约束,
3.私有分类 UIView+MASConstraints
在 .m
文件中,作者写了一个私有的 UIView 的分类,通过关联对象给这个分类添加了一个属性 mas_installedConstraints
,用于保存安装到视图上的视图约束封装对象。
@interface MAS_VIEW (MASConstraints)
@property (nonatomic, readonly) NSMutableSet *mas_installedConstraints;
@end
@implementation MAS_VIEW (MASConstraints)
static char kInstalledConstraintsKey;
- (NSMutableSet *)mas_installedConstraints {
NSMutableSet *constraints = objc_getAssociatedObject(self, &kInstalledConstraintsKey);
if (!constraints) {
constraints = [NSMutableSet set];
objc_setAssociatedObject(self, &kInstalledConstraintsKey, constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return constraints;
}
@end
复制代码
4.类扩展
/**
保存参考约束视图及其属性类对象,也就是 view2 + attr2。
*/
@property (nonatomic, strong, readwrite) MASViewAttribute *secondViewAttribute;
复制代码
/**
保存安装约束的接收视图。
*/
@property (nonatomic, weak) MAS_VIEW *installedView;
复制代码
/**
保存约束对象。
*/
@property (nonatomic, weak) MASLayoutConstraint *layoutConstraint;
复制代码
/**
保存约束关系,也就是 relation。
*/
@property (nonatomic, assign) NSLayoutRelation layoutRelation;
复制代码
/**
保存约束优先级。
*/
@property (nonatomic, assign) MASLayoutPriority layoutPriority;
复制代码
/**
保存约束乘数,也就是 multiplier。
*/
@property (nonatomic, assign) CGFloat layoutMultiplier;
复制代码
/**
保存约束常数,也就是 c。
*/
@property (nonatomic, assign) CGFloat layoutConstant;
复制代码
/**
保存是否已经设置过约束关系。
*/
@property (nonatomic, assign) BOOL hasLayoutRelation;
复制代码
/**
保存约束对象标识
*/
@property (nonatomic, strong) id mas_key;
复制代码
/**
保存安装约束时是否使用动画
*/
@property (nonatomic, assign) BOOL useAnimator;
复制代码
5.实现
5.1 公共方法
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
self = [super init];
if (!self) return nil;
// 保存参数
_firstViewAttribute = firstViewAttribute;
// 设置属性
self.layoutPriority = MASLayoutPriorityRequired;
self.layoutMultiplier = 1;
return self;
}
复制代码
+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view {
// 获取保存的所有元素
return [view.mas_installedConstraints allObjects];
}
复制代码
- (void)setSecondViewAttribute:(id)secondViewAttribute {
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
// 如果传入的约束属性是 NSValue 类型的,就调用父类 MASConstraint 中的方法处理
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
// 如果传入的约束属性是 UIView 类型的,就以传入的视图和 attr1 实例化一个约束视图及其属性类并保存
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
} else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
// 如果传入的约束属性是 MASViewAttribute 类型的,就直接保存。
_secondViewAttribute = secondViewAttribute;
} else {
// 传入的参数,只能是上面三种情况之一
NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
}
}
复制代码
5.2 私有方法
/**
重写了类扩展中添加的属性 layoutConstant 的 setter
*/
- (void)setLayoutConstant:(CGFloat)layoutConstant {
// 保存参数
_layoutConstant = layoutConstant;
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
// 兼容其他系统
if (self.useAnimator) {
[self.layoutConstraint.animator setConstant:layoutConstant];
} else {
self.layoutConstraint.constant = layoutConstant;
}
#else
// 直接将参数赋值给约束对象的 constant 属性
self.layoutConstraint.constant = layoutConstant;
#endif
}
复制代码
/**
重写了类扩展中添加的属性 layoutRelation 的 setter
*/
- (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {
// 保存参数
_layoutRelation = layoutRelation;
// 保存状态
self.hasLayoutRelation = YES;
}
复制代码
/**
判断当前系统版本是否支持 isActive 属性,iOS8 之后的版本支持这个属性,用于版本兼容
*/
- (BOOL)supportsActiveProperty {
return [self.layoutConstraint respondsToSelector:@selector(isActive)];
}
复制代码
/**
判断约束对象是否处于活动状态
*/
- (BOOL)isActive {
BOOL active = YES;
// 判断是否支持这个属性
if ([self supportsActiveProperty]) {
// 获取该属性的值
active = [self.layoutConstraint isActive];
}
return active;
}
复制代码
/**
判断约束对象是否已安装
*/
- (BOOL)hasBeenInstalled {
// 必须有约束对象,并且约束对象处于活动状态,才是已安装
return (self.layoutConstraint != nil) && [self isActive];
}
复制代码
/**
获取与指定约束对象相似的约束对象
*/
- (MASLayoutConstraint *)layoutConstraintSimilarTo:(MASLayoutConstraint *)layoutConstraint {
// 遍历要设置约束视图的所有已设置的约束
// 除了约束对象的常数 c 之外,其他属性必须都相同,才是相似的约束对象
for (NSLayoutConstraint *existingConstraint in self.installedView.constraints.reverseObjectEnumerator) {
if (![existingConstraint isKindOfClass:MASLayoutConstraint.class]) continue;
if (existingConstraint.firstItem != layoutConstraint.firstItem) continue;
if (existingConstraint.secondItem != layoutConstraint.secondItem) continue;
if (existingConstraint.firstAttribute != layoutConstraint.firstAttribute) continue;
if (existingConstraint.secondAttribute != layoutConstraint.secondAttribute) continue;
if (existingConstraint.relation != layoutConstraint.relation) continue;
if (existingConstraint.multiplier != layoutConstraint.multiplier) continue;
if (existingConstraint.priority != layoutConstraint.priority) continue;
return (id)existingConstraint;
}
return nil;
}
复制代码
5.3 父类方法
- (MASConstraint * (^)(CGFloat))multipliedBy {
return ^id(CGFloat multiplier) {
// 已安装的约束无法修改
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint multiplier after it has been installed");
// 保存参数
self.layoutMultiplier = multiplier;
return self;
};
}
复制代码
- (MASConstraint * (^)(CGFloat))dividedBy {
return ^id(CGFloat divider) {
// 已安装的约束无法修改
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint multiplier after it has been installed");
// 保存参数的倒数
self.layoutMultiplier = 1.0/divider;
return self;
};
}
复制代码
- (MASConstraint * (^)(MASLayoutPriority))priority {
return ^id(MASLayoutPriority priority) {
// 已安装的约束无法修改
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint priority after it has been installed");
// 保存参数
self.layoutPriority = priority;
return self;
};
}
复制代码
- (MASConstraint *)with {
// 重写这个方法主要是为了返回当前类的类型,否则返回的对象还是其父类 MASConstraint 类型的对象
return self;
}
复制代码
- (MASConstraint *)and {
// 作用同上
return self;
}
复制代码
- (MASConstraint * (^)(id))key {
return ^id(id key) {
// 保存传入的参数
self.mas_key = key;
return self;
};
}
复制代码
- (void)setInsets:(MASEdgeInsets)insets {
// 获取已设置的 attr1
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
// 根据 attr1 设置不同的常数 c
switch (layoutAttribute) {
case NSLayoutAttributeLeft:
case NSLayoutAttributeLeading:
self.layoutConstant = insets.left;
break;
case NSLayoutAttributeTop:
self.layoutConstant = insets.top;
break;
case NSLayoutAttributeBottom:
self.layoutConstant = -insets.bottom;
break;
case NSLayoutAttributeRight:
case NSLayoutAttributeTrailing:
self.layoutConstant = -insets.right;
break;
default:
break;
}
}
复制代码
- (void)setInset:(CGFloat)inset {
// 调用上面的方法
[self setInsets:(MASEdgeInsets){.top = inset, .left = inset, .bottom = inset, .right = inset}];
}
复制代码
- (void)setOffset:(CGFloat)offset {
// 记录参数
self.layoutConstant = offset;
}
复制代码
- (void)setSizeOffset:(CGSize)sizeOffset {
// 获取已设置的 attr1
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
// 根据 attr1 设置不同的常数 c
switch (layoutAttribute) {
case NSLayoutAttributeWidth:
self.layoutConstant = sizeOffset.width;
break;
case NSLayoutAttributeHeight:
self.layoutConstant = sizeOffset.height;
break;
default:
break;
}
}
复制代码
- (void)setCenterOffset:(CGPoint)centerOffset {
// 获取已设置的 attr1
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
// 根据 attr1 设置不同的常数 c
switch (layoutAttribute) {
case NSLayoutAttributeCenterX:
self.layoutConstant = centerOffset.x;
break;
case NSLayoutAttributeCenterY:
self.layoutConstant = centerOffset.y;
break;
default:
break;
}
}
复制代码
- (void)activate {
// 直接调用该类的 install 方法
[self install];
}
复制代码
- (void)deactivate {
// 直接调用该类的 uninstall 方法
[self uninstall];
}
复制代码
- (void)install {
// 如果已经安装了就直接返回
if (self.hasBeenInstalled) {
return;
}
// 兼容 iOS8 以后的版本
if ([self supportsActiveProperty] && self.layoutConstraint) {
// 将约束对象的活动状态激活
self.layoutConstraint.active = YES;
// 保存已安装的视图约束封装对象
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
// 返回
return;
}
// 兼容 iOS8 之前的版本
// 获取生成约束对象的参数
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
// 如果设置了像 make.left.equalTo(@10) 这样的约束
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
// 实例化约束对象
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
// 设置约束对象的属性
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
if (self.secondViewAttribute.view) {
// 如果设置了 view2
// 获取 view1 和 view2 的公共父视图
MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
// view1 和 view 必须有公共父视图
NSAssert(closestCommonSuperview,
@"couldn't find a common superview for %@ and %@",
self.firstViewAttribute.view, self.secondViewAttribute.view);
// 要设置约束的视图就是他们的公共父视图
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
// 如果设置的属性为 size 类型的, 要设置约束的视图就是 view1
self.installedView = self.firstViewAttribute.view;
} else {
// 否则,要设置约束的视图就是 view1 的父视图
self.installedView = self.firstViewAttribute.view.superview;
}
// 创建变量保存之前添加的约束
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
// 如果需要更新约束,就获取之前的约束对象
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// 如果之前安装过约束对象
// 更新约束对象的常数 c
existingConstraint.constant = layoutConstraint.constant;
// 保存新的约束对象
self.layoutConstraint = existingConstraint;
} else {
// 如果之前没有安装过约束对象
// 安装约束对象
[self.installedView addConstraint:layoutConstraint];
// 保存安装的约束对象
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
复制代码
- (void)uninstall {
// 兼容 iOS8 以后的版本
if ([self supportsActiveProperty]) {
// 将约束对象的活动状态关闭
self.layoutConstraint.active = NO;
// 从集合中移除
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
return;
}
// 兼容 iOS8 之前的版本
// 从视图上移除约束
[self.installedView removeConstraint:self.layoutConstraint];
// 将属性置空
self.layoutConstraint = nil;
self.installedView = nil;
// 从集合中移除
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
}
复制代码
5.4 私有分类方法
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {
// 如果是传入的属性是 NSArray 类型的
// 已设置约束关系的约束无法修改
NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
// 创建属性保存约束视图及其属性类
NSMutableArray *children = NSMutableArray.new;
// 遍历传入的参数
for (id attr in attribute) {
// 获取当前对象的副本,设置属性,并添加到数组
MASViewConstraint *viewConstraint = [self copy];
viewConstraint.layoutRelation = relation;
viewConstraint.secondViewAttribute = attr;
[children addObject:viewConstraint];
}
// 创建多约束封装对象
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
// 设置代理对象为当前对象的代理对象,也就是 MASConstraintMaker 对象
compositeConstraint.delegate = self.delegate;
// 调用代理对象实现的方法进行约束对象替换
[self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
// 返回多约束封装对象
return compositeConstraint;
} else {
// 要么没有设置过约束关系
// 要么设置过约束关系,但是新设置的约束关系和之前设置的是相同的,并且传入的参数是 NSValue 类型的
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
// 保存参数
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
return self;
}
};
}
复制代码
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
// 约束属性应该在添加约束关系之前添加
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
// 调用代理对象实现的方法进行约束对象添加
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
复制代码
6. 总结
该类封装了约束对象 NSLayoutConstraint ,具体实现了父类 MASConstraint 指定的功能。可以说是约束对象的管理类。