iOS自动布局 - Masonry

Masonry是对AutoLayout封装而成的一个轻量级的布局框架。其语法优雅、代码简洁,可读性很高,而且同时支持iOS和Mac OS X。

这篇文章主要讲解Masonry的用法。

1 安装Masonry

这里我们使用CocoaPods来安装Masonry。

首先打开Podfile文件,添加下面的内容:

pod 'Masonry'

如果项目中还没有Podfile文件,可以在当前项目中手动创建一个,也可以通过终端定位到项目目录,使用pod init命令创建一个Podfile文件。

在命令行中输入pod install安装Masonry:

$ pod install

如果出现下面的提示内容,则表示Masonry安装成功:

Analyzing dependencies
Downloading dependencies
Installing Masonry (1.1.0)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `MasonryDemo.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.

2 Masonry的使用

首先关闭之前创建的项目文件,打开后缀名为xcworkspace的项目。在需要使用Masonry的项目文件中导入Masonry的头文件。

这里我们创建一个greenView的子视图,并将它添加到self.view上,然后使用Masonry对它进行布局,让其距离父视图的上下左右边距均为20:

#import "ViewController.h"
#import "Masonry.h"

@interface ViewController ()
@property (nonatomic, strong) UIView *greenView;
@end

@implementation ViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 添加子视图
    [self.view addSubview:self.greenView];
    
    // 添加布局
    [self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view).offset(20);
        make.right.equalTo(self.view).offset(-20);
        make.top.equalTo(self.view).offset(20);
        make.bottom.equalTo(self.view).offset(-20);
    }];
}

- (UIView *)greenView
{
    if (!_greenView) {
        _greenView = [[UIView alloc] init];
        _greenView.backgroundColor = [UIColor greenColor];
        _greenView.layer.borderColor = [UIColor blackColor].CGColor;
        _greenView.layer.borderWidth = 2.0;
    }
    return _greenView;
}

运行结果:

在上面的代码中,我们调用了mas_makeConstraints:方法给视图添加布局信息:

[self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(self.view).offset(20);
    make.left.equalTo(self.view).offset(20);
    make.bottom.equalTo(self.view).offset(-20);
    make.right.equalTo(self.view).offset(-20);
}];

上面的leftrighttopbottom均是Masonry支持的属性,分别表示左侧、右侧、顶部、底部;equalTo表示约束的关系;offset表示偏移值,另外还有multipliedBy表示倍数值。

上面的代码也可以写成:

[self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.mas_equalTo(self.view.mas_top).offset(20);
    make.left.mas_equalTo(self.view.mas_left).offset(20);
    make.bottom.mas_equalTo(self.view.mas_bottom).offset(-20);
    make.right.mas_equalTo(self.view.mas_right).offset(-20);
}];

还可以写成:

[self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(@20);
    make.left.equalTo(@20);
    make.bottom.equalTo(@-20);
    make.right.equalTo(@-20);
}];

和:

[self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.mas_equalTo(20);
    make.left.mas_equalTo(20);
    make.bottom.mas_equalTo(-20);
    make.right.mas_equalTo(-20);
}];

还可以简化为:

[self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(20, 20, 20, 20));
}];

和:

[self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.left.bottom.and.right.equalTo(self.view).with.insets(UIEdgeInsetsMake(20, 20, 20, 20));
}];

值得一提的是,这里的andwith其实没有执行任何操作,实际上是可以省略的,但是用在这种链式语法中就很巧妙,增加了语句的可读性,而且特别容易理解。

如果不想使用mas_前缀,可以在导入Masonry之前添加下面的宏:

#define MAS_SHORTHAND

例如:

#define MAS_SHORTHAND

#import "ViewController.h"
#import "Masonry.h"

...
[self.greenView makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(20, 20, 20, 20));
}];

另外,也可以将#define MAS_SHORTHAND写在PrefixHeader.pch文件中。

创建PrefixHeader.pch文件

打开File\New\File...,选择iOS\Other\PCH File,点击Next,修改要保存的文件名称(这里使用默认的),点击Create即可。

然后在PrefixHeader.pch文件中添加下面的代码:

#define MAS_SHORTHAND

#import "Masonry.h"

这样下次再需要使用Masonry的时候就不需要再导入了,并且也可以省略掉mas_前缀。

需要注意的是,PrefixHeader.pch文件需要配置一下才能生效。

配置PrefixHeader.pch文件

选中项目,然后TARGETS\Build Settings,然后在搜索框中输入prefix header,然后定位到Apple Clang - Language这一栏,将Precompile Prefix Header的值改为YES,然后在Prefix Header右侧双击,在弹出的输入框中填写PrefixHeader.pch文件的路径即可。

这里可以使用$(SRCROOT)/项目名称/xxx.pch这样的格式来设置路径,其中$(SRCROOT)表示项目所在的目录。

3 Masonry支持的属性

前面代码中我们用到的leftrighttopbottom属性,其实Masonry还有很多属性。

下面列出的是标准属性:

@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
@property (nonatomic, strong, readonly) MASConstraint *width;
@property (nonatomic, strong, readonly) MASConstraint *height;
@property (nonatomic, strong, readonly) MASConstraint *centerX;
@property (nonatomic, strong, readonly) MASConstraint *centerY;
@property (nonatomic, strong, readonly) MASConstraint *baseline;

除此之外,Masonry还支持margin和safeArea相关的属性:

@property (nonatomic, strong, readonly) MASViewAttribute *leftMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *rightMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *topMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *bottomMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *leadingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *trailingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASViewAttribute *centerYWithinMargins;

@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideTop;
@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideBottom;
@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideLeft;
@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideRight;

另外,Masonry还提供了一些方便的方法,可以同时创建多种约束,它们被称为MASCompositeConstraints(复合约束):

@property (nonatomic, strong, readonly) MASConstraint *edges;
@property (nonatomic, strong, readonly) MASConstraint *size;
@property (nonatomic, strong, readonly) MASConstraint *center;
  • edges

    make.edges.equalTo(self.view);
    make.edges.mas_equalTo(UIEdgeInsetsMake(20, 20, 20, 20));
    make.edges.equalTo(self.view).insets(UIEdgeInsetsMake(20, 20, 20, 20));
    
  • size

    make.size.greaterThanOrEqualTo(view1);
    make.size.mas_equalTo(CGSizeMake(300, 200));
    
    make.size.equalTo(self.view).sizeOffset(CGSizeMake(100, -50));    
    // 表示:view.width = view.superView.width + 100, view.height = view.superView.height - 50
    
  • center

    make.center.equalTo(self.view);
    make.center.equalTo(@(CGPointMake(100, 200)));
    make.center.mas_equalTo(CGPointMake(100, 200));
    
    make.center.equalTo(self.view).centerOffset(CGPointMake(-5, 10)); 
    // 表示: view.centerX = view.superView.centerX - 5, view.centerY = view.superView.centerY + 10
    

4 Masonry中约束的关系

  • equalTo
  • lessThanOrEqualTo
  • greaterThanOrEqualTo

如果希望greenView的左侧大于或者等于self.view的左侧,我们可以在代码中可以这样写:

[self.greenView makeConstraints:^(MASConstraintMaker *make) {
    make.left.greaterThanOrEqualTo(self.view);
}];

如果希望greenView的宽度大于等于200,小于等于400,我们可以在代码中这样写:

[self.greenView makeConstraints:^(MASConstraintMaker *make) {
    // width >= 200 && width <= 400
    make.width.greaterThanOrEqualTo(@200);
    make.width.lessThanOrEqualTo(@400);
}];

5 Masonry中约束的优先级

  • priority可以指定一个确切的优先级值,范围在0 ~ 1000,值越大优先级越高。
  • priorityHigh相当于UILayoutPriorityDefaultHigh,优先级值为750。
  • priorityMedium介于高优先级和低优先级之间,优先级值在250 ~ 750之间。
  • priorityLow相当于UILayoutPriorityDefaultLow,优先级值为250。

优先级可以写在约束链的末尾,例如:

make.top.equalTo(self.view).with.priority(800);
make.bottom.equalTo(self.view).priorityMedium();
make.left.equalTo(self.view).priorityLow();
make.right.equalTo(self.view).priorityHigh();

6 创建约束

自动布局允许将宽度和高度设置为常量值,例如:

[self.greenView makeConstraints:^(MASConstraintMaker *make) {
    make.width.equalTo(@300);
    make.height.equalTo(@200);
}];

但是,自动布局不允许将对齐属性(如leftrightcenterY等)设置为常量值。如果为这些属性传递了一个NSNumber类型的值,Masonry会将这些变为相对于视图的父视图的约束,即:

[self.greenView makeConstraints:^(MASConstraintMaker *make) {
    make.left.equalTo(@10);
}];

相当于:

[self.greenView makeConstraints:^(MASConstraintMaker *make) {
    make.left.equalTo(self.view.mas_left).offset(10);
}];

表示:

view.left = view.superView.left + 10;

除了使用NSNumber类型创建约束以外,还可以使用结构等,示例如下:

make.size.equalTo(@(CGSizeMake(300, 200)));
make.size.mas_equalTo(CGSizeMake(300, 200));
make.edges.mas_equalTo(UIEdgeInsetsMake(20, 20, 20, 20));

make.left.mas_equalTo(self.view).mas_offset(UIEdgeInsetsMake(20, 20, 20, 20));  

上面最后一行代码的效果等同于:

make.left.mas_equalTo(self.view).mas_offset(20);

另外,还可以使用数组来传递不同类型的值创建约束,例如:

make.height.equalTo(@[view1.mas_height, view2.mas_height]);
make.height.equalTo(@[view1, view2]);
make.left.equalTo(@[view1.right, @100, view2]);

7 使用Masonry更新约束

有时候我们需要更改现有的约束以设置动画,或者删除、替换约束,在Mansory中,我们可以使用下面的方法来更新约束。

7.1 将约束的表达式结果保存为属性或变量,通过使用该约束变量或属性来添加或者删除约束。

例如,我们可以声明一个MASConstraint类型的属性:

@property (nonatomic, strong) MASConstraint *topConstraint;

然后定义该属性:

[self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    self.topConstraint = make.top.equalTo(self.view).with.offset(20);
}];

后面如果需要重新该约束值,可以这样写:

self.topConstraint.mas_equalTo(20);

另外,还可以根据需要调用下面的方法:

[self.topConstraint install];   // 创建NSLayoutConstraint并将其添加到适当的视图上。
[self.topConstraint uninstall]; // 删除之前安装的NSLayoutConstraint
[self.topConstraint activate];  // 如果操作系统支持NSLayoutConstraint,可以调用该方法来激活约束。否则需要调用install方法。
[self.topConstraint deactivate];    // 取消激活(使无效)之前安装/激活的NSLayoutConstraint。

以之前的greenView为例,我们在self.view上添加一个点击手势,每次点击的时候更新greenView的edges约束:

@property (nonatomic, strong) MASConstraint *edgesConstraint;

- (void)viewDidLoad
{
    ...
    
    // 添加子视图
    [self.view addSubview:self.greenView];
    
    // 给self.view添加点击手势
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
    [self.view addGestureRecognizer:tap];
    
    // 给greenView添加布局
    [self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
        self.edgesConstraint = make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(20, 20, 20, 20));
    }];
}

- (void)tap:(UITapGestureRecognizer *)tap
{
    int i = arc4random_uniform(10);
    
    // 更新greenView的布局
    self.edgesConstraint.mas_equalTo(UIEdgeInsetsMake(20 * i, 20 * i, 20 * i, 20 * i));
}

效果如下:

另外,还可以通过将约束存储在数组中来引用多个约束。

7.2 使用mas_updateConstraints方法更新约束。

如果我们只需要更新约束的常量值,我们可以使用mas_updateConstraints方法来代替mas_makeConstraints方法。mas_updateConstraints对于更新一组约束很有用。

比如我们可以使用下面的代码替换tap:方法中的内容,实现之前的效果:

- (void)tap:(UITapGestureRecognizer *)tap
{
    int i = arc4random_uniform(10);
    
    // 使用mas_updateConstraints更新约束
    [self.greenView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(20 * i, 20 * i, 20 * i, 20 * i));
    }];
}

7.3 使用mas_remakeConstraints方法更新约束。

mas_remakeConstraints类似于mas_updateConstraints,但它不是更新常量值,而是在再次安装之前删除所有约束。 这样我们就可以提供不同的约束,而无需保留对要删除约束的引用。

- (void)tap:(UITapGestureRecognizer *)tap
{
    int i = arc4random_uniform(10);
    
    // 使用remakeConstraints更新约束
    [self.greenView mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(20 * i, 20 * i, 20 * i, 20 * i));
    }];
}

7.4 总结

  • mas_makeConstraints只负责新增约束,每次调用会创建新的约束,如果对已存在的约束再次创建,会提示冲突(此时会存在对同一属性的两条约束)。
  • mas_updateConstraints用于更新之前创建的约束,如果对已存在的约束进行更新,没有任何问题(此时只存在对同一属性的最新一条约束)。
  • mas_remakeConstraints会清除之前的所有约束,仅保留最新的约束。

8 使用Masonry定位发生冲突的约束

使用Masonry可以为视图和约束创建有意义的名称,这样当设置的约束发生冲突时,我们可以定位到具体的元素。

例如:如果我们使用下面的代码为greenView添加布局:

[self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(20, 20, 20, 20));
    make.height.lessThanOrEqualTo(@(200));
}];

运行的时候控制台会提示约束冲突:

[LayoutConstraints] Unable to simultaneously satisfy constraints.
...... 
(
    "<MASLayoutConstraint:0x600000f01ce0 UIView:0x7fbdace024b0.top == UIView:0x7fbdace0b630.top + 20>",
    "<MASLayoutConstraint:0x600000f01da0 UIView:0x7fbdace024b0.bottom == UIView:0x7fbdace0b630.bottom - 20>",
    "<MASLayoutConstraint:0x600000f01e60 UIView:0x7fbdace024b0.height <= 200>",
    "<NSLayoutConstraint:0x600000818e10 UIView:0x7fbdace0b630.height == 736>"
)

Will attempt to recover by breaking constraint 
<MASLayoutConstraint:0x600000f01e60 UIView:0x7fbdace024b0.height <= 200>
......

面对这样的输出,我们很难找到造成约束冲突的问题。

Masonry为NSLayoutConstraint添加了一个分类,重写了- (NSString *)description方法的默认实现。这样我们就可以为视图和约束提供有意义的名称,并且还可以轻松选择由Masonry创建的约束。

更改上面的代码如下:

self.greenView.mas_key = @"greenView";
self.view.mas_key = @"self.view";
//    MASAttachKeys(self.greenView, self.view);   // 也可以使用这种写法自动关联一个key

// 添加布局
[self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(20, 20, 20, 20)).key(@"edgesConstraint");
    make.height.lessThanOrEqualTo(@(200)).key(@"heightConstraint");
//        make.height.lessThanOrEqualTo(@(200)).key(@1234);    // key可以是任意值
}];

这次的运行结果:

[LayoutConstraints] Unable to simultaneously satisfy constraints.
    ......
(
    "<MASLayoutConstraint:edgesConstraint[2] UIView:greenView.top == UIView:self.view.top + 20>",
    "<MASLayoutConstraint:edgesConstraint[3] UIView:greenView.bottom == UIView:self.view.bottom - 20>",
    "<MASLayoutConstraint:heightConstraint UIView:greenView.height <= 200>",
    "<NSLayoutConstraint:0x600002f9f6b0 UIView:self.view.height == 736>"
)

Will attempt to recover by breaking constraint 
<MASLayoutConstraint:heightConstraint UIView:greenView.height <= 200>
......

可以很明显看到greenView设置的高度约束冲突了。

9 使用Masonry布局scrollView

对UIScrollView使用自动布局的时候,经常会遇到很多问题,比如布局无效,不能滑动等。主要是因为UIScrollView除了有自己的frame之外,还有一个contentSize。如果对子视图进行布局时参考了scrollView,例如设置子视图的leading/trailing/top/bottom属性等于scrollView的leading/trailing/top/bottom,这实际上是子视图相对于scrollview的contentsize来确定的,而非bounds。而且由于scrollview的contentSize又是根据子视图的位置决定的,这样就形成了一个依赖循环。

为了打破这种循环依赖,为ScrollView中的子视图添加约束的时候需要注意:

  • 子视图不能依赖任何scrollView有关的布局,即不能参考scrollView的位置。
  • 子视图除了要确定自己的大小以外,还需要确定自己与contentSize四周的距离,以此来确定contentSize。

比如我们现在有一个scrollView,将greenView作为子视图添加到scrollView上,代码如下:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self.view addSubview:self.scrollView];
    [self.scrollView addSubview:self.greenView];
    
    // 布局scrollView
    [self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    
    // 布局greenView并确定scrollview的contentSize
    [self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
        // 1 确定contentSize的大小
        make.edges.equalTo(self.scrollView); 
        
        // 2 确定greenView的大小
        make.height.mas_equalTo(500);
        make.width.mas_equalTo(1000);
        // make.size.equalTo(self.scrollView).sizeOffset(CGSizeMake(500, -100));
    }];
}

上面代码中我们使用make.edges.equalTo(self.scrollView);来确定contentSize的大小,它表示greenView的上下左右距离contentSize的值为0,因此只要greenView的大小确定了,contentSize也就确定了。

如果要在scrollView上添加多个子视图,可以将多个子视图放入一个containerView中,这样布局子视图时只需要参考containerView就可以了。

比如现在有一个scrollView,它里面有多个子视图等间隔排列:


我们可以使用下面的代码来实现:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self.view addSubview:self.scrollView];
    [self.scrollView addSubview:self.containerView];

    [self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view).inset(20);
    }];

    [self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.scrollView);
        make.height.mas_equalTo(self.scrollView);
    }];
    
    // 添加子视图
    int count = 10;
    UIView *previousView = nil;

    for (int i = 1; i <= count; ++i) {
        UIView *subView = [UIView new];
        subView.backgroundColor = [UIColor greenColor];
        subView.layer.borderColor = [UIColor blackColor].CGColor;
        subView.layer.borderWidth = 2.0;
        [self.containerView addSubview:subView];
        // 布局子视图
        [subView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.bottom.equalTo(self.containerView);
            make.width.mas_equalTo(80);
            if (previousView) {
                make.left.equalTo(previousView.mas_right).offset(10);
            }
            else {
                make.left.equalTo(self.containerView).offset(10);
            }
        }];
        previousView = subView;
    }
    
    // 确定containerView的右边界
    [self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(previousView);
    }];
}

10 注意事项

  • 对视图使用自动布局之前,需要先将视图添加到父视图上。
  • 使用Masonry时注意区分mas_equalToequalTo的区别:
    • mas_equalTo
      • 是对equalTo的封装,支持NSNumber、CGSize、CGPoint、UIEdgeInsets;
      • mas_equalTo是一个Macro,比较的是值;
      • 一般对于数值元素使用mas_equalTo,例如make.width.mas_equalTo(100);
    • equalTo
      • 只支持基本类型;
      • equalTo比较的是view;
      • 对于对象或多个属性的处理使用equalTo,例如make.left.and.right.equalTo(self.view);
  • 对视图使用自动布局后,如果尝试获取它的frame很可能获取到的是0。对于这种情况,我们可以在获取视图frame之前手动调用layoutIfNeeded方法来更新布局。
  • Masonry中的block不存在循环引用的问题,不必使用weakSelf。虽然block内引用了view,但block并没有被view所持有,因此不会发生循环引用。

11 参考资料

Masonry
Masonry介绍与使用实践(快速上手Autolayout)
Masonry 简单使用
iOS开发之Masonry框架-使用方法须知
ScrollView使用Masonry自动布局
史上最简单的UIScrollView+Autolayout出坑指南
iOS开发笔记 | 由使用Masonry布局不能立即获取到frame想到的一些问题
Masonry布局控件frame为0的问题

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

推荐阅读更多精彩内容