IOS开发之自动布局框架设计(二)

  【上集剧情概要:上集我们主要剖析了原生的NSLayoutConstraint实现自动布局的方式,我们知道是通过constraintWithItem这个初始化的方法来配备所需要的7个参数,然后通过addConstraint方法将布局添加进去,并且定义了

NSLayoutAttribute,NSLayoutRelation这些枚举】

如果我们自己设计一款布局框架可以怎么设计呢?

1.封装原有的NSLayoutConstraint类,可以将代码的实现更加简洁化。例如:Masonry,SDAutoLayout。

2.封装坐标布局,并且实现自有的布局方式,至于用什么方式展现给用户,可以自行天马行空,方便易用就行。

3.使用CSS/XML等方式,将布局文件放到一个统一的文件里面,然后初始化的时候进行加载布局。

4.开发一款软件,来实现拖拽好布局之后,自动生成代码,并且有面板可以设置微调当前的参数。

5.还可以将UI动画封装到布局里面,以及控件的交互的封装。

好吧,我们来看看如何封装好NSLayoutConstraint,其实它的核心代码是:

 1 NSLayoutConstraint *constaintTop = [NSLayoutConstraint
 2                                         constraintWithItem:self.tableView
 3                                         attribute:NSLayoutAttributeTop
 4                                         relatedBy:NSLayoutRelationEqual
 5                                         toItem:self.view
 6                                         attribute:NSLayoutAttributeTop
 7                                         multiplier:1.0
 8                                         constant:20];
 9     
10     [_tableView addConstraint:constaintTop];

那我们其实就是要封装这一层代码,并且实现更好地接口展现给用户。

1.实现链式调用

首先我们写一个最简单地实现UI布局的代码,功能:实现上下左右四个属性,使用链式调用。

如果我们要实现链式调用,怎么实现呢?

IOS中调用方法的实现使用的是【】,只有属性访问的时候才用点得方式,那么可以通过Setter方法吗?

比如返回值我们设置为当前的view

@property(nonatomic, readonly) UIView *leftSpace;

-(UIView *)leftSpace
{
    return self;
}

那后面的参数怎么传给它呢,所以它不能满足要求。

橘子君又想到一个问题,使用Block呢,因为block作为属性的时候是可以用这种方式的:

blockLeft(10);

也是可以通过.语法来访问的比如:

self.blockLeft(10);

那我们怎么来实现连续的链式点语法的操作呢

如果self.blockLeft(10);返回的是self的话,那就可以实现了:

self.blockLeft(10).blockTop(10).blockRight(10).blockBottom(10);

ok, 这样的话我们可以模拟实现一下这种链式调用的方式:

#import <UIKit/UIKit.h>

@interface UIView (GCFAdditions)

@property(nonatomic, readonly) UIView *(^left)(NSInteger space);
@property(nonatomic, readonly) UIView *(^right)(NSInteger space);
@property(nonatomic, readonly) UIView *(^top)(NSInteger space);
@property(nonatomic, readonly) UIView *(^bottom)(NSInteger space);

@end

首先简单模拟写了left,right,top,bottom四个block属性,返回值都为self

#import "UIView+GCFAdditions.h"

@implementation UIView (GCFAdditions)

- (UIView *(^)(NSInteger space))left
{
    return ^(NSInteger space) {
        //code
        if (space) {
            NSLog(@"%ld",(long)space);
        } else {
            NSLog(@"%ld",(long)space);
        }
        return self;
    };
}

- (UIView *(^)(NSInteger space))right
{
    return ^(NSInteger space) {
        //code
        if (space) {
            NSLog(@"%ld",(long)space);
        } else {
            NSLog(@"%ld",(long)space);
        }
        
        return self;
    };
}

- (UIView *(^)(NSInteger space))top
{
    return ^(NSInteger space) {
        //code
        if (space) {
            NSLog(@"%ld",(long)space);
        } else {
            NSLog(@"%ld",(long)space);
        }
        
        return self;
    };
}

- (UIView *(^)(NSInteger space))bottom
{
    return ^(NSInteger space) {
        //code
        if (space) {
            NSLog(@"%ld",(long)space);
        } else {
            NSLog(@"%ld",(long)space);
        }
        
        return self;
    };
}

@end

好,我们来仔细分析下上面的代码:

首先在属性里面声明一个block,返回类型为UIView,保证下一步的调用,给了一个参数,测试用的。

然后在实现里面,返回self,这时候就可以完成链式调用

#define SCREEN_WIDTH   [UIScreen mainScreen].bounds.size.width
#define SCREEN_HEIGHT  [UIScreen mainScreen].bounds.size.height

#import "ViewController.h"
#import "Masonry.h"
#import "UIView+GCFAdditions.h"

@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>


@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    self.tableView.left(50).right(50).top(50).bottom(50);
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

-(UITableView *)tableView
{
    if (!_tableView) {
        _tableView=[[UITableView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];
        _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        _tableView.backgroundColor=[UIColor redColor];
        [self.view addSubview:_tableView];
    }
    return _tableView;
}

打印结果:

2016-09-08 11:22:31.214 TestMansonry[298:16133] 50
2016-09-08 11:22:31.215 TestMansonry[298:16133] 50
2016-09-08 11:22:31.216 TestMansonry[298:16133] 50
2016-09-08 11:22:31.216 TestMansonry[298:16133] 50

2.实现最简单的布局

上面我们实现了链式调用,那么我们如何运用它来实现布局呢,请看如下代码:

 NSLayoutConstraint *constaintRight = [NSLayoutConstraint
                                             constraintWithItem:self
                                             attribute:NSLayoutAttributeRight
                                             relatedBy:NSLayoutRelationEqual
                                             toItem:self.superview
                                             attribute:NSLayoutAttributeRight
                                             multiplier:1.0
                                             constant:-space];
        
[self.superview addConstraint:constaintRight];

我们知道实现布局的核心代码是这一段,我们直接移到之前的分类里面就会实现这种效果

- (UIView *(^)(NSInteger space))left
{
    return ^(NSInteger space) {
        if (space) {
            NSLog(@"%ld",(long)space);
        } else {
            NSLog(@"%ld",(long)space);
        }
        
        NSLayoutConstraint *constaintLeft = [NSLayoutConstraint
                                            constraintWithItem:self
                                            attribute:NSLayoutAttributeLeft
                                            relatedBy:NSLayoutRelationEqual
                                            toItem:self.superview
                                            attribute:NSLayoutAttributeLeft
                                            multiplier:1.0
                                            constant:space];
        
        [self.superview addConstraint:constaintLeft];
        
        return self;
    };
}

在实现里面加入

self.tableView.left(50).right(50).top(50).bottom(50);

就可以实现上下左右各50的间距了,哈哈!

原文地址:https://www.cnblogs.com/guchengfengyun/p/5852559.html