iOS --- 在代码中使用NSLayoutConstraint添加AutoLayout的约束条件

赵高韵
2023-12-01

AutoLayout是iOS开发中的布局适配神器。常在storyboard和xib文件中直接使用, 用于不同屏幕大小的适配。而在某些情况下,需要使用代码实现AutoLayout,则可以使用NSLayoutConstraint对象来添加约束条件。

NSLayoutConstraint对象

@interface NSLayoutConstraint : NSObject
{
    @private
    id _container;
    id _firstItem;
    id _secondItem;
    CGFloat _constant;
    CGFloat _loweredConstant;
    id _markerAndPositiveExtraVar;
    id _negativeExtraVar;
    float _coefficient;
    UILayoutPriority _priority;
    uint64_t _layoutConstraintFlags;
    id _flange;
}

/* Create an array of constraints using an ASCII art-like visual format string.
 */
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;

/* This macro is a helper for making view dictionaries for +constraintsWithVisualFormat:options:metrics:views:.  
 NSDictionaryOfVariableBindings(v1, v2, v3) is equivalent to [NSDictionary dictionaryWithObjectsAndKeys:v1, @"v1", v2, @"v2", v3, @"v3", nil];
 */
#define NSDictionaryOfVariableBindings(...) _NSDictionaryOfVariableBindings(@"" # __VA_ARGS__, __VA_ARGS__, nil)
UIKIT_EXTERN NSDictionary *_NSDictionaryOfVariableBindings(NSString *commaSeparatedKeysString, id firstValue, ...) NS_AVAILABLE_IOS(6_0); // not for direct use


/* Create constraints explicitly.  Constraints are of the form "view1.attr1 = view2.attr2 * multiplier + constant" 
 If your equation does not have a second view and attribute, use nil and NSLayoutAttributeNotAnAttribute.
 */
+(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;

/* If a constraint's priority level is less than UILayoutPriorityRequired, then it is optional.  Higher priority constraints are met before lower priority constraints.
 Constraint satisfaction is not all or nothing.  If a constraint 'a == b' is optional, that means we will attempt to minimize 'abs(a-b)'.
 This property may only be modified as part of initial set up.  An exception will be thrown if it is set after a constraint has been added to a view.
 */
@property UILayoutPriority priority;

/* When a view is archived, it archives some but not all constraints in its -constraints array.  The value of shouldBeArchived informs UIView if a particular constraint should be archived by UIView.
 If a constraint is created at runtime in response to the state of the object, it isn't appropriate to archive the constraint - rather you archive the state that gives rise to the constraint.  Since the majority of constraints that should be archived are created in Interface Builder (which is smart enough to set this prop to YES), the default value for this property is NO.
 */
@property BOOL shouldBeArchived;

/* accessors
 firstItem.firstAttribute {==,<=,>=} secondItem.secondAttribute * multiplier + constant
 */
@property (readonly, assign) id firstItem;
@property (readonly) NSLayoutAttribute firstAttribute;
@property (readonly) NSLayoutRelation relation;
@property (readonly, assign) id secondItem;
@property (readonly) NSLayoutAttribute secondAttribute;
@property (readonly) CGFloat multiplier;

/* Unlike the other properties, the constant may be modified after constraint creation.  Setting the constant on an existing constraint performs much better than removing the constraint and adding a new one that's just like the old but for having a new constant.
 */
@property CGFloat constant;

/* The receiver may be activated or deactivated by manipulating this property.  Only active constraints affect the calculated layout.  Attempting to activate a constraint whose items have no common ancestor will cause an exception to be thrown.  Defaults to NO for newly created constraints. */
@property (getter=isActive) BOOL active NS_AVAILABLE(10_10, 8_0);

/* Convenience method that activates each constraint in the contained array, in the same manner as setting active=YES. This is often more efficient than activating each constraint individually. */
+ (void)activateConstraints:(NSArray *)constraints NS_AVAILABLE(10_10, 8_0);

/* Convenience method that deactivates each constraint in the contained array, in the same manner as setting active=NO. This is often more efficient than deactivating each constraint individually. */
+ (void)deactivateConstraints:(NSArray *)constraints NS_AVAILABLE(10_10, 8_0);
@end

@interface NSLayoutConstraint (NSIdentifier)
/* For ease in debugging, name a constraint by setting its identifier, which will be printed in the constraint's description.
 Identifiers starting with UI and NS are reserved by the system.
 */
@property (copy) NSString *identifier NS_AVAILABLE_IOS(7_0);

@end

对其中的属性不多做解释, 多留意下priority,multiplier, constant分别对应优先级, 倍数, 约束值。

constraintWithItem方法

该方法常用添加单个约束条件。

/* Create constraints explicitly.  Constraints are of the form "view1.attr1 = view2.attr2 * multiplier + constant" 
 If your equation does not have a second view and attribute, use nil and NSLayoutAttributeNotAnAttribute.
 */
+(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;

参数解释如下:
- view1:应用该约束条件的UIView对象,
- attr1:约束参数(top,bottom,leading,training,width,height等)
- relation:相等或者其他关系
- view2:参考UIView
- attr2:参考UIView的参数
- multiplier:倍数
- c:约束值
即得到的约束为:
view1.attr1 = view2.attr2 * multiplier + constant

实例

_viewTooled1 = [[UIView alloc] init];
_viewTooled1.backgroundColor = [UIColor greenColor];
_viewTooled1.translatesAutoresizingMaskIntoConstraints = NO;
[_viewMain addSubview:_viewTooled1];

NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_viewTooled1
                                                       attribute:NSLayoutAttributeTop
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:_viewMain
                                                       attribute:NSLayoutAttributeTop
                                                      multiplier:1
                                                        constant:0];
NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_viewTooled1
                                                       attribute:NSLayoutAttributeBottom
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:_viewMain
                                                       attribute:NSLayoutAttributeBottom
                                                      multiplier:1
                                                        constant:0];
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_viewTooled1
                                                       attribute:NSLayoutAttributeLeading
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:_viewMain
                                                       attribute:NSLayoutAttributeLeading
                                                      multiplier:1
                                                        constant:0];
_widthViewTooled1 = [NSLayoutConstraint constraintWithItem:_viewTooled1
                                                 attribute:NSLayoutAttributeWidth
                                                 relatedBy:NSLayoutRelationEqual
                                                    toItem:nil
                                                 attribute:NSLayoutAttributeNotAnAttribute
                                                multiplier:1
                                                  constant:100];
[self.view addConstraints:@[top, bottom, leading, _widthViewTooled1]];

以上实例,设置_viewTooled的top,bottom,leading都与_viewMain对齐,而其width则可以根据_widthViewTooled1约束条件来修改,甚至做成动画形式。

constraintsWithVisualFormat

constraintsWithVisualFormat是另一个非常常用的约束方法,涉及到VFL这种特殊的语法规则,将留在下一篇博客中详细介绍。

需要注意的几点

有如下需要注意的地方:
1. [_viewMain addSubview:_viewTooled1];一定要放在[self.view addConstraints:@[top, bottom, leading, _widthViewTooled1]];的前边,否则添加约束失败,编译器报错。
2. 在UIView动画中,对constant进行了修改,则要使用layoutIfNeeded对布局进行刷新,否则看不到动画的过程。

- (void)viewToolAnimation {
    [UIView animateWithDuration:2.0 animations:^{
        _widthViewTooled1.constant = 300;
        [self.view layoutIfNeeded];
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:2.0 animations:^{
            _widthViewTooled2.constant = 300;
            [self.view layoutIfNeeded];
        } completion:^(BOOL finished) {
        }];
    }];
}
 类似资料: