// // UIView+RAConstraint.m // Constraint // // Created by Jack on 2018/12/10. // Copyright © 2018年 Jack Template. All rights reserved. // #import "UIView+RAConstraint.h" #import #pragma mark - Constraint @interface RAConstraint () @property (nonatomic,weak) id appliedView; @property (nonatomic,weak) NSLayoutConstraint *constraint; @property (nonatomic,assign) UILayoutPriority priority; @property (nonatomic,assign) BOOL validate; @property (nonatomic,copy) NSString *identifier; @property (nonatomic,assign) NSLayoutAttribute attribute; @property (nonatomic,assign) NSLayoutRelation relation; @property (nonatomic,weak) UIView *toView; @property (nonatomic,assign) NSLayoutAttribute toAttribute; @property (nonatomic,assign) CGFloat constant; @property (nonatomic,copy) RAConstraint *(^ra_p_offset)(CGFloat offset); @property (nonatomic,copy) RAConstraint *(^ra_p_lessThanOrEqualTo)(RAConstraint *constraint); @property (nonatomic,copy) RAConstraint *(^ra_p_equalTo)(RAConstraint *constraint); @property (nonatomic,copy) RAConstraint *(^ra_p_greaterThanOrEqualTo)(RAConstraint *constraint); @property (nonatomic,copy) RAConstraint *(^ra_p_priority)(UILayoutPriority prioruty); @property (nonatomic,strong) NSMutableDictionary *appendConstraints; @property (nonatomic,copy) RAConstraint *(^ra_p_append)(NSString *identifier); @property (nonatomic,copy) RAConstraint *(^ra_p_size_greaterThanOrEqualTo)(CGFloat size); @property (nonatomic,copy) RAConstraint *(^ra_p_size_lessThanOrEqualTo)(CGFloat size); @property (nonatomic,copy) void (^ra_p_modify)(CGFloat constant); @end @implementation RAConstraint - (instancetype)init { if (self = [super init]) { _attribute = NSLayoutAttributeNotAnAttribute; _relation = NSLayoutRelationEqual; _toAttribute = NSLayoutAttributeNotAnAttribute; _priority = UILayoutPriorityDefaultHigh; __weak typeof(self) weakSelf = self; self.ra_p_offset = [^RAConstraint *(CGFloat offset) { weakSelf.validate = YES; weakSelf.constant = offset; return weakSelf; } copy]; self.ra_p_lessThanOrEqualTo = [^RAConstraint *(RAConstraint *constraint) { weakSelf.validate = YES; weakSelf.toView = constraint.appliedView; weakSelf.relation = NSLayoutRelationLessThanOrEqual; weakSelf.toAttribute = constraint.attribute; return weakSelf; } copy]; self.ra_p_equalTo = [^RAConstraint *(RAConstraint *constraint) { weakSelf.validate = YES; weakSelf.toView = constraint.appliedView; weakSelf.relation = NSLayoutRelationEqual; weakSelf.toAttribute = constraint.attribute; return weakSelf; } copy]; self.ra_p_greaterThanOrEqualTo = [^RAConstraint *(RAConstraint *constraint) { weakSelf.validate = YES; weakSelf.toView = constraint.appliedView; weakSelf.relation = NSLayoutRelationGreaterThanOrEqual; weakSelf.toAttribute = constraint.attribute; return weakSelf; } copy]; self.ra_p_priority = [^RAConstraint *(UILayoutPriority priority) { weakSelf.priority = priority; return weakSelf; } copy]; self.ra_p_append = [^RAConstraint *(NSString *identifier) { if (!identifier) { return nil; } if (weakSelf.appendConstraints[identifier]) { return weakSelf.appendConstraints[identifier]; } RAConstraint *constraint = [RAConstraint new]; constraint.appliedView = weakSelf.appliedView; constraint.attribute = weakSelf.attribute; weakSelf.appendConstraints[identifier] = constraint; return constraint; } copy]; self.ra_p_size_lessThanOrEqualTo = [^RAConstraint *(CGFloat size) { if (weakSelf.attribute != NSLayoutAttributeWidth && weakSelf.attribute != NSLayoutAttributeHeight) { return weakSelf; } weakSelf.validate = YES; weakSelf.toView = nil; weakSelf.relation = NSLayoutRelationLessThanOrEqual; weakSelf.toAttribute = NSLayoutAttributeNotAnAttribute; weakSelf.ra_offset(size); return weakSelf; } copy]; self.ra_p_size_greaterThanOrEqualTo = [^RAConstraint *(CGFloat size) { if (weakSelf.attribute != NSLayoutAttributeWidth && weakSelf.attribute != NSLayoutAttributeHeight) { return weakSelf; } weakSelf.validate = YES; weakSelf.toView = nil; weakSelf.relation = NSLayoutRelationGreaterThanOrEqual; weakSelf.toAttribute = NSLayoutAttributeNotAnAttribute; weakSelf.ra_offset(size); return weakSelf; } copy]; self.ra_p_modify = [^(CGFloat constant) { weakSelf.constant = constant; if (weakSelf.constraint) { weakSelf.constraint.constant = constant; if ([weakSelf appliedViewIsView]) { UIView *appliedView = ((UIView *)weakSelf.appliedView); [appliedView.superview layoutIfNeeded]; } } } copy]; } return self; } - (RAConstraint *(^)(CGFloat))ra_offset { return self.ra_p_offset; } - (RAConstraint *(^)(RAConstraint *))ra_lessThanOrEqualTo { return self.ra_p_lessThanOrEqualTo; } - (RAConstraint *(^)(RAConstraint *))ra_equalTo { return self.ra_p_equalTo; } - (RAConstraint *(^)(RAConstraint *))ra_greaterThanOrEqualTo { return self.ra_p_greaterThanOrEqualTo; } - (RAConstraint *(^)(UILayoutPriority))ra_priority { return self.ra_p_priority; } - (RAConstraint *(^)(NSString *))ra_append { return self.ra_p_append; } - (RAConstraint *(^)(CGFloat))ra_size_lessThanOrEqualTo { return self.ra_p_size_lessThanOrEqualTo; } - (RAConstraint *(^)(CGFloat))ra_size_greaterThanOrEqualTo { return self.ra_p_size_greaterThanOrEqualTo; } - (void (^)(CGFloat))ra_modify { return self.ra_p_modify; } - (void)ra_uninstall { if (self.appliedView && self.appliedViewIsView && self.validate && self.constraint) { UIView *appliedView = ((UIView *)self.appliedView); if ((self.attribute == NSLayoutAttributeWidth || self.attribute == NSLayoutAttributeHeight) && !self.toView) { [appliedView removeConstraint:self.constraint]; } else { [appliedView.superview removeConstraint:self.constraint]; } } [self.appendConstraints enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, RAConstraint * _Nonnull obj, BOOL * _Nonnull stop) { [obj ra_uninstall]; }]; self.validate = NO; self.constraint = nil; self.identifier = nil; self.toView = nil; self.relation = NSLayoutRelationEqual; self.toAttribute = NSLayoutAttributeNotAnAttribute; self.constant = 0; self.priority = UILayoutPriorityDefaultHigh; [self.appendConstraints removeAllObjects]; } - (void)ra_install { if (self.appliedView && self.appliedViewIsView && self.validate && !self.constraint) { UIView *appliedView = ((UIView *)self.appliedView); NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:appliedView attribute:self.attribute relatedBy:self.relation toItem:self.toView attribute:self.toAttribute multiplier:1 constant:self.constant]; self.constraint = constraint; self.constraint.priority = self.priority; self.constraint.identifier = self.identifier; if ((self.attribute == NSLayoutAttributeWidth || self.attribute == NSLayoutAttributeHeight) && !self.toView) { [appliedView addConstraint:self.constraint]; } else{ [appliedView.superview addConstraint:self.constraint]; } } [self.appendConstraints enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, RAConstraint * _Nonnull obj, BOOL * _Nonnull stop) { [obj ra_install]; }]; } - (NSMutableDictionary *)appendConstraints { if (!_appendConstraints) { _appendConstraints = [NSMutableDictionary dictionary]; } return _appendConstraints; } - (BOOL)appliedViewIsView { BOOL isView = [self.appliedView isKindOfClass:[UIView class]]; return isView; } @end #pragma mark - Maker @interface RAConstraintMaker () @property (nonatomic,weak) UIView *appliedView; @property (nonatomic,strong) RAConstraint *left; @property (nonatomic,strong) RAConstraint *top; @property (nonatomic,strong) RAConstraint *right; @property (nonatomic,strong) RAConstraint *bottom; @property (nonatomic,strong) RAConstraint *centerX; @property (nonatomic,strong) RAConstraint *centerY; @property (nonatomic,strong) RAConstraint *baseLine; @property (nonatomic,strong) RAConstraint *width; @property (nonatomic,strong) RAConstraint *height; @property (nonatomic,strong) RAConstraint *ra_safeAreaLeft API_AVAILABLE(ios(11.0),tvos(11.0)); @property (nonatomic,strong) RAConstraint *ra_safeAreaTop API_AVAILABLE(ios(11.0),tvos(11.0)); @property (nonatomic,strong) RAConstraint *ra_safeAreaRight API_AVAILABLE(ios(11.0),tvos(11.0)); @property (nonatomic,strong) RAConstraint *ra_safeAreaBottom API_AVAILABLE(ios(11.0),tvos(11.0)); @property (nonatomic,strong) NSMutableArray *constraints; @property (nonatomic,copy) void (^ra_modifyConstraint)(NSString *identifier, CGFloat constant); @end static const UILayoutPriority kRALayoutPriorityNone = -1; @implementation RAConstraintMaker - (instancetype)initWithAppliedView:(UIView *)view { if (self = [super init]) { self.appliedView = view; [self.constraints addObjectsFromArray:@[ self.left, self.top, self.right, self.bottom, self.centerX, self.centerY, self.baseLine, self.width, self.height ]]; self.ra_horizontalHuggingPriority = kRALayoutPriorityNone; self.ra_verticalHuggingPriority = kRALayoutPriorityNone; self.ra_horizontalCompressionResistancePriority = kRALayoutPriorityNone; self.ra_verticalCompressionResistancePriority = kRALayoutPriorityNone; __weak typeof(self) weakSelf = self; self.ra_modifyConstraint = [^(NSString *identifier, CGFloat constant) { NSDictionary *constraintRes = [weakSelf findConstraintOfView:weakSelf.appliedView byIdentifier:identifier]; if (constraintRes) { UIView *view = constraintRes[@"view"]; NSLayoutConstraint *constraint = constraintRes[@"constraint"]; constraint.constant = constant; [view layoutIfNeeded]; } } copy]; } return self; } - (NSDictionary *)findConstraintOfView:(UIView *)view byIdentifier:(NSString *)identifier { if (!view || !identifier) { return nil; } NSLayoutConstraint *result = nil; NSArray *constraintArray = view.constraints; for (NSLayoutConstraint *constraint in constraintArray) { if (constraint.identifier && [constraint.identifier isEqualToString:identifier]) { result = constraint; break; } } if (result) { return @{ @"view" : view, @"constraint" : result }; } else { if (view.superview) { return [self findConstraintOfView:view.superview byIdentifier:identifier]; } else { return nil; } } } - (NSMutableArray *)constraints { if (!_constraints) { _constraints = [NSMutableArray array]; } return _constraints; } - (RAConstraint *)left { if (!_left) { _left = [RAConstraint new]; _left.attribute = NSLayoutAttributeLeft; _left.appliedView = self.appliedView; } return _left; } - (RAConstraint *)top { if (!_top) { _top = [RAConstraint new]; _top.attribute = NSLayoutAttributeTop; _top.appliedView = self.appliedView; } return _top; } - (RAConstraint *)right { if (!_right) { _right = [RAConstraint new]; _right.attribute = NSLayoutAttributeRight; _right.appliedView = self.appliedView; } return _right; } - (RAConstraint *)bottom { if (!_bottom) { _bottom = [RAConstraint new]; _bottom.attribute = NSLayoutAttributeBottom; _bottom.appliedView = self.appliedView; } return _bottom; } - (RAConstraint *)centerX { if (!_centerX) { _centerX = [RAConstraint new]; _centerX.attribute = NSLayoutAttributeCenterX; _centerX.appliedView = self.appliedView; } return _centerX; } - (RAConstraint *)centerY { if (!_centerY) { _centerY = [RAConstraint new]; _centerY.attribute = NSLayoutAttributeCenterY; _centerY.appliedView = self.appliedView; } return _centerY; } - (RAConstraint *)baseLine { if (!_baseLine) { _baseLine = [RAConstraint new]; _baseLine.attribute = NSLayoutAttributeBaseline; _baseLine.appliedView = self.appliedView; } return _baseLine; } - (RAConstraint *)width { if (!_width) { _width = [RAConstraint new]; _width.attribute = NSLayoutAttributeWidth; _width.appliedView = self.appliedView; } return _width; } - (RAConstraint *)height { if (!_height) { _height = [RAConstraint new]; _height.attribute = NSLayoutAttributeHeight; _height.appliedView = self.appliedView; } return _height; } // safe area - (RAConstraint *)ra_safeAreaLeft { if (!_ra_safeAreaLeft) { _ra_safeAreaLeft = [RAConstraint new]; _ra_safeAreaLeft.attribute = NSLayoutAttributeLeft; _ra_safeAreaLeft.appliedView = self.appliedView.safeAreaLayoutGuide; } return _ra_safeAreaLeft; } - (RAConstraint *)ra_safeAreaTop { if (!_ra_safeAreaTop) { _ra_safeAreaTop = [RAConstraint new]; _ra_safeAreaTop.attribute = NSLayoutAttributeTop; _ra_safeAreaTop.appliedView = self.appliedView.safeAreaLayoutGuide; } return _ra_safeAreaTop; } - (RAConstraint *)ra_safeAreaRight { if (!_ra_safeAreaRight) { _ra_safeAreaRight = [RAConstraint new]; _ra_safeAreaRight.attribute = NSLayoutAttributeRight; _ra_safeAreaRight.appliedView = self.appliedView.safeAreaLayoutGuide; } return _ra_safeAreaRight; } - (RAConstraint *)ra_safeAreaBottom { if (!_ra_safeAreaBottom) { _ra_safeAreaBottom = [RAConstraint new]; _ra_safeAreaBottom.attribute = NSLayoutAttributeBottom; _ra_safeAreaBottom.appliedView = self.appliedView.safeAreaLayoutGuide; } return _ra_safeAreaBottom; } - (void)ra_install { if (self.appliedView) { // 抗拉伸 if (self.ra_horizontalHuggingPriority != kRALayoutPriorityNone) { [self.appliedView setContentHuggingPriority:self.ra_horizontalHuggingPriority forAxis:UILayoutConstraintAxisHorizontal]; } if (self.ra_verticalHuggingPriority != kRALayoutPriorityNone) { [self.appliedView setContentHuggingPriority:self.ra_verticalHuggingPriority forAxis:UILayoutConstraintAxisVertical]; } // 抗压缩 if (self.ra_horizontalCompressionResistancePriority != kRALayoutPriorityNone) { [self.appliedView setContentCompressionResistancePriority:self.ra_horizontalCompressionResistancePriority forAxis:UILayoutConstraintAxisHorizontal]; } if (self.ra_verticalCompressionResistancePriority != kRALayoutPriorityNone) { [self.appliedView setContentCompressionResistancePriority:self.ra_verticalCompressionResistancePriority forAxis:UILayoutConstraintAxisVertical]; } // normal constraint for (RAConstraint *constraint in self.constraints) { [constraint ra_install]; } } } - (void)ra_uninstall { if (self.appliedView) { for (RAConstraint *constraint in self.constraints) { [constraint ra_uninstall]; } } } @end #pragma mark - UIView static const char RAMaker = '\a'; @implementation UIView (RAConstraint) - (void)setRA_Maker:(RAConstraintMaker *)maker { if (maker) { objc_setAssociatedObject(self, &RAMaker, maker, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } - (RAConstraintMaker *)ra_maker { RAConstraintMaker *maker = objc_getAssociatedObject(self, &RAMaker); if (maker == nil) { maker = [[RAConstraintMaker alloc] initWithAppliedView:self]; [self setRA_Maker:maker]; } return maker; } - (void)ra_applyConstraints:(void(^)(RAConstraintMaker *maker))apply { if (self.superview) { // self.superview.translatesAutoresizingMaskIntoConstraints = NO; self.translatesAutoresizingMaskIntoConstraints = NO; RAConstraintMaker *maker = [self ra_maker]; if (apply) { apply(maker); } [self applyMaker:maker]; } } - (void)applyMaker:(RAConstraintMaker *)maker { if (maker) { [self setRA_Maker:maker]; [maker ra_install]; [self.superview layoutIfNeeded]; } } - (void)ra_remakeConstraints:(void(^)(RAConstraintMaker *maker))remake { [[self ra_maker] ra_uninstall]; [self ra_applyConstraints:remake]; } #pragma mark - P - (RAConstraint *)left { return [self ra_maker].left; } - (RAConstraint *)top { return [self ra_maker].top; } - (RAConstraint *)right { return [self ra_maker].right; } - (RAConstraint *)bottom { return [self ra_maker].bottom; } - (RAConstraint *)centerX { return [self ra_maker].centerX; } - (RAConstraint *)centerY { return [self ra_maker].centerY; } - (RAConstraint *)baseLine { return [self ra_maker].baseLine; } - (RAConstraint *)width { return [self ra_maker].width; } - (RAConstraint *)height { return [self ra_maker].height; } // safe area - (RAConstraint *)ra_safeAreaLeft { return [self ra_maker].ra_safeAreaLeft; } - (RAConstraint *)ra_safeAreaTop { return [self ra_maker].ra_safeAreaTop; } - (RAConstraint *)ra_safeAreaRight { return [self ra_maker].ra_safeAreaRight; } - (RAConstraint *)ra_safeAreaBottom { return [self ra_maker].ra_safeAreaBottom; } // modify - (void (^)(NSString *, CGFloat))ra_modifyConstraint { return [self ra_maker].ra_modifyConstraint; } @end #pragma mark - View Controller #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @implementation UIViewController (RAConstraint) - (RAConstraint *)ra_topLayoutGuid { RAConstraint *constraint = [RAConstraint new]; constraint.attribute = NSLayoutAttributeBottom; constraint.appliedView = self.topLayoutGuide; return constraint; } - (RAConstraint *)ra_topLayoutGuidTop { RAConstraint *constraint = [RAConstraint new]; constraint.attribute = NSLayoutAttributeTop; constraint.appliedView = self.topLayoutGuide; return constraint; } - (RAConstraint *)ra_topLayoutGuidBottom { RAConstraint *constraint = [RAConstraint new]; constraint.attribute = NSLayoutAttributeBottom; constraint.appliedView = self.topLayoutGuide; return constraint; } - (RAConstraint *)ra_bottomLayoutGuid { RAConstraint *constraint = [RAConstraint new]; constraint.attribute = NSLayoutAttributeTop; constraint.appliedView = self.bottomLayoutGuide; return constraint; } - (RAConstraint *)ra_bottomLayoutGuidTop { RAConstraint *constraint = [RAConstraint new]; constraint.attribute = NSLayoutAttributeTop; constraint.appliedView = self.bottomLayoutGuide; return constraint; } - (RAConstraint *)ra_bottomLayoutGuidBottom { RAConstraint *constraint = [RAConstraint new]; constraint.attribute = NSLayoutAttributeBottom; constraint.appliedView = self.bottomLayoutGuide; return constraint; } #pragma clang diagnostic pop @end