iOS开发——多级非互斥TableView的实现

有时我们会需要用到类似QQ好友列表那样可展开的列表,无奈iOS并没有像Android那样给我们提供。只能自己实现了。其实自己封装也好,自由度高,更灵活。

感谢@TinyQ的TQMultiStateTableView为我提供了一个很好地思路。本文正是借鉴他的作品并做了一些自己的改动,例如实现了非互斥功能,即可以同时点开多个子列表。另外,这里实现的是二级列表,即只能展开一层,他的源代码中是展开两层,通过学习他的代码,我想展开几层都能实现了。不过现在由于我的应用中只需要展开一层,所以节省了不少代码


效果如下:


废话不多说,直接上代码


一、思路

第一级列表是列表的header,即有很多个section,每个section都有一个header,初始状态下隐藏各个section里的row,同时为header添加点击手势,当点击时判断点击section是否已经展开,如果展开了,就关闭之,否则将其打开。而为了实现非互斥的效果,我们用一个数组来记录已经展开的section。


二、头文件:

#import <UIKit/UIKit.h>

@protocol MultistageTableViewDataSource , MultistageTableViewDelegate;

@interface MultistageTableView : UIView <UITableViewDataSource, UITableViewDelegate>

/**
 *  主表格
 */
@property (strong, nonatomic) UITableView *tableView;
/**
 *  当前展开的所有cell的indexPath的数组
 */
@property (strong, nonatomic) NSMutableArray *currentOpenedIndexPaths;
/**
 *  数据源
 */
@property (weak, nonatomic) id<MultistageTableViewDataSource> dataSource;
/**
 *  协议
 */
@property (weak, nonatomic)id<MultistageTableViewDelegate> delegate;

/**
 *  根据标识符取出重用cell
 *
 *  @param identifier 重用标识符
 *
 *  @return 可重用的cell,或者nil(如果没有可重用的)
 */
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
- (id)dequeueReusableHeaderFooterViewWithIdentifier:(NSString *)identifier;

/**
 *  取消对cell的选中状态
 *
 *  @param indexPath 选中的cell的indexPath
 *  @param animated  是否使用动画
 */
- (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated;

/**
 *  重新加载数据
 */
- (void)reloadData;

@end

@protocol MultistageTableViewDataSource <NSObject>

@required

- (NSInteger)m_tableView:(MultistageTableView *)mtableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)m_tableView:(MultistageTableView *)mtableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

@optional
- (NSInteger)numberOfSectionsInMTableView:(MultistageTableView *)mtableView;

@end

@protocol MultistageTableViewDelegate <NSObject>

@optional

- (CGFloat)m_tableView:(MultistageTableView *)mtableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)m_tableView:(MultistageTableView *)mtableView heightForHeaderInSection:(NSInteger)section;

- (UIView *)m_tableView:(MultistageTableView *)mtableView viewForHeaderInSection:(NSInteger)section;

- (void)m_TableView:(MultistageTableView *)mTableView willOpenHeaderAtSection:(NSInteger)section;
- (void)m_TableView:(MultistageTableView *)mTableView willCloseHeaderAtSection:(NSInteger)section;
- (void)m_TableView:(MultistageTableView *)mTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

- (UITableViewCellEditingStyle)m_tableView:(MultistageTableView *)mtableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)m_tableView:(MultistageTableView *)mtableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;

@end

首先解释一点,看到这么“复制”来的UITableView的代码(实际上这还不全,随着以后的使用,这个还必定会继续扩充UITableView的方法),大家一定认为这是个low设计——直接继承UITableView不就行了?其实这样是有原因的。我试着继承UITableView写过,但是很可惜失败了,因为数据源需要“双方提供”,即该控件需要提供,同时需要客户端提供。因此此处我们用组合而不用继承。理解了这点后头文件就没有什么可说的了。


三、关键代码

1、获得展开的行,这里先根据数据源获得section的数量,然后触发代理,将当前要展开的section加入到记录数组中,将该行的下标记录下来并返回。

/**
 *  展开一个header所新增加的行
 *
 *  @param section 待展开的一组数据所在的section
 *
 *  @return 该section内所有indexPath信息
 */
- (NSMutableArray *)indexPathsForOpenHeaderInSection:(NSInteger)section {
    NSMutableArray *indexPaths = [NSMutableArray array];
    
    //询问数据源行数
    NSInteger rowCount = [self get_numberOfRowsInSection:section];
    
    //调用代理
    if ([self.delegate respondsToSelector:@selector(m_TableView:willOpenHeaderAtSection:)]) {
        [self.delegate m_TableView:self willOpenHeaderAtSection:section];
    }
    
    //打开了第section个子列表
    [self.currentOpenedIndexPaths addObject:[NSIndexPath indexPathForRow:-1 inSection:section]];
    
    //在当期列表中添加rowCount行数据
    for (int i = 0; i < rowCount; i++) {
        [indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:section]];
    }
    
    return indexPaths;
}

2、获得要关闭的行,与上类似

- (NSMutableArray *)indexPathsForCloseHeaderInSection:(NSInteger)section {
    NSMutableArray *indexPaths = [NSMutableArray array];
    
    //询问数据源行数
    NSInteger rowCount = [self get_numberOfRowsInSection:section];
    
    //调用代理
    if ([self.delegate respondsToSelector:@selector(m_TableView:willCloseHeaderAtSection:)]) {
        [self.delegate m_TableView:self willCloseHeaderAtSection:section];
    }
    
    //关闭第section个子列表
    [self.currentOpenedIndexPaths removeObject:[NSIndexPath indexPathForRow:-1 inSection:section]];
    
    for (int i = 0; i < rowCount; i++) {
        [indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:section]];
    }
    
    return indexPaths;
}
3、展开或关闭某section,判断是展开还是关闭,然后调用UITableView的相关方法更新视图

- (void)openOrCloseHeaderWithSection:(NSInteger)section {
    
    NSMutableArray *openedIndexPaths = [NSMutableArray array];
    NSMutableArray *deleteIndexPaths = [NSMutableArray array];
    
    //如果当前没有任何子列表被打开
    if (self.currentOpenedIndexPaths.count == 0) {
        openedIndexPaths = [self indexPathsForOpenHeaderInSection:section];
    } else {
        BOOL found = NO;
        
        for (NSIndexPath *ip in self.currentOpenedIndexPaths) {
            //如果是关闭当前已经打开的子列表
            if (ip.section == section) {
                found = YES;
                deleteIndexPaths = [self indexPathsForCloseHeaderInSection:section];
                break;
            }
        }
        
        //打开新的子列表
        if (!found) {
            openedIndexPaths = [self indexPathsForOpenHeaderInSection:section];
        }
    }
    
    [self.tableView beginUpdates];
    if (openedIndexPaths.count > 0) {
        [self.tableView insertRowsAtIndexPaths:openedIndexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
    }
    if (deleteIndexPaths.count > 0) {
        [self.tableView deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
    }
    [self.tableView endUpdates];
    
}

而tap的触发方法就是调用上述打开或关闭列表的方法。这其中,连接所有方法的section值可以通过在tableview的协议中为header的tag赋值得到。

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    UIView *header = [self get_viewForHeaderInSection:section];
    if (header) {
        CGFloat height = [self tableView:tableView heightForHeaderInSection:section];
        header.frame = CGRectMake(0, 0, self.tableView.frame.size.width, height);
        header.tag = section;
        
        [self addTapGestureRecognizerAction:@selector(tableViewHeaderTouchUpInside:) toView:header];
    }
    return header;
}


其余的都比较简单,就是一些初始化或者对tableview的方法的处理调用。这里就不一一列举了。可以参考上传的代码。



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值