Constraints(约束)

前言

Margin:

用过XIB的人肯定被下图中的Constraints to margins困扰过.
Constraints(约束)_第1张图片

这是为了在一般的屏幕上让用户容易触摸(左右是8/12PT貌似).X的那种屏幕上不挡住控件.以及不被状态栏导航栏等挡住的做法.

如果设计们觉得难看想要调整的话.能不能调整呢.答案是能的.我们可以通过UIView实例的directionalLayoutMargins属性去调整他.以前可能是通过layoutMargin属性去调整的.也不是说以前有什么不对.因为国际化之后不是所有的都是普通的从左到右布局.有可能有阿拉伯语言的那种从右到左布局的.

layoutMargin:

UIEdgeInsetsMake(<#CGFloat top#>, <#CGFloat left#>, <#CGFloat bottom#>, <#CGFloat right#>)

directionalLayoutMargins: iOS 11 Support

NSDirectionalEdgeInsetsMake(<#CGFloat top#>, <#CGFloat leading#>, <#CGFloat bottom#>, <#CGFloat trailing#>)

leadingtrailing包括了上面讲的国际化的情况.而leftright就是普通的那种左边.右边.设置约束的时候要注意是对齐父视图的Margin.不然设置失效.

不过需要注意的是.对于视图的根控制器.他有一个最小的margin.可以通过vc.systemMinimumLayoutMargins获取得到.如果不想使用系统的最小的设置vc.viewRespectsSystemMinimumLayoutMargins = NO即可.

Safe Area:

记得同上面的那个设置差不多.Safe Area也是iOS11推出的(为了X那种异形屏的操控).主要来说就是上面的下巴+一点点距离与底下系统的条子以及一点距离是原则上不允许填充交互控件的(因为会挡住系统交互造成用户的困惑巴拉巴拉的).

VC的Safe Area区域可以通过vc的additionalSafeAreaInsets属性修改.
View的Safe Area区域可以通过view的safeAreaInsets属性查看(readonly).

Constraints

接下来我们进入正题.约束.

苹果文档中告诉我们.每个约束都可以看成一个如下的方程式:

/*
item1 / item2: view
attribute1 / attribute2: 属性
multiplier: 比例
constant: 常数(可以理解为偏移量)
*/
item1.attribute1 = multiplier × item2.attribute2 + constant

创建一个约束

/// MARK: 1
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(nullable NSDictionary *)metrics views:(NSDictionary *)views API_AVAILABLE(macos(10.7), ios(6.0), tvos(9.0));

/// MARK: 2
+ (instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c API_AVAILABLE(macos(10.7), ios(6.0), tvos(9.0));

添加约束前我们需要知道两个东西.比如现在,VC的View上面add了一个subView叫redView.如果我们要约束redView.1.设置redView的translatesAutoresizingMaskIntoConstraintsNO.2.约束添加在想要约束视图的父视图里面.那么我们就需要把约束添加到View上.

VFL语言

以上面第一种方式创建的的约束就利用到了VFL语言.具体使用在下面有讲解

VFL(Visual Format Language)

使用讲解
第一个参数format
符号 使用规则 用例
(:)?()?()*()?
H(水平方向)|V(垂直方向)
|(表示父视图)
[()?]
e|--|-
|
|
((,)*)
()?()(@)?
==(等于)|<=(小于等于)|>=(大于等于)
| (see note)
|
|
Parsed as a C identifier. This must be a key mapping to an instance of NSView in the passed views dictionary.
Parsed as a C identifier. This must be a key mapping to an instance of NSNumber in the passed metrics dictionary.
As parsed by strtod_l, with the C locale.
第二个参数opts

一般传0即可

第三个参数metrics

一般传nil

第四个参数views

一般利用NSDictionaryOfVariableBindings(...)去绑定View.然后使用.不过绑定的时候可以使用self.xxx但是使用的时候要用_xxx.不然会崩溃

使用用例:
self.viewTest = [UIView new];
self.viewTest.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.viewTest];
self.viewTest.translatesAutoresizingMaskIntoConstraints = NO;
UIView *viewRed = [UIView new];
viewRed.backgroundColor = [UIColor redColor];
[self.viewTest addSubview:viewRed];
viewRed.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewDic = NSDictionaryOfVariableBindings(_viewTest ,viewRed);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_viewTest]|" options:0 metrics:nil views:viewDic]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_viewTest]|" options:0 metrics:nil views:viewDic]];
[self.viewTest addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[viewRed]-10-|" options:0 metrics:nil views:viewDic]];
[self.viewTest addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[viewRed]-10-|" options:0 metrics:nil views:viewDic]];

缺点:如果设置一个UIImageView的实例的宽高度比例什么的VFL就无能为力了.就需要使用上面的第二种方式创建约束了

另外一种创建方法

我们也知道Masonry以及他的Swift版本SnapKit(不是所有功能都有的版本)就是基于Constraints封装的(但不是上面恶心的VFL),虽说VFL看起来东西比较少,但是format看起来还是比较少.第二种如果直接用系统的就有些恶心了

/// view1 : 需要约束的视图
/// attr1 : 需要约束的视图的属性(margin、上下左右、宽高等)
/// relation : 小于等于、等于、大于等于
/// view2 : 参照的视图
/// attr2 : 参照的视图的属性
/// multiplier : 比例
/// c : 常数(这个需要注意.右边和底部和别的不一样,比如距离底部是10的话c需要传入-10).
+ (instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c API_AVAILABLE(macos(10.7), ios(6.0), tvos(9.0));

激活/取消激活约束


/// 可以通过设置YES/NO来开启/关闭约束.新创建的约束默认为NO.
@property (getter=isActive) BOOL active API_AVAILABLE(macos(10.10), ios(8.0));

/// 上面设置active=YES的便捷方法.
+ (void)activateConstraints:(NSArray *)constraints API_AVAILABLE(macos(10.10), ios(8.0));

/// 上面设置active=NO的便捷方法.
+ (void)deactivateConstraints:(NSArray *)constraints API_AVAILABLE(macos(10.10), ios(8.0));

有时候我们通过多个约束的开启关闭来控制视图的行为.这个active还是比较有用的.

约束属性

/// 第一个item
@property (nullable, readonly, assign) id firstItem;
/// 第二个item
@property (nullable, readonly, assign) id secondItem;
/// 第一个item的属性
@property (readonly) NSLayoutAttribute firstAttribute;
/// 第二个item的属性
@property (readonly) NSLayoutAttribute secondAttribute;

/* accessors
 firstAnchor{==,<=,>=} secondAnchor * multiplier + constant
 */
 
/// 创建约束的与item1有关的属性被转换成`NSLayoutAnchor`描述
@property (readonly, copy) NSLayoutAnchor *firstAnchor API_AVAILABLE(macos(10.12), ios(10.0));
/// 创建约束的与item2有关的属性被转换成`NSLayoutAnchor`描述
@property (readonly, copy, nullable) NSLayoutAnchor *secondAnchor API_AVAILABLE(macos(10.12), ios(10.0));
/// 小于等于、等于、大于等于
@property (readonly) NSLayoutRelation relation;
/// 比例
@property (readonly) CGFloat multiplier;

约束优先级

#if TARGET_OS_IPHONE
@property UILayoutPriority priority;
#else
@property NSLayoutPriority priority;
#endif

MacOS中这个属性是NSLayoutPriority类型的.在iOS系统中是UILayoutPriority类型的.

static const UILayoutPriority UILayoutPriorityRequired API_AVAILABLE(ios(6.0)) = 1000;
static const UILayoutPriority UILayoutPriorityDefaultHigh API_AVAILABLE(ios(6.0)) = 750;
static const UILayoutPriority UILayoutPriorityDefaultLow API_AVAILABLE(ios(6.0)) = 250;
static const UILayoutPriority UILayoutPriorityFittingSizeLevel API_AVAILABLE(ios(6.0)) = 50;

一般来说用的就是最上面的三个.默认的话是第一个.当然,我们也可以指定一些其他的值.但是我建议写成相应的宏来方便后期维护.

约束的优先级的使用也比较广泛.比如我一个View根据另外一个View拉伸.然而我这个View有一个最大宽度/高度.那么我们就可以把那个跟随的约束的优先级调低一点.那么就可以完美的实现效果了.

抗压/抗拉优先级

当两个控件的约束相同的情况下.例如两个UILabel水平布局.按照情况两个UILabel都需要压缩.而且约束的优先级都相同.那么.先压缩哪个控件呢.目前的效果就是不确定的.

不过我们可以使用UIView的抗拉/抗压优先级来解决这个问题

/// MARK: 抗拉
- (UILayoutPriority)contentHuggingPriorityForAxis:(UILayoutConstraintAxis)axis API_AVAILABLE(ios(6.0));
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis API_AVAILABLE(ios(6.0));

/// MARK: 抗压
- (UILayoutPriority)contentCompressionResistancePriorityForAxis:(UILayoutConstraintAxis)axis API_AVAILABLE(ios(6.0));
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis API_AVAILABLE(ios(6.0));

priority: 优先级

axis: 方向(水平/垂直)

个人理解是会像上面的约束优先级一样.会让设置的抗拉和抗压的优先级高于控件自身对应的水平/垂直方向的宽度/高度.举个例子,如果水平方向上抗压级别高,那么在控件需要被压缩的话会优先压缩其他的抗压优先级低的.

约束标识

@property (nullable, copy) NSString *identifier API_AVAILABLE(macos(10.7), ios(7.0));

和手势那一章的name其实是差不多的东西.就是为了方便我们调试用的.

UILayoutSupport

@protocol UILayoutSupport 
@property(nonatomic,readonly) CGFloat length;  // As a courtesy when not using auto layout, this value is safe to refer to in -viewDidLayoutSubviews, or in -layoutSubviews after calling super

/* Constraint creation conveniences. See NSLayoutAnchor.h for details.
 */
@property(readonly, strong) NSLayoutYAxisAnchor *topAnchor API_AVAILABLE(ios(9.0));
@property(readonly, strong) NSLayoutYAxisAnchor *bottomAnchor API_AVAILABLE(ios(9.0));
@property(readonly, strong) NSLayoutDimension *heightAnchor API_AVAILABLE(ios(9.0));
@end

这个协议由UIViewControllertopLayoutGuidebottomLayoutGuide实现.用来支持视图控制器的自动布局

@interface UIViewController (UILayoutSupport)
// These objects may be used as layout items in the NSLayoutConstraint API
@property(nonatomic,readonly,strong) id topLayoutGuide API_DEPRECATED("Use view.safeAreaLayoutGuide.topAnchor instead of topLayoutGuide.bottomAnchor", ios(7.0,11.0), tvos(7.0,11.0));
@property(nonatomic,readonly,strong) id bottomLayoutGuide API_DEPRECATED("Use view.safeAreaLayoutGuide.bottomAnchor instead of bottomLayoutGuide.topAnchor", ios(7.0,11.0), tvos(7.0,11.0));

/* Custom container UIViewController subclasses can use this property to add to the overlay
 that UIViewController calculates for the safeAreaInsets for contained view controllers.
 */
@property(nonatomic) UIEdgeInsets additionalSafeAreaInsets API_AVAILABLE(ios(11.0), tvos(11.0));

/* Minimum layoutMargins for the view determined by the view controller from context and hardware information.
 The view controller's view will respect these minimums unless viewRespectsSystemMinimumLayoutMargins
 (which defaults to YES) is set to NO.
 */
@property(nonatomic,readonly) NSDirectionalEdgeInsets systemMinimumLayoutMargins API_AVAILABLE(ios(11.0), tvos(11.0));

/* Default YES. The return value of the view's layoutMargins and directionalLayoutMargins properties will have
 values no smaller than the systemMinimumLayoutMargins. Set to NO for full customizability of the view's
 layoutMargins.
 */
@property(nonatomic) BOOL viewRespectsSystemMinimumLayoutMargins API_AVAILABLE(ios(11.0), tvos(11.0));

- (void)viewLayoutMarginsDidChange NS_REQUIRES_SUPER API_AVAILABLE(ios(11.0), tvos(11.0));
- (void)viewSafeAreaInsetsDidChange NS_REQUIRES_SUPER API_AVAILABLE(ios(11.0), tvos(11.0));

@end

你可能感兴趣的:(ViewLayout)