其中每个UITableViewCell左右两部分拥有不一样的功能git
下面是我在作这个功能时的思路, 使用的是MVCgithub
name
: 选项的名称subs
: 选项的子层级数据#import <Foundation/Foundation.h>
@interface LTMenuItem : NSObject
/** 名字 */
@property (nonatomic, strong) NSString *name;
/** 子层 */
@property (nonatomic, strong) NSArray<LTMenuItem *> *subs;
@end
复制代码
#import "LTMenuItemViewController.h"
#import "LTMenuItem.h"
#import "LTMenuItemCell.h"
#import <MJExtension/MJExtension.h>
@interface LTMenuItemViewController ()
/** 菜单项 */
@property (nonatomic, strong) NSMutableArray<LTMenuItem *> *menuItems;
@end
@implementation LTMenuItemViewController
static NSString *LTMenuItemId = @"LTMenuItemCell";
- (void)viewDidLoad {
[super viewDidLoad];
[self setup];
[self setupTableView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - < 基本设置 >
- (void)setup
{
self.title = @"多级菜单";
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"a" ofType:@"plist"];
NSArray *date = [NSArray arrayWithContentsOfFile:filePath];
self.menuItems = [LTMenuItem mj_objectArrayWithKeyValuesArray:date];
self.tableView.separatorStyle = UITableViewCellSelectionStyleNone;
self.tableView.rowHeight = 45;
[self.tableView registerClass:[LTMenuItemCell class] forCellReuseIdentifier:LTMenuItemId];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.menuItems.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LTMenuItemCell *cell = [tableView dequeueReusableCellWithIdentifier:LTMenuItemId forIndexPath:indexPath];
cell.menuItem = self.menuItems[indexPath.row];
return cell;
}
复制代码
LTMenuItem
类添加几个辅助属性, 用于表示选中和展开闭合
isSelected
: 用于表示选项的选中状态isUnfold
: 用来表示本层级的展开和闭合状态isCanUnfold
: 用于表示本层级是否可以展开, 只有当subs
属性的个数不为0时, 才取值YES
index
: 表示当前的层级, 第一层的值为0#import <Foundation/Foundation.h>
@interface LTMenuItem : NSObject
/** 名字 */
@property (nonatomic, strong) NSString *name;
/** 子层 */
@property (nonatomic, strong) NSArray<LTMenuItem *> *subs;
#pragma mark - < 辅助属性 >
/** 是否选中 */
@property (nonatomic, assign) BOOL isSelected;
/** 是否展开 */
@property (nonatomic, assign) BOOL isUnfold;
/** 是否能展开 */
@property (nonatomic, assign) BOOL isCanUnfold;
/** 当前层级 */
@property (nonatomic, assign) NSInteger index;
@end
复制代码
#import "LTMenuItem.h"
@implementation LTMenuItem
/**
指定subs数组中存放LTMenuItem类型对象
*/
+ (NSDictionary *)mj_objectClassInArray
{
return @{@"subs" : [LTMenuItem class]};
}
/**
判断是否可以展开, 当subs中有数据时才能展开
*/
- (BOOL)isCanUnfold
{
return self.subs.count > 0;
}
@end
复制代码
LTMenuItemViewController
中, 当前展现的数据是数组menuItems
, 此时并很差控制应该展现在tableView
中的数据, 因此添加一个新的属性, 用来包含须要展现的数据@interface LTMenuItemViewController ()
/** 菜单项 */
@property (nonatomic, strong) NSMutableArray<LTMenuItem *> *menuItems;
/** 当前须要展现的数据 */
@property (nonatomic, strong) NSMutableArray<LTMenuItem *> *latestShowMenuItems;
@end
复制代码
latestShowMenuItems
就是展现在tableView中的数据latestShowMenuItems
- (NSMutableArray<LTMenuItem *> *)latestShowMenuItems
{
if (!_latestShowMenuItems) {
self.latestShowMenuItems = [[NSMutableArray alloc] init];
}
return _latestShowMenuItems;
}
复制代码
latestShowMenuItems
替换menuItems
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.latestShowMenuItems.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LTMenuItemCell *cell = [tableView dequeueReusableCellWithIdentifier:LTMenuItemId forIndexPath:indexPath];
cell.menuItem = self.latestShowMenuItems[indexPath.row];
return cell;
}
复制代码
latestShowMenuItems
中包含的数据, 就能够控制页面的展现, 而menuItems
中的数据不须要增长和减小latestShowMenuItems
中数据的方法latestShowMenuItems
中没有数据, 因此界面初始化后将不会展现任何数据latestShowMenuItems
中添加初始化界面时须要展现的数据, 并设置层级为0- (void)setupRowCount
{
// 添加须要展现项, 并设置层级, 初始化0
[self setupRouCountWithMenuItems:self.menuItems index:0];
}
/**
将须要展现的选项添加到latestShowMenuItems中
*/
- (void)setupRouCountWithMenuItems:(NSArray<LTMenuItem *> *)menuItems index:(NSInteger)index
{
for (int i = 0; i < menuItems.count; i++) {
LTMenuItem *item = menuItems[i];
// 设置层级
item.index = index;
// 将选项添加到数组中
[self.latestShowMenuItems addObject:item];
}
}
复制代码
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
方法, 处理菜单的展开闭合操做- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 取出点击的选项
LTMenuItem *menuItem = self.latestShowMenuItems[indexPath.row];
// 判断是否可以展开, 不能展开当即返回, 不错任何处理
if (!menuItem.isCanUnfold) return;
// 设置展开闭合
menuItem.isUnfold = !menuItem.isUnfold;
// 刷新列表
[self.tableView reloadData];
}
复制代码
isUnfold
属性, 并刷新界面latestShowMenuItems
中数据没有数量变化, 因此子层级并不能显示出来latestShowMenuItems
中的数据进行修改#pragma mark - < 添加能够展现的选项 >
- (void)setupRowCount
{
// 清空当前全部展现项
[self.latestShowMenuItems removeAllObjects];
// 从新添加须要展现项, 并设置层级, 初始化0
[self setupRouCountWithMenuItems:self.menuItems index:0];
}
/**
将须要展现的选项添加到latestShowMenuItems中, 此方法使用递归添加全部须要展现的层级到latestShowMenuItems中
@param menuItems 须要添加到latestShowMenuItems中的数据
@param index 层级, 即当前添加的数据属于第几层
*/
- (void)setupRouCountWithMenuItems:(NSArray<LTMenuItem *> *)menuItems index:(NSInteger)index
{
for (int i = 0; i < menuItems.count; i++) {
LTMenuItem *item = menuItems[i];
// 设置层级
item.index = index;
// 将选项添加到数组中
[self.latestShowMenuItems addObject:item];
// 判断该选项的是否能展开, 而且已经须要展开
if (item.isCanUnfold && item.isUnfold) {
// 当须要展开子集的时候, 添加子集到数组, 并设置子集层级
[self setupRouCountWithMenuItems:item.subs index:index + 1];
}
}
}
复制代码
latestShowMenuItems
中的数据, 而后添加第一层数据latestShowMenuItems
中, 同时设置了每一层数据的层级属性index
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
方法, 须要作以下修改- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 取出点击的选项
LTMenuItem *menuItem = self.latestShowMenuItems[indexPath.row];
// 判断是否可以展开, 不能展开当即返回, 不错任何处理
if (!menuItem.isCanUnfold) return;
// 设置展开闭合
menuItem.isUnfold = !menuItem.isUnfold;
// 修改latestShowMenuItems中数据
[self setupRowCount];
// 刷新列表
[self.tableView reloadData];
}
复制代码
oldShowMenuItems
, 用来记录改变前latestShowMenuItems
中的数据@interface LTMenuItemViewController ()
/** 菜单项 */
@property (nonatomic, strong) NSMutableArray<LTMenuItem *> *menuItems;
/** 当前须要展现的数据 */
@property (nonatomic, strong) NSMutableArray<LTMenuItem *> *latestShowMenuItems;
/** 之前须要展现的数据 */
@property (nonatomic, strong) NSMutableArray<LTMenuItem *> *oldShowMenuItems;
@end
复制代码
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
方法, 添加展开动画效果- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
LTMenuItem *menuItem = self.latestShowMenuItems[indexPath.row];
if (!menuItem.isCanUnfold) return;
// 记录改变以前的数据
self.oldShowMenuItems = [NSMutableArray arrayWithArray:self.latestShowMenuItems];
// 设置展开闭合
menuItem.isUnfold = !menuItem.isUnfold;
// 更新被点击cell的箭头指向
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:(UITableViewRowAnimationAutomatic)];
// 设置须要展开的新数据
[self setupRowCount];
// 判断老数据和新数据的数量, 来进行展开和闭合动画
// 定义一个数组, 用于存放须要展开闭合的indexPath
NSMutableArray<NSIndexPath *> *indexPaths = @[].mutableCopy;
// 若是 老数据 比 新数据 多, 那么就须要进行闭合操做
if (self.oldShowMenuItems.count > self.latestShowMenuItems.count) {
// 遍历oldShowMenuItems, 找出多余的老数据对应的indexPath
for (int i = 0; i < self.oldShowMenuItems.count; i++) {
// 当新数据中 没有对应的item时
if (![self.latestShowMenuItems containsObject:self.oldShowMenuItems[i]]) {
NSIndexPath *subIndexPath = [NSIndexPath indexPathForRow:i inSection:indexPath.section];
[indexPaths addObject:subIndexPath];
}
}
// 移除找到的多余indexPath
[self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimationTop)];
}else {
// 此时 新数据 比 老数据 多, 进行展开操做
// 遍历 latestShowMenuItems, 找出 oldShowMenuItems 中没有的选项, 就是须要新增的indexPath
for (int i = 0; i < self.latestShowMenuItems.count; i++) {
if (![self.oldShowMenuItems containsObject:self.latestShowMenuItems[i]]) {
NSIndexPath *subIndexPath = [NSIndexPath indexPathForRow:i inSection:indexPath.section];
[indexPaths addObject:subIndexPath];
}
}
// 插入找到新添加的indexPath
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimationTop)];
}
}
复制代码
LTMenuItem
中isSelected
的值, 来控制选中状态- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LTMenuItemCell *cell = [tableView dequeueReusableCellWithIdentifier:LTMenuItemId forIndexPath:indexPath];
cell.menuItem = self.latestShowMenuItems[indexPath.row];
cell.delegate = self;
return cell;
}
复制代码
#pragma mark - < LTMenuItemCellDelegate >
- (void)cell:(LTMenuItemCell *)cell didSelectedBtn:(UIButton *)sender
{
cell.menuItem.isSelected = !cell.menuItem.isSelected;
[self.tableView reloadData];
}
复制代码
全选
按钮, 并实现对应的点击方法#pragma mark - < 点击事件 >
- (void)allBtnClick:(UIButton *)sender
{
sender.selected = !sender.selected;
[self selected:sender.selected menuItems:self.menuItems];
}
/**
取消或选择, 某一数值中全部的选项, 包括子层级
@param selected 是否选中
@param menuItems 选项数组
*/
- (void)selected:(BOOL)selected menuItems:(NSArray<LTMenuItem *> *)menuItems
{
for (int i = 0; i < menuItems.count; i++) {
LTMenuItem *menuItem = menuItems[i];
menuItem.isSelected = selected;
if (menuItem.isCanUnfold) {
[self selected:selected menuItems:menuItem.subs];
}
}
[self.tableView reloadData];
}
复制代码
- (void)cell:(LTMenuItemCell *)cell didSelectedBtn:(UIButton *)sender
的实现#pragma mark - < LTMenuItemCellDelegate >
- (void)cell:(LTMenuItemCell *)cell didSelectedBtn:(UIButton *)sender
{
cell.menuItem.isSelected = !cell.menuItem.isSelected;
// 修改按钮状态
self.allBtn.selected = NO;
[self.tableView reloadData];
}
复制代码
selectedMenuItems
, 用于存储已选数据@interface LTMenuItemViewController () <LTMenuItemCellDelegate>
/** 菜单项 */
@property (nonatomic, strong) NSMutableArray<LTMenuItem *> *menuItems;
/** 当前须要展现的数据 */
@property (nonatomic, strong) NSMutableArray<LTMenuItem *> *latestShowMenuItems;
/** 之前须要展现的数据 */
@property (nonatomic, strong) NSMutableArray<LTMenuItem *> *oldShowMenuItems;
/** 已经选中的选项, 可用于回调 */
@property (nonatomic, strong) NSMutableArray<LTMenuItem *> *selectedMenuItems;
/** 全选按钮 */
@property (nonatomic, strong) UIButton *allBtn;
@end
复制代码
#pragma mark - < 选中数据 >
- (void)printSelectedMenuItems:(UIButton *)sender
{
[self.selectedMenuItems removeAllObjects];
[self departmentsWithMenuItems:self.menuItems];
NSLog(@"这里是所有选中数据\n%@", self.selectedMenuItems);
}
/**
获取选中数据
*/
- (void)departmentsWithMenuItems:(NSArray<LTMenuItem *> *)menuItems
{
for (int i = 0; i < menuItems.count; i++) {
LTMenuItem *menuItem = menuItems[i];
if (menuItem.isSelected) {
[self.selectedMenuItems addObject:menuItem];
}
if (menuItem.subs.count) {
[self departmentsWithMenuItems:menuItem.subs];
}
}
}
复制代码
printSelectedMenuItems
方法中的NSLog(@"这里是所有选中数据\n%@", self.selectedMenuItems);
便可demo地址: https://github.com/963527512/MultilayerMenu数组