源碼閱讀:Masonry(六)——MASViewConstraint

NO IMAGE

該文章閱讀的 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 指定的功能。可以說是約束對象的管理類。

相關文章

[UIViewhitTest:withEvent:]方法總結

源碼閱讀:Masonry(九)——NSArray+MASAdditions/其他分類

源碼閱讀:Masonry(八)——View+MASAdditions

源碼閱讀:Masonry(七)——MASConstraintMaker