view动态layout

view 动态显示位置

核心思路

将view存储在array中, 每次增加或者删除view的时候, 去遍历数组, 然后重新排序

实现方案

// - 定义的结构体和枚举 :

//
//  QIEContainerBgViewHeader.h
//  QEZB
//
//  Created by 李超群 on 2020/6/15.
//  Copyright © 2020 zhou. All rights reserved.
//

#ifndef QIEContainerBgViewHeader_h
#define QIEContainerBgViewHeader_h

#pragma mark - 定义位置的结构体
typedef struct{
    CGFloat horMargin;
    CGFloat verMargin;
    CGFloat horPadding;
    CGFloat verPadding;
} QIEPosition;

CG_INLINE QIEPosition
QIEPositionMake(CGFloat horMargin, CGFloat verMargin, CGFloat horPadding, CGFloat verPadding){
    QIEPosition pos;
    pos.horMargin = horMargin;
    pos.verMargin = verMargin;
    pos.horPadding = horPadding;
    pos.verPadding = verPadding;
  return pos;
}

#pragma mark - 开始位置的类型
typedef NS_ENUM(NSInteger, QIELayoutType) {
    QIELayoutTypeTopLeft,
    QIELayoutTypeTopRight,
    QIELayoutTypeBottomLeft,
    QIELayoutTypeBottomRight,
};

#pragma mark - 行和列的结构体
typedef NSInteger QIEViewColumn;
typedef NSInteger QIEViewRow;

#pragma mark - 普通直播间的chatview
typedef NS_ENUM(QIEViewRow, QIEViewLandspaceChatViewRow) {
    /** 领取鹅蛋 */
    QIEViewRowEDan = 0,
    /** 宝箱的行 */
    QIEViewRowGiftBox = 1,

    /** 大神推单的行 */
    QIEViewRowGuessExpert = 2,
    
    /** 广告的行 */
    QIEViewRowAdView = 3,
    
    /** 抽奖的行 */
    QIEViewRowLuckyDraw = 4,
        
    /** 行的最大值,  此枚举中定义的值不能超过这个值 */
    QIEViewRowMax = 5
};

typedef NS_ENUM(QIEViewColumn, QIEViewLandspaceChatViewColumn) {
    
    /** 宝箱的列 */
    QIEViewColumnGiftBox = 0,
    
    /** 列的最大值,  此枚举中定义的值不能超过这个值 */
    QIEViewColumnMax,
};

#pragma mark - 女神直播间的BottomView
typedef NS_ENUM(QIEViewRow, QIEViewPortraitLiveRoomBottomViewRow) {
   QIEViewPortraitLiveRoomBottomViewRow0 = 0,
    
    /** 行的最大值,  此枚举中定义的值不能超过这个值 */
    QIEViewPortraitLiveRoomBottomViewRowMax,
};

typedef NS_ENUM(QIEViewColumn, QIEViewPortraitLiveRoomBottomViewColumn) {
    QIEViewPortraitLiveRoomBottomViewColumn0 = 0,
    QIEViewPortraitLiveRoomBottomViewColumn10 = 10,
    QIEViewPortraitLiveRoomBottomViewColumn20 = 20,
    QIEViewPortraitLiveRoomBottomViewColumn30 = 30,
    
    /** 列的最大值,  此枚举中定义的值不能超过这个值 */
    QIEViewPortraitLiveRoomBottomViewColumnMax,
};

#pragma mark - 女神手播的BottomView
typedef NS_ENUM(QIEViewRow, QIEViewPortraitStartLiveBottomViewRow) {
    QIEViewPortraitStartLiveBottomViewRow0 = 0,
    
    /** 行的最大值,  此枚举中定义的值不能超过这个值 */
    QIEViewPortraitStartLiveBottomViewRowMax,
};

typedef NS_ENUM(QIEViewColumn,  QIEViewPortraitStartLiveBottomViewColumn) {
    QIEViewPortraitStartLiveBottomViewColumn0 = 0,
    QIEViewPortraitStartLiveBottomViewColumn10 = 10,
    QIEViewPortraitStartLiveBottomViewColumn20 = 20,
    QIEViewPortraitStartLiveBottomViewColumn30 = 30,
    QIEViewPortraitStartLiveBottomViewColumn40 = 40,
    
    /** 列的最大值,  此枚举中定义的值不能超过这个值 */
    QIEViewPortraitStartLiveBottomViewColumnMax,
};

#endif /* QIEContainerBgViewHeader_h */

// - 声明

//
//  QIEDynamicLayoutBgView.h
//  AFNetworking
//
//  Created by 李超群 on 2020/5/9.
//

#import "QIEContainerBgView.h"
#import "QIEDynamicLayoutBgViewHeader.h"

@interface QIEDynamicLayoutBgView : QIEContainerBgView

/** layoutType : 开始布局的位置;  margin : 距离边框的位置;  padding : view之间的间距; */
-(instancetype)initWithLayoutType:(QIELayoutType)layoutType
                         position:(QIEPosition)position
                           maxRow:(NSInteger)maxRow
                        maxColumn:(NSInteger)maxColumn;

/** 为某一行增加一个 view */
- (void)showSubView:(UIView *)subView row:(QIEViewRow)row column:(QIEViewColumn)column shouldForcPosition:(BOOL)shouldForcPosition;

/** 去掉某一行的某个 view */
- (void)hiddenSubView:(UIView *)subView;

@end

// - 实现 :

//
//  QIEDynamicLayoutBgView.m
//  AFNetworking
//
//  Created by 李超群 on 2020/5/9.
//

#import "QIEDynamicLayoutBgView.h"
#import 

#ifndef QIEKeypath
#define QIEKeypath(OBJ, PATH)  @(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
#endif

@interface UIView (DYPosition)
/** 行 */
@property (nonatomic, assign) QIEViewRow row;

/** 列 */
@property (nonatomic, assign) QIEViewColumn column;
@end

@implementation UIView (DYPosition)
NSString *rowKey = @"rowKey";
NSString *columnKey = @"columnKey";

- (void)setRow:(QIEViewRow)row{
    objc_setAssociatedObject(self, [rowKey UTF8String], [NSNumber numberWithInteger:row], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(QIEViewRow)row{
    return [objc_getAssociatedObject(self, [rowKey UTF8String]) integerValue];
}

- (void)setColumn:(QIEViewColumn)column{
    objc_setAssociatedObject(self, [columnKey UTF8String], [NSNumber numberWithInteger:column], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (QIEViewColumn)column{
    return [objc_getAssociatedObject(self, [columnKey UTF8String]) integerValue];
}

@end


@interface QIEDynamicLayoutViewConfig : NSObject

/** 行 */
@property (nonatomic, assign) QIEViewRow row;

/** 列 */
@property (nonatomic, assign) QIEViewColumn column;

/** 尺寸 */
@property (nonatomic, assign) CGSize viewSize;

/** 是否强制显示在这个位置 */
@property (nonatomic, assign) BOOL shouldForcPosition;

/** 绑定的view */
@property (nonatomic, weak) UIView *view;

@end

@implementation QIEDynamicLayoutViewConfig

- (BOOL)isEqual:(QIEDynamicLayoutViewConfig *)object{
    return self.view == object.view;
}

@end

@interface QIEDynamicLayoutBgView ()
/** 所有的row的数组 */
@property (nonatomic, strong) NSMutableArray *> *rowsArray;

/** 被暂时隐藏的view的config的集合 */
@property (nonatomic, strong) NSMutableArray  *backupConfigArray;

/** 超过规定的枚举值 */
@property (nonatomic, assign) int exceedCount;

@property (nonatomic, assign) QIELayoutType layoutType;
@property (nonatomic, assign) QIEPosition position;
@property (nonatomic, assign) NSInteger maxRow;
@property (nonatomic, assign) NSInteger maxColumn;


@end

@implementation QIEDynamicLayoutBgView
/** layoutType : 开始布局的位置;  margin : 距离边框的位置;  padding : view之间的间距; */
-(instancetype)initWithLayoutType:(QIELayoutType)layoutType
                         position:(QIEPosition)position
                           maxRow:(NSInteger)maxRow
                        maxColumn:(NSInteger)maxColumn{
    if (self = [super init]){
        self.layoutType = layoutType;
        self.position = position;
        self.maxRow = maxRow;
        self.maxColumn = maxColumn;
        self.rowsArray = [NSMutableArray array];
        self.backupConfigArray = [NSMutableArray array];
        for (int i = 0; i < self.maxRow; i++) {
            NSMutableArray  * configsArray = [NSMutableArray array];
            [self.rowsArray addObject:configsArray];
        }
    }
    return self;
}

/** 为某一行增加一个 view */
- (void)showSubView:(UIView *)subView row:(QIEViewRow)row column:(QIEViewColumn)column shouldForcPosition:(BOOL)shouldForcPosition{
    if (!subView) return;

    // - 设置view的属性值
    QIEDynamicLayoutViewConfig *config = [[QIEDynamicLayoutViewConfig alloc]init];
    config.row = row;
    config.column = column;
    config.view = subView;
    config.shouldForcPosition = shouldForcPosition;
    config.viewSize = subView.frame.size;
    [self addSubview:config.view];

// - 测试环境bug检查
#if DEBUG
    if (!shouldForcPosition) {
        if (row >= self.maxRow || column >= self.maxColumn) {
            @throw [NSException exceptionWithName:@"程序被终止" reason:@"传入的view的row和column超过了最大的限制" userInfo:nil];
        }
        NSMutableArray  *debugArray = [self.rowsArray objectAtIndex:row];
        for (QIEDynamicLayoutViewConfig *pConfig in debugArray) {
            if (pConfig.row == row && pConfig.column == column && subView != pConfig.view && !pConfig.shouldForcPosition) {
                @throw [NSException exceptionWithName:@"程序被终止" reason:@"传入的view的row和column已经存在了" userInfo:nil];
                break;
            }
        }
    }
#endif

    // - 添加view
    NSMutableArray  *configArray = [self.rowsArray objectAtIndex:row];

    // - 正式环境如果view的column重复了 就重新赋值, 把新的view向后移动
    NSArray *columeArray = [configArray valueForKeyPath:QIEKeypath(config, column)];

    // - 如果已经包含了相同位置的view,  判断是否强制显示在这个位置, 如果是强制显示, 隐藏之前的view, 否则把新加进来的view, 移动到之前的位置
    for (int idx = 0; idx < columeArray.count; idx++) {
        NSNumber *obj = columeArray[idx];
        if (obj.intValue == column) {
            QIEDynamicLayoutViewConfig *tempConfig = configArray[idx];
            if (!config.shouldForcPosition && !tempConfig.shouldForcPosition) {
                self.exceedCount++;
                config.column = self.exceedCount + self.maxColumn;

            }else{
                QIEDynamicLayoutViewConfig *removedConfig = config.shouldForcPosition ? tempConfig : config;
                removedConfig.view.hidden = YES;
                [configArray removeObject:removedConfig];
                [self.backupConfigArray containsObject:removedConfig] ?: [self.backupConfigArray insertObject:removedConfig atIndex:0];
                
                if (removedConfig == config) {
                    subView.row = config.row;
                    subView.column = config.column;
                    return;
                }
            }
            
            break;
        }
    }

    // - 添加并布局view
    if (![configArray containsObject:config]) {
        [configArray addObject:config];
        subView.row = config.row;
        subView.column = config.column;
        config.view.hidden = NO;
        [self relayoutSubviews];
    }
    return;
}

/** 去掉某一行的某个 view */
- (void)hiddenSubView:(UIView *)subView{
    if (!subView) return;

    // -
    QIEDynamicLayoutViewConfig *config = [[QIEDynamicLayoutViewConfig alloc]init];
    config.view = subView;
    config.view.hidden = YES;
    
    // - 从布局数组中移除view
    for (NSMutableArray  * configArray in self.rowsArray) {
        if ([configArray containsObject:config]) {
            [configArray removeObject:config];
            break;
        }
    }
    
    // - 在备用数组中查询, 如果存在这个的view, 就移除这个view, 如果存在相同位置的view, 就显示这个view;
    if ([self.backupConfigArray containsObject:config]) {
        [self.backupConfigArray removeObject:config];
    }else{
        for (QIEDynamicLayoutViewConfig * obj in self.backupConfigArray) {
            if (obj.row == subView.row && obj.column == subView.column) {
                [self showSubView:obj.view row:obj.row column:obj.column shouldForcPosition:obj.shouldForcPosition];
                [self.backupConfigArray removeObject:obj];
                return;
            }

        };

    }
    [self relayoutSubviews];
}

/** 确定每个view的位置 */
-(void)layoutViewPositionWithlastVerView:(UIView *)lastVerView lastHorView:(UIView *)lastHorView  config:(QIEDynamicLayoutViewConfig *)config row:(NSInteger)row column:(NSInteger)column{
    
    CGFloat viewVerMargin = (row == 0) ? self.position.verMargin : self.position.verPadding;
    CGFloat viewHorMargin = (column == 0) ? self.position
    .horMargin : self.position.horPadding;
    
    if (self.layoutType == QIELayoutTypeTopRight) {
        [config.view mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.size.mas_equalTo(config.viewSize);
            column == 0 ? make.right.equalTo(self).offset(-viewHorMargin) : make.right.equalTo(lastHorView.mas_left).offset(-viewHorMargin);
            row == 0 ? make.top.equalTo(self).offset(viewVerMargin) : make.top.equalTo(lastVerView.mas_bottom).offset(viewVerMargin);
        }];
    }else if(self.layoutType == QIELayoutTypeTopLeft){
        [config.view mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.size.mas_equalTo(config.viewSize);
            column == 0 ? make.left.equalTo(self).offset(viewHorMargin) : make.left.equalTo(lastHorView.mas_right).offset(viewHorMargin);
            row == 0 ? make.top.equalTo(self).offset(viewVerMargin) : make.top.equalTo(lastVerView.mas_bottom).offset(viewVerMargin);
        }];
    }else if(self.layoutType == QIELayoutTypeBottomRight){
        [config.view mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.size.mas_equalTo(config.viewSize);
            column == 0 ? make.right.equalTo(self).offset(-viewHorMargin) : make.right.equalTo(lastHorView.mas_left).offset(-viewHorMargin);
            row == 0 ? make.bottom.equalTo(self).offset(-viewVerMargin) : make.bottom.equalTo(lastVerView.mas_top).offset(-viewVerMargin);
        }];
    }else if(self.layoutType == QIELayoutTypeBottomLeft){
        [config.view mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.size.mas_equalTo(config.viewSize);
            column == 0 ? make.left.equalTo(self).offset(viewHorMargin) : make.left.equalTo(lastHorView.mas_right).offset(viewHorMargin);
            row == 0 ? make.bottom.equalTo(self).offset(-viewVerMargin) : make.bottom.equalTo(lastVerView.mas_top).offset(-viewVerMargin);
        }];
    }
}

/** 重新排序布局 */
-(void)relayoutSubviews{

    // - 移除掉没有数据的行
    NSMutableArray *> *tempArray = [NSMutableArray array];
    [self.rowsArray enumerateObjectsUsingBlock:^(NSMutableArray * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj.count) {
            [tempArray addObject:obj];
        }
    }];

    /** 先排序 然后循环设置布局 */
    __block UIView *lastVerView = nil;
    [tempArray enumerateObjectsUsingBlock:^(NSMutableArray  *rowArray, NSUInteger row, BOOL * _Nonnull stop) {

        /** 遍历每一行的时候, 排序每个view */
        [rowArray sortUsingComparator:^NSComparisonResult(QIEDynamicLayoutViewConfig *config1, QIEDynamicLayoutViewConfig *config2) {
            return (config1.column < config2.column) ? NSOrderedAscending : NSOrderedDescending;
        }];

        /** 布局 */
        __block UIView *lastHorView = nil;
        [rowArray enumerateObjectsUsingBlock:^(QIEDynamicLayoutViewConfig *config, NSUInteger column, BOOL * _Nonnull stop) {

            // - 确定每个view的位置
            [self layoutViewPositionWithlastVerView:lastVerView lastHorView:lastHorView config:config row:row column:column];
            lastHorView = config.view;
            if (column == rowArray.count - 1) lastVerView = config.view;
        }];
    }];
}

/** 移除时候先把这个view hidden */
-(void)willRemoveSubview:(UIView *)subview{
    if (self.superview) {
        [self hiddenSubView:subview];
    }
}

/** 添加的按钮默认是hidden的 */
- (void)addSubview:(UIView *)view{
    [super addSubview:view];
    view.hidden  = YES;
}

@end

你可能感兴趣的:(项目中使用的技巧)