MasonryFloatLayout : 基于Masonry的浮动布局

786 阅读4分钟


前言


在iOS中使用Masonry进行布局算是日常操作,但是类似于网页的浮动布局的时候,具体示意图如下.

移除其中的某个元素,剩下的元素就会往某个方向进行移动,在Web端,这种布局方式就叫做浮动布局.

另外还有下面的这种情况,虽然也进行浮动但是仍然还另外一边保持着约束关系,这一种在iOS也是比较常见的约束情况.

在iOS的Masonry使用实现上述过程其实非常麻烦的,如果是我们基于Masonry,基于这种情况,我们一般会有两种写法,一种是状态穷举法,另外一种是临时视图变量记录法.

状态穷举法例:

状态穷举法就是所有的视图组合情况列举出来然后添加对应的约束布局.具体示例可以看下面代码.

- (void)loseConstraintsAction {
    if (!_A.hidden && !_B.hidden && !_C.hidden) {
        [_A mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.view).offset(20.0f);
        }];
        [_B mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.A.mas_right).offset(8.0f);
        }];
        [_C mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.B.mas_right).offset(8.0f);
        }];
    }
    
    if (!_A.hidden && _B.hidden && !_C.hidden) {
        [_A mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.view).offset(20.0f);
        }];
        [_C mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.A.mas_right).offset(8.0f);
        }];
    }
    if (_A.hidden && _B.hidden && !_C.hidden) {
        [_C mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.view).offset(8.0f);
        }];
    }
}

状态穷举法算是非常暴力的方案了.状态穷举法的写法形式多种多样,但是整体思想是一致的, 穷举法虽然理解起来简单, 但是弊端也是显而易见的,代码量却是不容小觑,每增加一种组合,我们就需要多一种约束方案.....


临时视图变量记录法:

临时变量记录法就是使用一个临时变量记录与哪个视图建立约束,具体的代码示例如下所示.

- (void)loseConstraintsAction {
    UIView *lastView = self.view;
    if (!_A.hidden) {
        [_A mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo([lastView isEqual:self.view] ? lastView : lastView.mas_right).offset(20.0f);
        }];
        lastView = _A;
    }
    if (!_B.hidden) {
        [_B mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo([lastView isEqual:self.view] ? lastView : lastView.mas_right).offset(8.0f);
        }];
        lastView = _B;
    }
    if (!_C.hidden) {
        [_C mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo([lastView isEqual:self.view] ? lastView : lastView.mas_right).offset(8.0f);
        }];
    }
}

相比于穷举法,在代码结构上已经有很大的改观,但是整体代码仍然让人感觉不是那么满意.

由此骚栋就在想是否可以基于Masonry封装一个库,来实现这种浮动效果呢? 所以我就封装了一个基于Masonry的浮动布局 MasonryFloatLayout.

在说实现之前,我们先看一下如何使用封装好的 MasonryFloatLayout.


MasonryFloatLayout的使用


  • 首先先导入Demo中 MasonryFloatLayout 文件夹,如下图所示.

  • 这里需要改动的就是 UIViewFloatLayoutHeader 的Masonry的文件路径地址,如果报错,请导入正确的文件路径地址.
#ifndef UIViewFloatLayoutHeader_h
#define UIViewFloatLayoutHeader_h

#import "Masonry/Masonry.h"

#endif /* UIViewFloatLayoutHeader_h */
  • 在需要的ViewController或者View中引入头文件.
#import "NSArray+FloatLayout.h"
  • 假设我们现在有三个View,需要进行浮动布局,那么我们先要把初始化工作完成.包括声明View属性和懒加载View方法实现,当然了,你可以按照你的习惯来.
@property (nonatomic, strong) UIView *firstView;
@property (nonatomic, strong) UIView *secondView;
@property (nonatomic, strong) UILabel *thirdView;
- (UIView *)firstView {
    if (_firstView == nil) {
        _firstView = [[UIView alloc] initWithFrame:CGRectZero];
        _firstView.backgroundColor = [UIColor redColor];
    }
    return _firstView;
}

- (UIView *)secondView {
    if (_secondView == nil) {
        _secondView = [[UIView alloc] initWithFrame:CGRectZero];
        _secondView.backgroundColor = [UIColor orangeColor];
    }
    return _secondView;
}

- (UILabel *)thirdView {
    if (_thirdView == nil) {
        _thirdView = [[UILabel alloc] initWithFrame:CGRectZero];
        _thirdView.backgroundColor = [UIColor blueColor];
        _thirdView.textColor = [UIColor whiteColor];
        _thirdView.text = @"333333333333";
    }
    return _thirdView;
}
  • 接下来我们需要浮动约束布局的添加了.和Masonry布局一样,首先我们需要使用mas_remakeFloatLayoutConstraints给每一个View添加具体的布局.最后再用数组添加浮动布局.具体代码如下所示.
    [self.firstView mas_remakeFloatLayoutConstraints:^(MASConstraintMaker * _Nonnull make, UIView * _Nonnull lastView, UIView * _Nonnull nextView) {
        make.left.equalTo(@50);
        make.height.equalTo(@100);
        make.width.equalTo(@100);
        make.lastFloatConstraint.offset(10.0f);
        make.nextFloatConstraint.offset(-10.0f).priorityHigh();
    }];
    [self.secondView mas_remakeFloatLayoutConstraints:^(MASConstraintMaker * _Nonnull make, UIView * _Nonnull lastView, UIView * _Nonnull nextView) {
        make.left.equalTo(@50);
        make.height.equalTo(@100);
        make.width.equalTo(@100);
        make.lastFloatConstraint.offset(30.0f).priorityLow();
        make.nextFloatConstraint.offset(-10.0f);
    }];
    [self.thirdView mas_remakeFloatLayoutConstraints:^(MASConstraintMaker * _Nonnull make, UIView * _Nonnull lastView, UIView * _Nonnull nextView) {
        make.left.equalTo(@50);
        make.height.equalTo(@100);
        make.lastFloatConstraint.offset(10.0f);
        make.nextFloatConstraint.offset(-10.0f);
    }];

    [@[self.thirdView, self.firstView, self.secondView] mas_remakeFloatLayoutConstraintsWithOrientation:FloatLayoutOrientationBottomToTop needLastConstraint:need];
  • 在上面添加约束过程中,我们需要使用 lastFloatConstraintnextFloatConstraint,这两个属性都是来源于 MASConstraintMaker+FloatLayout,我们可以通过这两个属性添加与上一个View和下一个View的间距关系.
@property (nonatomic, strong) MASFloatLayoutConstraint *lastFloatConstraint;

@property (nonatomic, strong) MASFloatLayoutConstraint *nextFloatConstraint;
  • 当然了,间距关系也可以设置优先级,这样的话.如果有间距约束冲突,通过设置优先级来解决这种问题.这在上面的示例中也是有所体现.
- (MASFloatLayoutConstraint * (^)(MASLayoutPriority priority))priority;

- (MASFloatLayoutConstraint * (^)(void))priorityLow;

- (MASFloatLayoutConstraint * (^)(void))priorityMedium;

- (MASFloatLayoutConstraint * (^)(void))priorityHigh;
  • mas_remakeFloatLayoutConstraints 方法的Block参数除了常见的MASConstraintMaker *make 之外,还会返回上一个视图lastView和下一个视图nextView,当然了,这两个参数有可能是nil.
typedef void(^FloatConstraintMaker)(MASConstraintMaker *make, UIView *lastView, UIView *nextView);
  • 每一个视图的约束添加完成之后,我们添加浮动布局,浮动布局是基于NSArray的分类 NSArray+FloatLayout, mas_remakeFloatLayoutConstraintsWithOrientation 方法总共有两个参数.一个是用来设定浮动的方向,另外一个是用来设定最后一个视图与父视图的关系,类似于上一个模块说到的第二种情况.
typedef enum : NSUInteger {
    FloatLayoutOrientationUnknow,      // 未知或者根据自定义的约束进行约束布局
    FloatLayoutOrientationLeftToRight, // 从左到右进行布局
    FloatLayoutOrientationRightToLeft, // 从右到左进行布局
    FloatLayoutOrientationTopToBottom, // 从上到下进行布局
    FloatLayoutOrientationBottomToTop, // 从下到上进行布局
} FloatLayoutOrientation;

/// 执行浮动布局
/// @param orientation 相对于父视图的浮动方向
/// @param needLastConstraint 是否需要添加最后的约束
- (void)mas_remakeFloatLayoutConstraintsWithOrientation:(FloatLayoutOrientation)orientation
                                     needLastConstraint:(BOOL)needLastConstraint;

整体的使用流程就是这样的.整体来说和原来的Masonry布局代码方式没有什么太大区别.具体的示例可以看一下我的Demo.


总结


如果在使用过程中遇到任何问题欢迎随时找我,骚栋在这里谢谢大家了.

MasonryFloatLayout传送门