iOS 制作一个多联动支持上下合并的表格

概述

项目中要实现一个楼盘表,要求实现以下功能:
1、支持无数据的地方进行补位;
2、支持行列任意拓展;
3、支持支持多向联动;
4、支持上下表格合并。
后面经过研究,考虑通过UICollectionView的灵活嵌套的方式来进行实现。先看下最后实现效果:


表格.jpeg

联动.gif

功能实现

一个表格制作完总结起来主要就是三个要点:

  • 视图布局
  • 联动实现
  • 数据构造

1、视图布局

由于需求要求要实现行列任意拓展,首先就想到UICollectionView的复用特性,有了复用再多的数据页面滑起来也会流程一些,还有就是需求要求上下表格支持合并,然后就可以利用UICollectionView设置item高度的方式来进行实现。但是一般手机屏幕一般不会大到把横向或者竖向的表格都能展示出来,所以就得要求楼盘表既能横向滑动又能竖向滑动。为了实现这个要求单个UICollectionView肯定也是实现不了要求了,单个UICollectionView只能基本实现横向或者竖向的滑动并复用了,所以这时候想到的是UICollectionView双层嵌套,UICollectionView中的UICollectionViewCell套UICollectionView,大的一个负责横向滑动,嵌套的负责竖向滑动。基本布局就是这样子了,可以看到整个页面基本上都是用UICollectionView来进行实现的。


布局.png

如果想控制表格合并与否,合并哪些,这些就是根据数据源传值过来的数据来进行基本控制了,我们定义的规则是当highNominalLayer大于lowerNominalLayer时就进行表格合并,和并数量就是highNominalLayer与lowerNominalLayer差值加一,当然进行表格合并表格时还要注意把中间的间距加上,不然会出现对不齐的情况了。

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.itemArr.count > indexPath.row) {
        //处理表格合并
        ZHItemModel *infoModel = self.itemArr[indexPath.row];
        NSInteger difference = infoModel.highNominalLayer - infoModel.lowerNominalLayer + 1;
        CGFloat height = KITEMHEIGHT * difference + KSPACE * (difference - 1);
        return CGSizeMake(KITEMWIDTH, height);
    }
    return CGSizeMake(KITEMWIDTH, KITEMHEIGHT);
}

2、联动实现

我们知道iOS中滑动的结果显现就是列表contentOffset偏移量值的改变,所以要实现联动的基本原理也就很简单了,只要滑动一个控件时把当前控件的contentOffset偏移量赋值给想要联动控件的contentOffset就OK了。这样两个控件的contentOffset偏移量始终一致,动与停也就会一致了,联动也就基本实现了。两个单独的控件这样赋值可以很快实现,但是由于表格中用了UICollectionView的嵌套,滑动的时候会出现复用的情况,所以如果想要简单的赋值估计会出现无从赋值的情况,怎么去解决这个赋值的问题呢,我的处理方式是循环遍历取出再赋值。

/** 处理多向滑动 */
- (void)itemCollectionViewDidScroll:(UIScrollView *)scrollView
{
    [self scrollViewDidScroll:scrollView];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (scrollView == self.headCollectionView){
        self.bgCollectionView.contentOffset = CGPointMake(self.headCollectionView.contentOffset.x, 0);
    }else if (scrollView == self.bgCollectionView){
        self.headCollectionView.contentOffset = CGPointMake(self.bgCollectionView.contentOffset.x, 0);
    }
    
    if (scrollView == self.headCollectionView || scrollView == self.bgCollectionView || scrollView == self.leftCollectionView){
        [self updateCollectionViewOffictYWithView:self.leftCollectionView];
    }else{
        self.leftCollectionView.contentOffset = CGPointMake(0, scrollView.contentOffset.y);
        [self updateCollectionViewOffictYWithView:self.leftCollectionView];
    }
}

//循环取出赋值偏移量
- (void)updateCollectionViewOffictYWithView:(UIScrollView *)view
{
    NSIndexPath *indexPath = [self.bgCollectionView indexPathForItemAtPoint:self.bgCollectionView.contentOffset];
    NSInteger min = indexPath.section - self.refreshCount;
    NSInteger max = indexPath.section + self.refreshCount;
    max = max > self.dataArr.count ? self.dataArr.count : max;
    min = min > 0 ? min : 0;
    for (NSInteger i = min; i < max; i ++) {
        for (NSInteger j = 0; j < [self.dataArr[i] count]; j ++) {
            ZHBgCollectionViewCell *cell = (ZHBgCollectionViewCell *)[self.bgCollectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:j inSection:i]];
            if (!cell) {
                continue;
            }
            if (cell.collectionView.contentOffset.y == view.contentOffset.y) {
                continue;
            }
            cell.collectionView.contentOffset = CGPointMake(0, view.contentOffset.y);
        }
    }
}

通过届定一个范围,通过滑动时调用cellForItemAtIndexPath方法取出collectionView再赋值contentOffset这样的方式来解决无法赋值的情况,以此来解决因为嵌套联动的问题。

3、数据构造

由于我们收到后台返回的数据是一维数组,并且是只返回有值的数据,空白表格是不返回数据的。但是由于制作的表格是用的嵌套的方式来实现的界面,所以需要根据后台返回数据来进行一下本地处理,转换成三维数组,而且空白表格也不返回数据,所以本地也要处理一下空白数据占位填充。为了更快捷的去数据处理,在这里推荐一个为Objective-C带来Linq风格的流畅查询的库LinqToObjectiveC。基本处理逻辑是通过NSSortDescriptor类按单元、单元内序号、楼层进行一次整体排序,排序好后进行分组切割,空值补位,去重,然后进行值的保存,最后实现了数据的构造。

/** 处理数据并进行传值 */
- (void)getQueryWithLayersCount:(NSInteger)layersCount topLayers:(NSInteger)topLayers
{
    NSMutableArray *dataArr = [NSMutableArray array];
    
    NSString *strPath = [[NSBundle mainBundle] pathForResource:@"layer" ofType:@"geojson"];
    NSString *layerJson = [[NSString alloc] initWithContentsOfFile:strPath encoding:NSUTF8StringEncoding error:nil];
    NSMutableArray *jsonArr = [ZHItemModel mj_objectArrayWithKeyValuesArray:layerJson];
    
    ZHItemModel *layerModel = [[ZHItemModel alloc] init];
    //总楼层
    layerModel.layersCount = layersCount;
    layerModel.topLayers = topLayers;
    
    //key :按照unitNum属性 升序排序
    NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"unitNum" ascending:YES];
    //unitNum 相同 按照uIndex属性 升序排序
    NSSortDescriptor *sort1 = [NSSortDescriptor sortDescriptorWithKey:@"uIndex" ascending:YES];
    //uIndex 相同 按照onNominalLayer属性 降序排序
    NSSortDescriptor *sort2 = [NSSortDescriptor sortDescriptorWithKey:@"onNominalLayer" ascending:NO];
    //给数组添加排序规则
    [jsonArr sortUsingDescriptors:@[sort,sort1,sort2]];
    //    NSLog(@"dataArr:%@",[AppHouseInfoModel mj_keyValuesArrayWithObjectArray:dataArr]);
    
    NSDictionary *dict = [jsonArr linq_groupBy:^id(id item) {
        ZHItemModel *model = (ZHItemModel *)item;
        return [NSString stringWithFormat:@"%ld",model.unitNum];
    }];
    
    //key排序
    NSMutableArray *unitKeysArr = [NSMutableArray array];
    for (NSString *key in dict.allKeys) {
        [unitKeysArr addObject:[NSNumber numberWithInteger:key.integerValue]];
    }
    NSArray *allUnitKeys = [unitKeysArr linq_sort];
    for (NSNumber *unitKey in allUnitKeys) {
        NSMutableArray *tempArr = [NSMutableArray array];
        NSDictionary *unitDict = [[dict objectForKey:unitKey.stringValue] linq_groupBy:^id(id item) {
            ZHItemModel *tempModel = (ZHItemModel *)item;
            return [NSString stringWithFormat:@"%ld",tempModel.uIndex];
        }];
        //key排序
        NSMutableArray *indexKeysArr = [NSMutableArray array];
        for (NSString *key in unitDict.allKeys) {
            [indexKeysArr addObject:[NSNumber numberWithInteger:key.integerValue]];
        }
        NSArray *allIndexKeys = [indexKeysArr linq_sort];
        for (NSNumber *indexKey in allIndexKeys) {
            NSMutableArray *allLayerArr = [NSMutableArray array];
            NSMutableArray *layerArr = [unitDict objectForKey:indexKey.stringValue];
            for (int i = 0; i < layersCount; i ++) {
                ZHItemModel *placeholderModel = [[ZHItemModel alloc] init];
                NSInteger layer = topLayers - i;
                if (layer <= 0 && topLayers > 0) {
                    layer = layer - 1;
                }
                placeholderModel.physicLayers = layer;
                for (ZHItemModel *layerModel in layerArr) {
                    //符合判断条件赋值,不再展示空值
                    if (layerModel.lowerNominalLayer <= layer
                        && layer <= layerModel.highNominalLayer) {
                        placeholderModel = layerModel;
                        break;
                    }
                }
                [allLayerArr addObject:placeholderModel];
            }
            //去重保存 避免因为楼层合并出现重复值多的问题
            NSArray *distinctLayers = [allLayerArr linq_distinct];
            if (distinctLayers.count) {
                [tempArr addObject:distinctLayers];
            }
        }
        [dataArr addObject:tempArr];
    }
    //    NSLog(@"modelArr:%@",self.houseArr);
    //传入数据
    self.topView.allKeysArr = allUnitKeys;
    self.chartView.itemModel = layerModel;
    self.chartView.allKeysArr = allUnitKeys;
    self.chartView.dataArr = dataArr;
}

4、结尾

下载地址:ZHLinkageChartView,如果有更好的建议欢迎提出,喜欢的话记得给个star喽!

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 车上面那些小图标,你真的都能说出个所以然来?有了这个,那都不是事儿!
    汽车家族阅读 668评论 0 2
  • 我目前所在的橙子学院#30天专注橙长计划#社群,让我有一种很上进的感受。里面都是写作输出的人,每周以打卡形式输出文...
    敏同学谈成长阅读 410评论 0 0
  • 这封随笔就随便写写了,之前没在简书写过东西。 关于来意 有打算在简书更新自己的学习进程,但近期估计不会有记录。等合...
    Cinque_Peng阅读 329评论 0 0
  • 内存泄漏(memory leakage)概述 定义:在编写应用程序的时候,程序分配了一块内存,但已经不再持有引用这...
    MagicDong阅读 14,298评论 3 4