IOS开发系列——Masonry手写Autolayout专题【整理,部分原创】

Masonry手写Autolayout专题

 

Masonry常规开发指导整理自此篇文档(可别说我转载不注明出处哦,^_^),后面加入了笔者在实际开发使用过程中遇到过的一些问题,希望对你有所帮助。

Masonry介绍与使用实践:快速上手Autolayout

http://www.cocoachina.com/ios/20141219/10702.html

 

1    Masonry开发技巧

1.1    简单使用技巧

1.1.1 [基础] 居中显示一个view

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

- (void)viewDidLoad

{

    [super viewDidLoad];

    // Do any additional setup after loading the view.

     

    WS(ws);

     

    UIView *sv = [UIView new];

    [sv showPlaceHolder];

    sv.backgroundColor = [UIColor blackColor];

    [self.view addSubview:sv];

    [sv mas_makeConstraints:^(MASConstraintMaker *make) {

        make.center.equalTo(ws.view);

        make.size.mas_equalTo(CGSizeMake(300, 300));

    }];

     

}

代码效果


使用我之间写的MMPlaceHolder可以看到superview已经按照我们预期居中并且设置成了适当的大小

那么先看看这几行代码

1

2

3

4

5

6

7

8

9

10

11

12

//从此以后基本可以抛弃CGRectMake

UIView *sv = [UIView new];

//在做autoLayout之前 一定要先将view添加到superview 否则会报错

[self.view addSubview:sv];

//mas_makeConstraints就是Masonryautolayout添加函数 将所需的约束添加到block中行了

[sv mas_makeConstraints:^(MASConstraintMaker *make) {

//sv居中(很容易理解吧?)

    make.center.equalTo(ws.view);

     

    //size设置成(300,300)

    make.size.mas_equalTo(CGSizeMake(300, 300));

}];

这里有两个问题要分解一下

首先在Masonry中能够添加autolayout约束有三个函数

1

2

3

4

5

6

7

8

9

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;

- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;

- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;

/*

mas_makeConstraints 只负责新增约束 Autolayout不能同时存在两条针对于同一对象的约束 否则会报错 

mas_updateConstraints 针对上面的情况 会更新在block中出现的约束 不会导致出现两个相同约束的情况

mas_remakeConstraints 则会清除之前的所有约束 仅保留最新的约束

三种函数善加利用 就可以应对各种情况了

*/

其次 equalTo mas_equalTo的区别在哪里呢?其实 mas_equalTo是一个MACRO

1

2

3

4

#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))

#define mas_greaterThanOrEqualTo(...)    greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))

#define mas_lessThanOrEqualTo(...)       lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))

#define mas_offset(...)                  valueOffset(MASBoxValue((__VA_ARGS__)))

可以看到 mas_equalTo只是对其参数进行了一个BOX操作(装箱) MASBoxValue的定义具体可以看看源代码太长就不贴出来了

所支持的类型除了NSNumber支持的那些数值类型之外就只支持CGPoint CGSize UIEdgeInsets

介绍完这几个问题我们就继续往下了 PS:刚才定义的sv会成为我们接下来所有samplesuperView

1.1.2  [初级] 让一个view略小于其superView(边距为10)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

UIView *sv1 = [UIView new];

[sv1 showPlaceHolder];

sv1.backgroundColor = [UIColor redColor];

[sv addSubview:sv1];

[sv1 mas_makeConstraints:^(MASConstraintMaker *make) {

    make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));

     

    /* 等价于

    make.top.equalTo(sv).with.offset(10);

    make.left.equalTo(sv).with.offset(10);

    make.bottom.equalTo(sv).with.offset(-10);

    make.right.equalTo(sv).with.offset(-10);

    */

     

    /* 也等价于

    make.top.left.bottom.and.right.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));

    */

}];

代码效果


可以看到 edges其实就是top,left,bottom,right的一个简化,分开写也可以一句话更省事

那么为什么bottomright里的offset是负数呢?因为这里计算的是绝对的数值计算的bottom需要小鱼sv的底部高度所以要-10同理用于right

这里有意思的地方是andwith其实这两个函数什么事情都没做

1

2

3

4

5

6

- (MASConstraint *)with {

    return self;

}

- (MASConstraint *)and {

    return self;

}

但是用在这种链式语法中就非常的巧妙和易懂不得不佩服作者的心思(虽然我现在基本都会省略)

1.1.3  [初级] 让两个高度为150的view垂直居中且等宽且等间隔排列 间隔为10(自动计算其宽度)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

int padding1 = 10;

[sv2 mas_makeConstraints:^(MASConstraintMaker *make) {

    make.centerY.mas_equalTo(sv.mas_centerY);

    make.left.equalTo(sv.mas_left).with.offset(padding1);

    make.right.equalTo(sv3.mas_left).with.offset(-padding1);

    make.height.mas_equalTo(@150);

    make.width.equalTo(sv3);

}];

[sv3 mas_makeConstraints:^(MASConstraintMaker *make) {

    make.centerY.mas_equalTo(sv.mas_centerY);

    make.left.equalTo(sv2.mas_right).with.offset(padding1);

    make.right.equalTo(sv.mas_right).with.offset(-padding1);

    make.height.mas_equalTo(@150);

    make.width.equalTo(sv2);

}];

代码效果


这里我们在两个子view之间互相设置的约束可以看到他们的宽度在约束下自动的被计算出来了

1.1.4  [中级] 在UIScrollView顺序排列一些view并自动计算contentSize

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

UIScrollView *scrollView = [UIScrollView new];

scrollView.backgroundColor = [UIColor whiteColor];

[sv addSubview:scrollView];

[scrollView mas_makeConstraints:^(MASConstraintMaker *make) {

    make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(5,5,5,5));

}];

UIView *container = [UIView new];

[scrollView addSubview:container];

[container mas_makeConstraints:^(MASConstraintMaker *make) {

    make.edges.equalTo(scrollView);

    make.width.equalTo(scrollView);

}];

int count = 10;

UIView *lastView = nil;

for ( int i = 1 ; i <= count ; ++i )

{

    UIView *subv = [UIView new];

    [container addSubview:subv];

    subv.backgroundColor = [UIColor colorWithHue:( arc4random() % 256 / 256.0 )

                                      saturation:( arc4random() % 128 / 256.0 ) + 0.5

                                      brightness:( arc4random() % 128 / 256.0 ) + 0.5

                                           alpha:1];

     

    [subv mas_makeConstraints:^(MASConstraintMaker *make) {

        make.left.and.right.equalTo(container);

        make.height.mas_equalTo(@(20*i));

         

        if ( lastView )

        {

            make.top.mas_equalTo(lastView.mas_bottom);

        }

        else

        {

            make.top.mas_equalTo(container.mas_top);

        }

    }];

     

    lastView = subv;

}

[container mas_makeConstraints:^(MASConstraintMaker *make) {

    make.bottom.equalTo(lastView.mas_bottom);

}];

头部效果

 

尾部效果


scrollViewscrollIndicator可以看出 scrollView的内部已如我们所想排列好了。这里的关键就在于container这个view起到了一个中间层的作用能够自动的计算uiscrollViewcontentSize。

1.1.5  [高级] 横向或者纵向等间隙的排列一组view

很遗憾 autoLayout并没有直接提供等间隙排列的方法(Masonry的官方demo中也没有对应的案例)但是参考案例3我们可以通过一个小技巧来实现这个目的为此我写了一个Category

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

@implementation UIView(Masonry_LJC)

- (void) distributeSpacingHorizontallyWith:(NSArray*)views

{

    NSMutableArray *spaces = [NSMutableArray arrayWithCapacity:views.count+1];

     

    for ( int i = 0 ; i < views.count+1 ; ++i )

    {

        UIView *v = [UIView new];

        [spaces addObject:v];

        [self addSubview:v];

         

        [v mas_makeConstraints:^(MASConstraintMaker *make) {

            make.width.equalTo(v.mas_height);

        }];

    }    

     

    UIView *v0 = spaces[0];

     

    __weak __typeof(&*self)ws = self;

     

    [v0 mas_makeConstraints:^(MASConstraintMaker *make) {

        make.left.equalTo(ws.mas_left);

        make.centerY.equalTo(((UIView*)views[0]).mas_centerY);

    }];

     

    UIView *lastSpace = v0;

    for ( int i = 0 ; i < views.count; ++i )

    {

        UIView *obj = views[i];

        UIView *space = spaces[i+1];

         

        [obj mas_makeConstraints:^(MASConstraintMaker *make) {

            make.left.equalTo(lastSpace.mas_right);

        }];

         

        [space mas_makeConstraints:^(MASConstraintMaker *make) {

            make.left.equalTo(obj.mas_right);

            make.centerY.equalTo(obj.mas_centerY);

            make.width.equalTo(v0);

        }];

         

        lastSpace = space;

    }

     

    [lastSpace mas_makeConstraints:^(MASConstraintMaker *make) {

        make.right.equalTo(ws.mas_right);

    }];

     

}

- (void) distributeSpacingVerticallyWith:(NSArray*)views

{

    NSMutableArray *spaces = [NSMutableArray arrayWithCapacity:views.count+1];

     

    for ( int i = 0 ; i < views.count+1 ; ++i )

    {

        UIView *v = [UIView new];

        [spaces addObject:v];

        [self addSubview:v];

         

        [v mas_makeConstraints:^(MASConstraintMaker *make) {

            make.width.equalTo(v.mas_height);

        }];

    }

     

     

    UIView *v0 = spaces[0];

     

    __weak __typeof(&*self)ws = self;

     

    [v0 mas_makeConstraints:^(MASConstraintMaker *make) {

        make.top.equalTo(ws.mas_top);

        make.centerX.equalTo(((UIView*)views[0]).mas_centerX);

    }];

     

    UIView *lastSpace = v0;

    for ( int i = 0 ; i < views.count; ++i )

    {

        UIView *obj = views[i];

        UIView *space = spaces[i+1];

         

        [obj mas_makeConstraints:^(MASConstraintMaker *make) {

            make.top.equalTo(lastSpace.mas_bottom);

        }];

         

        [space mas_makeConstraints:^(MASConstraintMaker *make) {

            make.top.equalTo(obj.mas_bottom);

            make.centerX.equalTo(obj.mas_centerX);

            make.height.equalTo(v0);

        }];

         

        lastSpace = space;

    }

     

    [lastSpace mas_makeConstraints:^(MASConstraintMaker *make) {

        make.bottom.equalTo(ws.mas_bottom);

    }];

}

@end

简单的来测试一下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

UIView *sv11 = [UIView new];

UIView *sv12 = [UIView new];

UIView *sv13 = [UIView new];

UIView *sv21 = [UIView new];

UIView *sv31 = [UIView new];

sv11.backgroundColor = [UIColor redColor];

sv12.backgroundColor = [UIColor redColor];

sv13.backgroundColor = [UIColor redColor];

sv21.backgroundColor = [UIColor redColor];

sv31.backgroundColor = [UIColor redColor];

[sv addSubview:sv11];

[sv addSubview:sv12];

[sv addSubview:sv13];

[sv addSubview:sv21];

[sv addSubview:sv31];

//给予不同的大小 测试效果

[sv11 mas_makeConstraints:^(MASConstraintMaker *make) {

    make.centerY.equalTo(@[sv12,sv13]);

    make.centerX.equalTo(@[sv21,sv31]);

    make.size.mas_equalTo(CGSizeMake(40, 40));

}];

[sv12 mas_makeConstraints:^(MASConstraintMaker *make) {

    make.size.mas_equalTo(CGSizeMake(70, 20));

}];

[sv13 mas_makeConstraints:^(MASConstraintMaker *make) {

    make.size.mas_equalTo(CGSizeMake(50, 50));

}];

[sv21 mas_makeConstraints:^(MASConstraintMaker *make) {

    make.size.mas_equalTo(CGSizeMake(50, 20));

}];

[sv31 mas_makeConstraints:^(MASConstraintMaker *make) {

    make.size.mas_equalTo(CGSizeMake(40, 60));

}];

[sv distributeSpacingHorizontallyWith:@[sv11,sv12,sv13]];

[sv distributeSpacingVerticallyWith:@[sv11,sv21,sv31]];

[sv showPlaceHolderWithAllSubviews];

[sv hidePlaceHolder];

代码效果


perfect!简洁明了的达到了我们所要的效果

 

1.2    高级开发技巧

1.2.1 更新layout

1.2.1.1 mas_updateConstraints

使用Masonry创建constraint来定义布局的方式有三种:mas_makeConstraints, mas_updateConstraintsmas_remakeConstraints

       有时你需要更新constraint(例如,动画和调试)而不是创建固定constraint,可以使用mas_updateConstraints方法。

1.2.1.2 mas_remakeConstraints

    mas_remakeConstraintsmas_updateConstraints比较相似,都是更新constraint。不过,mas_remakeConstraints是删除之前constraint,然后再添加新的constraint(适用于移动动画);而mas_updateConstraints只是更新constraint的值。

1.2.1.3 代码示例 

    [_loginButton mas_remakeConstraints:^(MASConstraintMaker *make) {

        make.width.equalTo(weakSelf.loginNameTextField.mas_width);

        make.height.equalTo(weakSelf.loginNameTextField.mas_height);

        make.centerX.mas_equalTo(weakSelf.loginContainerView.mas_centerX);

       

        if (_loginVerifyImageCodeTextField) {

            make.top.equalTo(weakSelf.loginVerifyImageCodeTextField.mas_bottom).with.offset(20);

        }

    }];

   

    [_loginDescLabel mas_updateConstraints:^(MASConstraintMaker *make) {

        make.top.equalTo(weakSelf.loginButton.mas_bottom).with.offset(5);

    }];

 

1.3    常见问题

1.3.1 在TableCell单元格中无法使用Masonry

在TableCell中增加子控件时,无法正常使用Masonry来进行布局,可能是Cell重用引起的。

 

1.3.2 使用了Masonry布局的控件最好手动释放内存,若有APP自动释放内存,会有延迟,导致页面布局失败


//需要手动释放mas_makeConstraints的内存,若有APP自动释放内存,会有延迟,导致页面布局失败

    _loginNameTextField = nil;

    _loginVerifyCodeGetButton =nil;

    _loginVerifyCodeTextField = nil;

 

1.3.3 尽量直接用equalTo,而不用mas_equalTo

mas_equalTo需要等控件自身布局完成了之后才能调用,而equalTo不需要。因此进行子视图布局时,用前者容易挂机。

 

1.3.4 使用dispatch_get_main_queue保证布局与后续处理的同步

虽然mas_updateConstraints的block回调是顺序执行的,但是布局真正起作用还是放在了main_queue的下一个runloop中执行的,所以如果要在布局完成后顺序执行某些处理(例如开始进行自定义绘制或者异步绘制),最好手动放到main_queue的下一个runloop中执行,确保布局完成后才开始执行代码。

如下是示例代码:

-(void)draw

{

    [selfsetLayout];

    //因为Layout库是异步的,为了保证执行顺序上的同步,需要作此处理

    dispatch_async(dispatch_get_main_queue(), ^{

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

            CGRectdrawFrame = CGRectMake(0, 0, SCREEN_BOUNDS.size.width,_headerHeight);

           

            UIGraphicsBeginImageContextWithOptions(drawFrame.size,YES,0);

            CGContextRefcontext =UIGraphicsGetCurrentContext();

           

            if(!context) {

                return;

            }

           

            [[UIColor whiteColor] set];

           

            CGContextFillRect(context,drawFrame);

           

            [_separationLineViewdrawRect:CGRectMake(0,0,drawFrame.size.width,_lineHeight)];

            [_iconButton drawRect:_iconButton.frame];

            [_nameButton drawRect:_nameButton.frame];

            [_postDateLabel drawRect:_postDateLabel.frame];

           

            UIImage*tempImage =UIGraphicsGetImageFromCurrentImageContext();

            UIGraphicsEndImageContext();

           

            dispatch_async(dispatch_get_main_queue(),^{

                //                _postContentTextImageView.frame= _postContentTextView.frame;//textViewRect;//

               

                [_iconButton setHidden:NO];

                [_nameButton setHidden:NO];

                [_postDateLabelsetHidden:NO];

               

                _bgImageView.image =nil;

                _bgImageView.image =tempImage;

            });

        });

    });

}

 

2    参考链接

实时显示iOS编写UI代码效果

http://blog.csdn.net/zhang_red/article/details/45503683

 

Masonry介绍与使用实践:快速上手Autolayout

http://www.cocoachina.com/ios/20141219/10702.html


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章