Objective-C的Auto Layout(自动布局)学习笔记

Auto Layout

Auto Layout 即自动布局,在iOS6引入,不同于frame框架式的布局,自动布局根据视图间的相对约束来确定视图位置与大小,使视图得以动态的适应位置与大小的变化,匹配不同尺寸的设备,从而节省大量设置或更新视图位置与大小的代码。自动布局涉及:NSLayoutConstraint(布局约束)、NSLayoutAnchor(布局锚)、UILayoutGuide(布局占位)、SizeClasses(屏幕适配)、Constraints in Interface Builder(故事板约束)等。

\color{red}{例如一个简单的frame布局:}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIView *view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 200)];
    view.backgroundColor = [UIColor greenColor];
    [self.view addSubview:view];
}

正常情况下,显示没有问题:

截屏2022-04-11 下午3.29.59.png

但当设备发生旋转时,问题便产生了:

截屏2022-04-11 下午3.31.08.png

这显然不是我们想看到的效果,如果使用约束进行布局:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIView *view2 = [[UIView alloc]initWithFrame:CGRectZero];
    view2.backgroundColor = [UIColor greenColor];
    view2.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:view2];

    NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:view2
                                                                     attribute:NSLayoutAttributeTop
                                                                     relatedBy:NSLayoutRelationEqual
                                                                        toItem:self.view
                                                                     attribute:NSLayoutAttributeTop
                                                                    multiplier:1.0
                                                                      constant:0];

    NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:view2
                                                                     attribute:NSLayoutAttributeLeft
                                                                     relatedBy:NSLayoutRelationEqual
                                                                        toItem:self.view
                                                                     attribute:NSLayoutAttributeLeft
                                                                    multiplier:1.0
                                                                      constant:0];

    NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:view2
                                                                     attribute:NSLayoutAttributeWidth
                                                                     relatedBy:NSLayoutRelationEqual
                                                                        toItem:self.view
                                                                     attribute:NSLayoutAttributeWidth
                                                                    multiplier:1.0
                                                                      constant:0];

    NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:view2
                                                                     attribute:NSLayoutAttributeHeight
                                                                     relatedBy:NSLayoutRelationEqual
                                                                        toItem:nil
                                                                     attribute:NSLayoutAttributeNotAnAttribute
                                                                    multiplier:1.0
                                                                      constant:200];
    [self.view addConstraint:topConstraint];
    [self.view addConstraint:leftConstraint];
    [self.view addConstraint:widthConstraint];
    [self.view addConstraint:heightConstraint];
}

正常情况下,显示没有问题:

截屏2022-04-11 下午3.36.47.png

当设备发生旋转时,显示也是没有问题:

截屏2022-04-11 下午3.40.53.png

当然现在添加约束的代码仍旧非常繁琐,写起来仍旧让人想在心里默默的数着羊驼,但是该看的api还是要看,谁让我们要去学习,要去认知呢。

UIView (UIConstraintBasedCompatibility) - 基于约束的兼容性

@property(nonatomic) BOOL translatesAutoresizingMaskIntoConstraints API_AVAILABLE(ios(6.0));

属性描述一个布尔值,如果此属性的值为YES,系统将基于视图的大小和位置的掩码为视图转换为自动布局约束,而约束完全指定了视图的大小和位置,因此,在不引入冲突的情况下,无法添加其他约束来修改此大小或位置。如果要使用“Auto Layout”动态计算视图的大小和位置,则必须将此属性设置为NO,然后为视图提供一组无歧义、无冲突的约束。默认情况下,对于以编程方式创建的任何视图,该属性都设置为YES。如果在Interface Builder中添加视图,系统会自动将此属性设置为NO。这个扩展于UIView中的属性也就是我们使用自动布局设置约束的先决条件了。

NSLayoutConstraint - 布局约束

NSLayoutConstraint,描述一条约束的对象,基于约束的布局系统必须满足的两个用户界面对象之间的关系。每个约束都是一个线性方程,格式如下:

item1.attribute1 = multiplier × item2.attribute2 + constant

在这个等式中,attribute1和attribute2是Auto Layout在解决这些约束时可以调整的变量。其他值在创建约束时定义。例如,如果定义两个按钮的相对位置,可能这样描述 “第二个按钮的前缘应该在第一个按钮的后缘之后8个点。” 此关系的线性方程如下所示(在英语等从左到右的语言中,正值向右移动):

button2.leading = 1.0 × button1.trailing + 8.0

Auto Layout然后修改指定的前边和后缘的值,直到公式的两边相等。注意,Auto Layout不会简单地将该等式右侧的值指定给左侧。相反,系统可以根据需要修改其中一个属性或两个属性来解决此约束。

约束是表达式(而不是赋值运算符)这一事实意味着可以根据需要切换方程中项目的顺序,以更清楚地表达所需的关系。但是,如果切换顺序,还必须反转乘数和常数。例如,以下两个等式产生相同的约束:

button2.leading = 1.0 × button1.trailing + 8.0

button1.trailing = 1.0 × button2.leading - 8.0

一个有效的布局被定义为一组约束,并且只有一个可能的解决方案。有效的布局也被称为无歧义、无冲突的布局。具有多个解决方案的约束是不明确的。没有有效解决方案的约束是冲突的

此外,约束并不局限于等式关系。它们还可以使用大于或等于(>=)或小于或等于(<=)来描述这两个属性之间的关系。约束的priority(优先级)也在1到1000之间,priority为1000的约束是必要约束,所有低于1000的priority都是可选约束,默认情况下,所有约束都是必需的(priority = 1000)

在求解完所需的约束条件后,Auto Layout将尝试按照从高到低的优先级顺序求解所有可选约束条件。如果它不能解决一个可选的约束,它就会尝试尽可能接近期望的结果,然后转向下一个约束。这种不相等、相等和优先级的组合提供了很大的灵活性和权力。通过组合多个约束,可以定义随着用户界面中元素的大小和位置变化而动态适应的布局。

NSLayoutConstraint的常用属性
@property CGFloat constant;

属性描述加入到参与约束的第二个属性的乘数上的常数。即item1.attribute1 = multiplier × item2.attribute2 + constant中的constant。与其他属性不同,该常量可以在约束创建后修改。 在现有约束上设置常量比删除约束并添加一个与旧约束完全相同的新约束要好得多

@property UILayoutPriority priority;

属性描述约束的优先级

  • 系统提供的约束优先级:
//必要的约束。不要指定超过此数字的布局约束优先级。(最大优先级)
static const UILayoutPriority UILayoutPriorityRequired API_AVAILABLE(ios(6.0)) = 1000; 
//按钮阻止压缩其内容的优先级。(可作为中位优先级)
static const UILayoutPriority UILayoutPriorityDefaultHigh API_AVAILABLE(ios(6.0)) = 750; 
//这可能是拖动调整窗口最终场景大小的适当优先级。
static const UILayoutPriority UILayoutPriorityDragThatCanResizeScene API_AVAILABLE(macCatalyst(13.0)) = 510;
//这是窗口的场景希望保持相同大小的优先级。一般来说,在这种优先级下进行约束是不合适的。
static const UILayoutPriority UILayoutPrioritySceneSizeStayPut API_AVAILABLE(macCatalyst(13.0)) = 500; 
//这是分屏视图分隔符被拖动时的优先级。它不会调整窗口场景的大小。
static const UILayoutPriority UILayoutPriorityDragThatCannotResizeScene API_AVAILABLE(macCatalyst(13.0)) = 490; 
//这是按钮水平拥抱其内容的优先级。(可作为低位优先级)
static const UILayoutPriority UILayoutPriorityDefaultLow API_AVAILABLE(ios(6.0)) = 250; 
//发送-[UIView systemLayoutSizeFittingSize:]时,将计算与目标大小(参数)最接近的大小。
//UILayoutPriorityFittingSizeLevel是视图希望符合该计算中目标大小的优先级。
//很低。一般来说,在这个优先级上进行约束是不合适的。
static const UILayoutPriority UILayoutPriorityFittingSizeLevel API_AVAILABLE(ios(6.0)) = 50;
@property (getter=isActive) BOOL active API_AVAILABLE(macos(10.10), ios(8.0));

属性描述约束的活动状态。可以通过更改此属性来激活或停用约束。只有活动约束会影响计算的布局。对于新创建的约束,默认情况下active属性是NO。激活或停用约束会在视图上调用addConstraint:和removeconconstraint:,在为ios8.0或更高版本开发时,应该使用这个属性,而不是直接调用addConstraint:或removeconconstraint:。

@property (nullable, readonly, assign) id firstItem;

属性描述 :要添加约束的对象。

@property (nullable, readonly, assign) id secondItem;

属性描述 :要添加约束对象的参照对象。

@property (readonly) NSLayoutAttribute firstAttribute;

属性描述 :添加约束的对象的约束属性。

@property (readonly) NSLayoutAttribute secondAttribute;

属性描述 :添加约束时,作为约束对象的参照对象的约束属性。

NSLayoutConstraint的常用函数
+ (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));

函数描述创建一个约束,该约束定义给定视图的指定属性之间的关系。约束表示形式为view1.attr1<relation> = multiplier × view2.attr2 + c 的线性方程。如果要表达的约束没有第二个视图和属性,请使用nil和NSLayoutAttributeNotAnAttribute。

如果此方法用于创建无效约束,则该方法将引发NSInvalidArgumentException异常。例如这种我们无法描述的约束:view1.top = 0.0 x nil.NSLayoutAttributeNotAnAttribute + 200.0(视图1的顶部等于0倍的不知道哪个视图的哪个布局属性加200,你什么都不知道,啊~,我崩溃了)或view1.top = 1.0 x view2.height + 20.0(视图1的顶部等于1倍的视图2的高度加20,我俩的确切关系是什么啊,啊~,我崩溃了)。

参数 :

view1 :约束左侧的视图(要约束的视图)。

attr1 :约束左侧的视图属性。

relation :约束的左侧和右侧之间的关系。

view2 :约束右侧的视图(参照的视图)。

attr2 :约束右侧的视图属性。

multiplier:常数乘以约束右侧的属性,作为获取修改属性的一部分。

c:约束右侧的乘以属性值以后,添加约束值生成最终修改的属性的常量。

返回值 : 用指定的关系、属性、乘数和常量将两个提供的视图关联起来的约束对象。

- (void)addConstraint:(NSLayoutConstraint *)constraint API_AVAILABLE(ios(6.0)); 

函数描述在调用函数的视图或其子视图的布局上添加约束。约束必须只涉及调用函数的视图范围内的视图。具体来说,涉及的任何视图必须是调用函数的视图本身,或者是调用函数的视图的子视图。添加到视图中的约束被称为该视图持有的约束。在评估约束时使用的坐标系统是持有约束的视图的坐标系统。

在为ios8.0或更高版本开发时,将约束的active属性设置为YES,而不是直接调用addConstraint:方法。active属性会自动在正确的视图中添加和删除约束。

参数 :

constraint :要添加到视图中的约束。约束只能引用视图本身或其子视图。

- (void)addConstraints:(NSArray<__kindof NSLayoutConstraint *> *)constraints API_AVAILABLE(ios(6.0));

函数描述在调用函数的视图或其子视图的布局上添加多个约束。所有约束必须只涉及调用函数的视图范围内的视图。具体来说,涉及的任何视图必须是调用函数的视图本身,或者是调用函数的视图的子视图。添加到视图中的约束称为该视图所持有。计算每个约束时使用的坐标系是包含该约束的视图的坐标系。

在为iOS8.0或更高版本开发时,可以使用NSLayoutConstraint类的activateConstraint:方法,而不是直接调用addConstraints:方法。activateConstraints:方法自动将约束添加到正确的视图中。

参数 :

constraints : 要添加到视图中的约束数组。所有约束只能引用视图本身或其子视图。

+ (void)activateConstraints:(NSArray<NSLayoutConstraint *> *)constraints API_AVAILABLE(macos(10.10), ios(8.0));

函数描述激活指定数组中的每个约束。这个方便的方法提供了一种简单的方法来通过一次调用激活一组约束。此方法的效果与将每个约束的active属性设置为YES相同。通常,使用此方法比单独激活每个约束更有效

参数:

constraints : 要激活的一组约束。

+ (void)deactivateConstraints:(NSArray<NSLayoutConstraint *> *)constraints API_AVAILABLE(macos(10.10), ios(8.0));

函数描述这是一种方便的方法,提供了一种简单的方法来通过一次调用禁用一组约束。此方法的效果与将每个约束的active属性设置为NO相同。通常,使用此方法比单独禁用每个约束更有效

参数:

constraints : 要禁用的一组约束。

+ (NSArray<NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(nullable NSDictionary<NSString *, id> *)metrics views:(NSDictionary<NSString *, id> *)views API_AVAILABLE(macos(10.7), ios(6.0), tvos(9.0));

函数描述 :创建由ASCII技术(如可视格式字符串)描述的约束。

参数 :

format : 约束的格式规范。

opts : 描述视觉格式字符串中所有对象的属性和布局方向的选项。

metrics : 出现在可视格式字符串中的常量字典。字典的键必须是视觉格式字符串中使用的字符串值。它们的值必须是NSNumber对象。

views : 以可视格式字符串显示的视图字典。键必须是视觉格式字符串中使用的字符串值,而值必须是视图对象。

返回值 : 一个约束数组,组合在一起,表示所提供的视图与其父视图之间的约束,如可视化格式字符串所述。约束的返回顺序与在可视格式字符串中指定的顺序相同。

注 : 字符串约束的格式规范规则:

|   其含义表示父视图
-   其含义表示距离
V:  其含义表示垂直
H:  其含义表示水平
>=  其含义表示视图间距、宽度和高度必须大于或等于某个值
<=  其含义表示视图间距、宽度和高度必须小宇或等于某个值
==  其含义表示视图间距、宽度或者高度必须等于某个值
@   其含义表示>=、<=、==  其值限制最大设为1000
[view(>=200@300)]  其含义表示视图的宽度为至少为200 不能超过  300
|-[view]-|  其含义表示视图处在父视图的左右边缘内
|-[view]    其含义表示视图处在父视图的左边缘
|[view]     其含义表示视图和父视图左边对齐
|-50.0-[view]-50.0-|  其含义表示离父视图左右间距50
[view(200.0)]  其含义表示视图宽度为 200.0
V:[view2(200.0)]  其含义表示视图高度为 200.0

可以使用字符串来描述视图间的约束,是不是很神奇,而且代码设置约束的代码是不是就减少了:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIView *view2 = [[UIView alloc]initWithFrame:CGRectZero];
    view2.backgroundColor = [UIColor greenColor];
    view2.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:view2];
    //描述view2距其父视图顶部为0
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0.0-[view2](0.0)" options:0 metrics:nil views:@{@"view2": view2}]];
    //描述view2距其父视图左右为0
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-0.0-[view2]-0.0-|" options:0 metrics:nil views:@{@"view2": view2}]];
    //描述view2高度为200
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[view2(200.0)]" options:0 metrics:nil views:@{@"view2": view2}]];

}

效果如图 :

截屏2022-04-12 上午11.58.59.png

然鹅,但凡有一条约束使用字符串描述错误,IOS就会让你NSInvalidArgumentException异常了解一下,坑爹呢啊,我要掀桌了!!

NSLayoutAttribute - 布局属性

NSLayoutAttribute,表示可视对象的一部分,应该用于获取约束的值。

typedef NS_ENUM(NSInteger, NSLayoutAttribute) {
    //对象对齐矩形的左侧
    NSLayoutAttributeLeft = 1,
    //对象对齐矩形的右侧
    NSLayoutAttributeRight,
    //对象对齐矩形的顶部
    NSLayoutAttributeTop,
    //对象对齐矩形的底部
    NSLayoutAttributeBottom,
    //对象对齐矩形的前缘
    NSLayoutAttributeLeading,
    //对象对齐矩形的后缘
    NSLayoutAttributeTrailing,
    //对象对齐矩形的宽度
    NSLayoutAttributeWidth,
    //对象对齐矩形的高度
    NSLayoutAttributeHeight,
    //沿对象对齐矩形x轴的中心
    NSLayoutAttributeCenterX,
    //沿对象对齐矩形的y轴的中心
    NSLayoutAttributeCenterY,
    //对象的基线。对于具有多行文本的对象,这是最下面一行文本的基线。
    NSLayoutAttributeLastBaseline,
#if TARGET_OS_IPHONE
    NSLayoutAttributeBaseline NS_SWIFT_UNAVAILABLE("Use 'lastBaseline' instead") = NSLayoutAttributeLastBaseline,
#else
    NSLayoutAttributeBaseline = NSLayoutAttributeLastBaseline,
#endif
    //对象的基线。对于具有多行文本的对象,这是最上面一行文本的基线
    NSLayoutAttributeFirstBaseline API_AVAILABLE(macos(10.11), ios(8.0)),

#if TARGET_OS_IPHONE
    //对象的左边距。对于UIView对象,页边距由其layoutMargins属性定义
    NSLayoutAttributeLeftMargin API_AVAILABLE(ios(8.0)),
    //对象的右边距。对于UIView对象,页边距由其layoutMargins属性定义
    NSLayoutAttributeRightMargin API_AVAILABLE(ios(8.0)),
    //对象的上边距。对于UIView对象,页边距由其layoutMargins属性定义。
    NSLayoutAttributeTopMargin API_AVAILABLE(ios(8.0)),
    //对象的下边距。对于UIView对象,页边距由其layoutMargins属性定义。
    NSLayoutAttributeBottomMargin API_AVAILABLE(ios(8.0)),
    //对象的前缘边距。对于UIView对象,页边距由其layoutMargins属性定义
    NSLayoutAttributeLeadingMargin API_AVAILABLE(ios(8.0)),
    //对象的后缘边距。对于UIView对象,页边距由其layoutMargins属性定义
    NSLayoutAttributeTrailingMargin API_AVAILABLE(ios(8.0)),
    //对象左右边距之间沿x轴的中心。对于UIView对象,页边距由其layoutMargins属性定义。
    NSLayoutAttributeCenterXWithinMargins API_AVAILABLE(ios(8.0)),
    //对象上下边距之间沿y轴的中心。对于UIView对象,页边距由其layoutMargins属性定义。
    NSLayoutAttributeCenterYWithinMargins API_AVAILABLE(ios(8.0)),
#endif
    //占位符值,用于指示约束的第二项和第二个属性在任何计算中都不使用。创建将常量指定给属性的约束时,请使用此值。
    //例如,item1.height>=40。如果约束只有一个项,请将第二项设置为nil,并将第二个属性设置为NSLayoutAttributeNotAnAttribute
    NSLayoutAttributeNotAnAttribute = 0
};

NSLayoutRelation - 布局关系

typedef NS_ENUM(NSInteger, NSLayoutRelation) {
    //约束要求第一个属性小于或等于修改后的第二个属性。
    NSLayoutRelationLessThanOrEqual = -1,
    //约束要求第一个属性与修改后的第二个属性完全相等。
    NSLayoutRelationEqual = 0,
    //约束要求第一个属性大于或等于修改后的第二个属性。
    NSLayoutRelationGreaterThanOrEqual = 1,
};

NSLayoutAnchor - 布局锚

使用流式 API 创建布局约束对象的工厂类。使用这些约束以编程方式使用 Auto Layout 定义布局。不要直接创建 NSLayoutConstraint 对象,而是从一个你想要约束的UIView、 NSView或者UILayoutGuide对象开始,然后选择一个对象的锚定属性。这些属性对应于 Auto Layout 中使用的主要 NSLayoutAttribute 值,并提供一个适当的NSLayoutAnchor子类来创建该属性的约束。使用锚定的方法来构造约束。

NSLayoutAnchor类比直接使用NSLayoutConstraint的API创建约束代码更简洁,更容易阅读。NSLayoutAttribute的子类提供了额外的类型检查,在一定程度上防止创建无效的约束,但仍旧需要仔细检查约束,以避免无效约束造成崩溃。

NSLayoutAnchor的常用函数
- (NSLayoutConstraint *)constraintEqualToAnchor:(NSLayoutAnchor<AnchorType> *)anchor __attribute__((warn_unused_result));

函数描述返回一条约束,此方法定义两个锚点相等(=)的关系。其中,调用此方法的锚点表示要布局的锚点,参数锚点表示布局要参照的锚点。

参数 :

anchor : 一个来自UIView、NSView或UILayoutGuide对象的布局锚。用来作为参照锚点。你必须使用NSLayoutAnchor的一个子类来匹配当前的锚点。例如,如果在一个NSLayoutXAxisAnchor对象上调用这个方法,这个参数必须是另一个NSLayoutXAxisAnchor。

返回值 : 一个NSLayoutConstraint对象,它定义了两个布局锚点之间的相等关系。

- (NSLayoutConstraint *)constraintEqualToAnchor:(NSLayoutAnchor<AnchorType> *)anchor constant:(CGFloat)c __attribute__((warn_unused_result));

函数描述返回一条约束,此方法定义第一个锚点等于(=)第二个锚点加上常量偏移量的关系。其中,调用此方法的锚点表示要布局的锚点,参数锚点表示布局要参照的锚点。值c表示恒定偏移量。

参数 :

anchor : 一个来自UIView、NSView或UILayoutGuide对象的布局锚。用来作为参照锚点。你必须使用NSLayoutAnchor的一个子类来匹配当前的锚点。例如,如果在一个NSLayoutXAxisAnchor对象上调用这个方法,这个参数必须是另一个NSLayoutXAxisAnchor。

c : 约束的恒定偏移量。

返回值 :一个 NSLayoutConstraint对象,它定义了两个布局锚点之间的相等关系与一个恒定偏移量。

- (NSLayoutConstraint *)constraintGreaterThanOrEqualToAnchor:(NSLayoutAnchor<AnchorType> *)anchor __attribute__((warn_unused_result));

函数描述返回一条约束,此方法定义第一个锚点大于等于(>=)第二个锚点。其中,调用此方法的锚点表示要布局的锚点,参数锚点表示布局要参照的锚点。

参数 :

anchor : 一个来自UIView、NSView或UILayoutGuide对象的布局锚。用来作为参照锚点。你必须使用NSLayoutAnchor的一个子类来匹配当前的锚点。例如,如果在一个NSLayoutXAxisAnchor对象上调用这个方法,这个参数必须是另一个NSLayoutXAxisAnchor。

返回值 :一个 NSLayoutConstraint对象,它定义了布局锚点大于或等于参照锚点。

- (NSLayoutConstraint *)constraintGreaterThanOrEqualToAnchor:(NSLayoutAnchor<AnchorType> *)anchor constant:(CGFloat)c __attribute__((warn_unused_result));

函数描述返回一条约束,此方法定义第一个锚点大于或等于(>=)第二个锚点加上常量偏移量。其中,调用此方法的锚点表示要布局的锚点,参数锚点表示布局要参照的锚点。

参数 :

anchor : 一个来自UIView、NSView或UILayoutGuide对象的布局锚。用来作为参照锚点。你必须使用NSLayoutAnchor的一个子类来匹配当前的锚点。例如,如果在一个NSLayoutXAxisAnchor对象上调用这个方法,这个参数必须是另一个NSLayoutXAxisAnchor。

c : 约束的恒定偏移量。

返回值 :一个 NSLayoutConstraint对象,它定义了两个布局锚点之间大于或等于(>=)的关系与一个恒定偏移量。

- (NSLayoutConstraint *)constraintLessThanOrEqualToAnchor:(NSLayoutAnchor<AnchorType> *)anchor __attribute__((warn_unused_result));

函数描述返回一条约束,此方法定义第一个锚点小于于或等于(<=)第二个锚点。其中,调用此方法的锚点表示要布局的锚点,参数锚点表示布局要参照的锚点。

参数 :

anchor : 一个来自UIView、NSView或UILayoutGuide对象的布局锚。用来作为参照锚点。你必须使用NSLayoutAnchor的一个子类来匹配当前的锚点。例如,如果在一个NSLayoutXAxisAnchor对象上调用这个方法,这个参数必须是另一个NSLayoutXAxisAnchor。

返回值 :一个 NSLayoutConstraint对象,它定义了布局锚点小于或等于参照锚点。

- (NSLayoutConstraint *)constraintLessThanOrEqualToAnchor:(NSLayoutAnchor<AnchorType> *)anchor constant:(CGFloat)c __attribute__((warn_unused_result));

函数描述返回一条约束,此方法定义第一个锚点小于或等于(<=)第二个锚点加上常量偏移量。其中,调用此方法的锚点表示要布局的锚点,参数锚点表示布局要参照的锚点。

参数 :

anchor : 一个来自UIView、NSView或UILayoutGuide对象的布局锚。用来作为参照锚点。你必须使用NSLayoutAnchor的一个子类来匹配当前的锚点。例如,如果在一个NSLayoutXAxisAnchor对象上调用这个方法,这个参数必须是另一个NSLayoutXAxisAnchor。

c : 约束的恒定偏移量。

返回值 :一个 NSLayoutConstraint对象,它定义了两个布局锚点之间小于或等于(<=)的关系与一个恒定偏移量。

小结 :上面的方法分别定义了锚点间等于(=)、大于等于(>=)、小于等于(<=)的关系,以及是否有偏移量参与锚点间的定位,偏移量的数值均以点为单位测量。根据布局锚点的类型,偏移量的值可以以不同的方式进行解释

  • 对于NSLayoutXAxisAnchor对象,当使用leadingAnchor(前缘)或trailingAnchor(后缘)时,偏移量增加,锚点定位的视图后移,偏移量减少,锚点定位的视图前移。当使用leftAnchor(左侧)或rightAnchor(右侧)时,偏移量增加,锚点定位的视图右移,偏移量减少,锚点定位的视图左移。

  • 对于NSLayoutYAxisAnchor对象,当使用topAnchor(顶部)或bottomAnchor(底部)时,偏移量增加,锚点定位的视图下移,偏移量减少,锚点定位的视图上移。

  • 对于NSLayoutDimension对象,偏移量增加,锚点定位的视图增大,偏移量减少,锚点定位的视图减小。

NSLayoutXAxisAnchor - X轴布局锚

继承自NSLayoutAnchor,NSLayoutXAxisAnchor会将类型信息添加到继承自NSLayoutAnchor的方法中。具体来说,NSLayoutAnchor声明的泛型方法现在必须接受一个匹配的NSLayoutXAxisAnchor对象,这在一定程度上防止创建无效的约束。

例如 :

// 此约束有效
[self.cancelButton.leadingAnchor constraintEqualToAnchor:self.saveButton.trailingAnchor  constant: 8.0].active = true;
 
// 这个约束产生一个不兼容的指针类型警告
[self.cancelButton.leadingAnchor constraintEqualToAnchor:self.saveButton.topAnchor constant: 8.0].active = true;
NSLayoutXAxisAnchor的常用函数
- (NSLayoutDimension *)anchorWithOffsetToAnchor:(NSLayoutYAxisAnchor *)otherAnchor API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0));

函数描述从两个锚点创建布局尺寸对象。使用返回的对象来定义当前锚点相对于和 otherAnchor 参数中的锚点对象之间的空间的约束。

参数 :

otherAnchor : 创建布局尺寸时要使用的相对锚点。

返回值 : 由两个锚表示的NSLayoutDimension对象。

NSLayoutYAxisAnchor - Y轴布局锚

继承自NSLayoutAnchor,NSLayoutYAxisAnchor将类型信息添加到继承自NSLayoutAnchor的方法中。具体来说,NSLayoutAnchor声明的泛型方法现在必须接受一个匹配的NSLayoutYAxisAnchor对象,这在一定程度上防止创建无效的约束。

例如 :

// 此约束有效
[self.cancelButton.leadingAnchor constraintEqualToAnchor:self.saveButton.trailingAnchor  constant: 8.0].active = true;
 
// 这个约束产生一个不兼容的指针类型警告
[self.cancelButton.topAnchor constraintEqualToAnchor:self.saveButton.trailingAnchor constant: 8.0].active = true;
NSLayoutYAxisAnchor的常用函数
- (NSLayoutDimension *)anchorWithOffsetToAnchor:(NSLayoutYAxisAnchor *)otherAnchor API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0));

函数描述从两个锚点创建布局尺寸对象。使用返回的对象来定义当前锚点相对于和 otherAnchor 参数中的锚点对象之间的空间的约束。

参数 :

otherAnchor : 创建布局尺寸时要使用的相对锚点。

返回值 : 由两个锚表示的NSLayoutDimension对象。

NSLayoutDimension - 布局尺寸

继承自NSLayoutAnchor,除了为创建约束提供特定大小的方法外,这个类还将类型信息添加到继承自NSLayoutAnchor的方法中。具体来说,由NSLayoutAnchor声明的泛型方法现在必须接受一个匹配的NSLayoutDimension对象,这在一定程度上防止创建无效的约束。

例如 :

// 此约束有效
[self.saveButton.widthAnchor constraintEqualToAnchor:self.cancelButton.widthAnchor].active = YES;
 
// 这个约束产生一个不兼容的指针类型警告
[self.saveButton.widthAnchor constraintEqualToAnchor:self.cancelButton.leadingAnchor].active = YES;
NSLayoutDimension的常用函数
- (NSLayoutConstraint *)constraintEqualToConstant:(CGFloat)c __attribute__((warn_unused_result));

函数描述返回一条约束,该约束为锚定视图的size属性中宽或高定义为常量大小。这取决于视图锚的类型。

参数 :

c :一个常量,表示与此锚定视图size属性某一维度(宽或高)的关联的属性的大小。

返回值 :一个NSLayoutConstraint对象,它为与这个锚定视图size属性某一维度(宽或高)定义一个常量大小。

UIView (UIViewLayoutConstraintCreation) - 创建视图布局约束

扩展于UIView中的属性为我们提供了创建约束的便利锚点,如下 :

//表示视图框架前缘的布局定位
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *leadingAnchor API_AVAILABLE(ios(9.0));
//表示视图框架后缘的布局定位
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *trailingAnchor API_AVAILABLE(ios(9.0));
//表示视图框架左侧的布局定位
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *leftAnchor API_AVAILABLE(ios(9.0));
//表示视图框架右侧的布局定位
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *rightAnchor API_AVAILABLE(ios(9.0));
//表示视图框架顶部的布局定位
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *topAnchor API_AVAILABLE(ios(9.0));
//表示视图框架底部的布局定位
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *bottomAnchor API_AVAILABLE(ios(9.0));
//表示视图框架宽
@property(nonatomic,readonly,strong) NSLayoutDimension *widthAnchor API_AVAILABLE(ios(9.0));
//表示视图框架高
@property(nonatomic,readonly,strong) NSLayoutDimension *heightAnchor API_AVAILABLE(ios(9.0));
//表示视图框架X轴中心点
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *centerXAnchor API_AVAILABLE(ios(9.0));
//表示视图框架Y轴中心点
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *centerYAnchor API_AVAILABLE(ios(9.0));
//表示视图中最顶行文本的基线
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *firstBaselineAnchor API_AVAILABLE(ios(9.0));
//表示视图中文本最底行的基线。
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *lastBaselineAnchor API_AVAILABLE(ios(9.0));

\color{red}{此时我们可以更方便的为视图添加约束了:}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIView *view2 = [[UIView alloc]initWithFrame:CGRectZero];
    view2.backgroundColor = [UIColor greenColor];
    view2.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:view2];
    
    [view2.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES;
    [view2.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
    [view2.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
    [view2.heightAnchor constraintEqualToConstant:200].active = YES;
}

\color{red}{我们也可以一次添加一组约束:}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIView *view2 = [[UIView alloc]initWithFrame:CGRectZero];
    view2.backgroundColor = [UIColor greenColor];
    view2.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:view2];
    
    [NSLayoutConstraint activateConstraints:@[
        [view2.topAnchor constraintEqualToAnchor:self.view.topAnchor],
        [view2.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [view2.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
        [view2.heightAnchor constraintEqualToConstant:200],
    ]];
}

竖屏时效果如下 :

截屏2022-04-13 上午11.39.18.png

横屏时效果如下 :

截屏2022-04-13 上午11.39.32.png

\color{red}{我们还可以根据调整常数constant更改约束:}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIView *view1 = [[UIView alloc]initWithFrame:CGRectZero];
    view1.backgroundColor = [UIColor blueColor];
    view1.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:view1];
    
    [NSLayoutConstraint activateConstraints:@[
        [view1.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
        [view1.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [view1.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
        [view1.heightAnchor constraintEqualToConstant:200],
    ]];
    
    __block UIView *view2 = [[UIView alloc]initWithFrame:CGRectZero];
    view2.backgroundColor = [UIColor greenColor];
    view2.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:view2];
    
    [NSLayoutConstraint activateConstraints:@[
        [view2.topAnchor constraintEqualToAnchor:self.view.topAnchor],
        [view2.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [view2.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
        [view2.heightAnchor constraintEqualToConstant:200],
    ]];
    
    //获取self.view上添加的约束
    NSArray<__kindof NSLayoutConstraint *> *constraints = view2.superview.constraints;
    //遍历self.view上添加的约束
    [constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        //根据约束对象找到view2
        if ([obj.firstItem isEqual:view2]) {
            //找到view2的顶部约束
            if (obj.firstAttribute == NSLayoutAttributeTop) {
                //修改constant常量
                obj.constant = 50;
            }
            //获取view2对象
            view2 = obj.firstItem;
            //获取view2上添加的约束
            NSArray<__kindof NSLayoutConstraint *> *view2constraints = view2.constraints;
            //遍历view2上添加的约束
            [view2constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull view2obj, NSUInteger idx, BOOL * _Nonnull stop) {
                ///找到view2的高度约束
                if (view2obj.firstAttribute == NSLayoutAttributeHeight) {
                    //修改constant常量
                    view2obj.constant = 100;
                }
            }];
        }
    }];
}

修改约束常数后效果如图 :

截屏2022-04-14 上午10.30.27.png

UIView (UIConstraintBasedLayoutInstallingConstraints) -- 基于约束的布局安装约束

扩展自UIView,用于添加约束,约束通常安装在视图约束中涉及的最近的父视图上。约束中的数字将在安装该约束的视图的坐标系中进行解释。

UIView (UIConstraintBasedLayoutInstallingConstraints)的常用属性
@property(nonatomic,readonly) NSArray<__kindof NSLayoutConstraint *> *constraints API_AVAILABLE(ios(6.0));

属性描述 : 视图所包含的约束。

UIView (UIConstraintBasedLayoutInstallingConstraints)的常用函数
//为视图添加约束,在为iOS 8.0或更高版本开发时,采用将约束的active属性设置为YES。
- (void)addConstraint:(NSLayoutConstraint *)constraint API_AVAILABLE(ios(6.0)); 
//为视图添加一组约束,在为iOS 8.0或更高版本开发时,请使用NSLayoutConstraint类的activateConstraints:方法。
- (void)addConstraints:(NSArray<__kindof NSLayoutConstraint *> *)constraints API_AVAILABLE(ios(6.0));
//为视图移除指定的约束,在为iOS 8.0或更高版本开发时,采用将约束的active属性设置为NO。
- (void)removeConstraint:(NSLayoutConstraint *)constraint API_AVAILABLE(ios(6.0)); 
//为视图移除指定的一组约束,在为iOS 8.0或更高版本开发时,请使用NSLayoutConstraint类的deactivateConstraints:方法。
- (void)removeConstraints:(NSArray<__kindof NSLayoutConstraint *> *)constraints API_AVAILABLE(ios(6.0)); 

UILayoutGuide - 布局占位

UILayoutGuide会创建一个用于占位的对象,它可以表示为一个矩形,并与自动布局交互,它不会显示在视图层次结构中,可以用来包含和封装部分用户界面,使复杂的页面模块化,简化自动布局约束逻辑。对比使用其它会显示在视图层次结构中的对象作为占位图,UILayoutGuide创建和维护成本更低,也更安全。使用init方法创建一个UILayoutGuide,然后用[UIView addLayoutGuide:]添加之后设置约束。

\color{red}{例如我们使用UILayoutGuide包含三个视图放置在视图中心 :}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UILayoutGuide *packGuide = [[UILayoutGuide alloc]init];
    [self.view addLayoutGuide:packGuide];
    [NSLayoutConstraint activateConstraints:@[
        [packGuide.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
        [packGuide.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
        [packGuide.widthAnchor constraintEqualToConstant:300],
        [packGuide.heightAnchor constraintEqualToConstant:300],
    ]];
    
    UIView *redView = [[UIView alloc]initWithFrame:CGRectZero];
    redView.backgroundColor = [UIColor redColor];
    redView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:redView];
    [NSLayoutConstraint activateConstraints:@[
        [redView.topAnchor constraintEqualToAnchor:packGuide.topAnchor],
        [redView.leadingAnchor constraintEqualToAnchor:packGuide.leadingAnchor],
        [redView.widthAnchor constraintEqualToConstant:150],
        [redView.heightAnchor constraintEqualToConstant:150],
    ]];
    
    UIView *greenView = [[UIView alloc]initWithFrame:CGRectZero];
    greenView.backgroundColor = [UIColor greenColor];
    greenView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:greenView];
    [NSLayoutConstraint activateConstraints:@[
        [greenView.topAnchor constraintEqualToAnchor:packGuide.topAnchor],
        [greenView.trailingAnchor constraintEqualToAnchor:packGuide.trailingAnchor],
        [greenView.widthAnchor constraintEqualToConstant:150],
        [greenView.heightAnchor constraintEqualToConstant:150],
    ]];
    
    UIView *yellowView = [[UIView alloc]initWithFrame:CGRectZero];
    yellowView.backgroundColor = [UIColor yellowColor];
    yellowView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:yellowView];
    [NSLayoutConstraint activateConstraints:@[
        [yellowView.centerXAnchor constraintEqualToAnchor:packGuide.centerXAnchor],
        [yellowView.bottomAnchor constraintEqualToAnchor:packGuide.bottomAnchor],
        [yellowView.widthAnchor constraintEqualToConstant:150],
        [yellowView.heightAnchor constraintEqualToConstant:150],
    ]];
    
}

UILayoutGuide放置在视图上,但是是不显示在图层结构中的:

截屏2022-04-14 下午4.52.06.png
截屏2022-04-14 下午4.54.11.png

UILayoutGuide同样提供了布局使用的锚点属性:

@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *leadingAnchor;
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *trailingAnchor;
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *leftAnchor;
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *rightAnchor;
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *topAnchor;
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *bottomAnchor;
@property(nonatomic,readonly,strong) NSLayoutDimension *widthAnchor;
@property(nonatomic,readonly,strong) NSLayoutDimension *heightAnchor;
@property(nonatomic,readonly,strong) NSLayoutXAxisAnchor *centerXAnchor;
@property(nonatomic,readonly,strong) NSLayoutYAxisAnchor *centerYAnchor;

SizeClasses(屏幕适配)

在Interface Builder(故事板)中使用,通过结合Auto Layout技术,使设置的约束可以适应不同设备的屏幕大小。苹果将不同的设备在不同的状态下,根据屏幕的宽高进行了分类,屏幕的宽高分为Compact(紧凑)、Regular(常规)类型与Any(任意)类型,然后可以根据屏幕宽高的分类,对视图进行约束,我们通过iPhone11为一个App设置启动图观察一下:

首先在LaunchScreen.storyboard中在设备竖屏的情况下,设置启动图,如图:

截屏2022-04-15 16.33.06.png

但当屏幕横屏时,启动图就产生了问题,这显然不是我们需要的,如图:

截屏2022-04-15 16.39.31.png

我们先去观察一下竖屏时,Xcode的设置面板:

截屏2022-04-15 16.08.18.png

我们将竖屏状态下的约束进行逐条的安装,安装操作如下:

截屏2022-04-15 16.34.00.png

之后我们将设置状态改为横屏,并取消默认的约束安装,此时视图约束开始报错,因为横屏状态下,控件的约束还都未设置,需要我们重新添加。操作如下:

截屏2022-04-15 17.05.54.png
截屏2022-04-15 17.12.46.png

重新设置横屏状态下的约束后,横屏时,启动图展示如下 :

截屏2022-04-18 11.18.40.png

设置的约束一定要区分状态进行安装,否则页面容易错乱,然后我们对比一下不同状态时,安装约束给出的默认值:

竖屏时 :

截屏2022-04-18 10.24.23.png

横屏时:

截屏2022-04-18 11.20.41.png

我们可以对比出在iPhone11下,竖屏时,屏幕宽度是Compact(紧凑)的,高度是Regular(常规)的,但设备横屏时,宽度是Regular(常规)的,高度是Compact(紧凑)的,根据这种分类,我们设置了视图不同的约束。但是这些分类在不同的设备中是不一样的,在iPhoneSE下,竖屏时,屏幕宽度是Compact(紧凑)的,高度是Regular(常规)的,但设备横屏时,屏幕宽度与高度是Compact(紧凑)的,而在ipad中,竖屏与横屏时,宽度与高度都是Regular(常规)的。这需要多种不同的约束让视图以不同的状态适应屏幕。

图片资源也是有这些分类的,我们可以根据分类的不同设置不同的图片,如图:

截屏2022-04-18 11.43.39.png
截屏2022-04-18 11.45.35.png
截屏2022-04-18 11.46.07.png

竖屏时:

截屏2022-04-18 11.51.56.png

横屏时:

截屏2022-04-18 11.51.34.png

添加约束的注意事项

  • 1.如果视图的布局方式为autolayout,再添加约束之前要将视图的translatesAutoresizingMaskIntoConstraints属性设为NO,否则会约束冲突。

  • 2.确保将子视图添加到父视图后再添加约束,如果子视图未添加到父视图就添加约束么,会抛出NSInternalInconsistencyException异常,提示原因为无法设置视图层次结构未准备好约束的布局。

添加约束选择目标View的规则

    1. 对于两个同层级View之间的约束关系,添加到他们的父View上。
    1. 对于两个不同层级View之间的约束关系,添加到他们最近的共同的父View上
    1. 对于有层次关系的两个View之间的约束关系,添加到层次较高的父View上

至此,系统提供的糟糕的约束方法我们可以简单的使用了,然后我们愉快的去使用Masonry,有封装好的约束布局库,和系统方法较什么劲呢🤪。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267