「OC」CAlayer——巧用动画实现一个丝滑的折叠cell

news2024/9/24 17:10:02

「OC」CAlayer——巧用动画实现一个丝滑的折叠cell

前言

在这个暑假集训后的时间,都在家里做着学习笔记的整理,深入学习了CALayer的相关知识,掌握了第三方库Masonry自动布局的用法,以及学习了MVC的相关内容,正好组内新学期的第一个任务就是写一个折叠cell的小demo,所以就打算将暑假学习过的内容,尽量整合在一块,进行巩固复习。

分装Model

由于我们只是简单的写一个折叠cell,所以单个的cell并不需要太过复杂的内容,所以我们将section进行分装即可,一个section就有着一个布尔值,判断是否展开,一个存储section数据的数组,还有对应section的headerView的标题

@interface Model : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSArray<NSString *> *items;
@property (nonatomic, assign) BOOL collapsed;

- (instancetype)initWithName:(NSString *)name items:(NSArray<NSString *> *)items collapsed:(BOOL)collapsed;

@end

NS_ASSUME_NONNULL_END
#import "Model.h"

@implementation Model

- (instancetype)initWithName:(NSString *)name items:(NSArray<NSString *> *)items collapsed:(BOOL)collapsed {
    self = [super init];
    if (self) {
        _name = name;
        _items = items;
        _collapsed = collapsed;
    }
    return self;
}

@end

重写headerView

为了更好的分装,提现MVC的实现,我写了一个headerView的子类,使用Masonry进行布局

//Header.h
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface Header : UITableViewHeaderFooterView

@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *rowButton;


@end

NS_ASSUME_NONNULL_END


//Header.m
#import "Header.h"
#import "Masonry.h"
@implementation Header

- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithReuseIdentifier:reuseIdentifier];
    if (self) {
        [self setupViews];
    }
    return self;
}

- (void)setupViews {
    self.contentView.backgroundColor = [UIColor purpleColor];
    
    self.titleLabel = [[UILabel alloc] init];
    self.titleLabel.font = [UIFont boldSystemFontOfSize:16];
    self.titleLabel.textColor = [UIColor whiteColor];
    [self.contentView addSubview:self.titleLabel];
    
    self.rowButton = [UIButton buttonWithType:UIButtonTypeSystem];
    [self.rowButton setImage:[UIImage systemImageNamed:@"chevron.right"] forState:UIControlStateNormal];
    self.rowButton.tintColor = [UIColor darkTextColor];
    [self.contentView addSubview:self.rowButton];
    
    [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.contentView).offset(15);
        make.centerY.equalTo(self.contentView);
        make.right.equalTo(self.contentView);
    }];
    
    [self.rowButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.width.equalTo(@30);
        make.height.equalTo(@30);
        make.right.equalTo(self.contentView).offset(-15);
        make.centerY.equalTo(self.contentView);
    }];

}

@end

我们还需要在控制器之中对headerView进行注册

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setupSections];
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.view addSubview:self.tableView];
    [self.tableView registerClass:[Header class] forHeaderFooterViewReuseIdentifier:@"header"];
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    Header *header = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"header"];
    
    if (!header) {
        header = [[Header alloc] initWithReuseIdentifier:@"header"];
    }
    
    Model *sectionData = self.models[section];
    header.titleLabel.text = sectionData.name;
    header.rowButton.tag = section;
    [header.rowButton addTarget:self action:@selector(toggleCollapse:) forControlEvents:UIControlEventTouchUpInside];
    
    [self rotateButton:header.rowButton collapsed:sectionData.collapsed];
    
    return header;
}

折叠的相关逻辑

其实早在暑假的3Gshare之中就已经学习折叠cell,只不过那时候写的比较大粗糙,还是为了应付是直接设置了一个button,点击就将tableView展开,当button不再选中状态的时候tableView就收回,这次编写的是使用tableView的headerView来进行操作,不过大致展开的逻辑还是相同的

我们先前在Model之中定义了一个判定是否展开的布尔值

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    Model *sectionData = self.models[section];
    return sectionData.collapsed ? 0 : sectionData.items.count;
}

- (void)toggleCollapse:(UIButton *)sender {//设置相关的方法使得headerView的按钮能触发该事件
    NSInteger section = sender.tag;
    Model *sectionData = self.models[section];
    sectionData.collapsed = !sectionData.collapsed;
    
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationNone];//使用动画进行自动变化
  
}	

设置动画

由于按钮图片之中的箭头,需要在点击的时候顺带进行,90度的翻转,所以,我们我用上了CALayer之中的旋转动画,内容如下

- (void)rotateButton:(UIButton *)button collapsed:(BOOL)collapsed {
    CGFloat angle = collapsed ? 0.0 : M_PI_2;
    button.transform = CGAffineTransformMakeRotation(angle);
}

完整代码

控制器的完整代码如下

#import "ViewController.h"
#import "Model.h"
#import "Header.h"

@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>

@property (nonatomic, strong) NSMutableArray<Model *> * models;
@property (nonatomic, strong) UITableView *tableView;
@end

@implementation ViewController



- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setupSections];
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.view addSubview:self.tableView];
    [self.tableView registerClass:[Header class] forHeaderFooterViewReuseIdentifier:@"header"];
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
}

- (void)setupSections {
    self.models = [NSMutableArray array];
    [self.models addObject:[[Model alloc] initWithName:@"Mac" items:@[@"MacBook", @"MacBook Air", @"MacBook Pro", @"iMac", @"Mac Pro", @"Mac mini", @"Accessories", @"OS X El Capitan"] collapsed:YES]];
    [self.models addObject:[[Model alloc] initWithName:@"iPad" items:@[@"iPad Pro", @"iPad Air 2", @"iPad mini 4", @"Accessories"] collapsed:NO]];
    [self.models addObject:[[Model alloc] initWithName:@"iPhone" items:@[@"iPhone 6s", @"iPhone 6", @"iPhone SE", @"Accessories"] collapsed:NO]];
}



- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.models.count;
}

-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    return 0;
}

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
    return view;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    Model *sectionData = self.models[section];
    return sectionData.collapsed ? 0 : sectionData.items.count;
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    Header *header = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"header"];
    
    if (!header) {
        header = [[Header alloc] initWithReuseIdentifier:@"header"];
    }
    
    Model *sectionData = self.models[section];
    header.titleLabel.text = sectionData.name;
    header.rowButton.tag = section;
    [header.rowButton addTarget:self action:@selector(toggleCollapse:) forControlEvents:UIControlEventTouchUpInside];
    
    [self rotateButton:header.rowButton collapsed:sectionData.collapsed];
    
    return header;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    return 50;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    
    Model *sectionData = self.models[indexPath.section];
    cell.textLabel.text = sectionData.items[indexPath.row];
    
    return cell;
}



- (void)toggleCollapse:(UIButton *)sender {
    NSInteger section = sender.tag;
    Model *sectionData = self.models[section];
    sectionData.collapsed = !sectionData.collapsed;
    
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationNone];
}

- (void)rotateButton:(UIButton *)button collapsed:(BOOL)collapsed {
    CGFloat angle = collapsed ? 0.0 : M_PI_2;
    button.transform = CGAffineTransformMakeRotation(angle);
}
@end

展示

实现的完整内容如下

Aug-30-2024 10-58-33

参考资料

如何在 IOS 中实现可折叠 / 展开的 Table Section?!

ios-swift-collapsible-table-section

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2090148.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在Postgresql中计算工单的对应的GPS轨迹距离

一、概述 在某个App开发中&#xff0c;要求记录用户的日常轨迹&#xff0c;在用户巡逻设备的时&#xff0c;将记录的轨迹点当做该设备巡逻时候的轨迹。 由于业务逻辑上没有明确的指示人员巡逻工单-GPS位置之间的关系&#xff0c;所以通过时间关系进行轨迹划定。 二、创建测试表…

Core ML

本文翻译整理自&#xff1a;Core ML : https://developer.apple.com/cn/documentation/coreml/ 文章目录 一、概览二、获取 Core ML 模型三、将 Core ML 模型集成到你的 App 中1、将模型添加到您的Xcode项目2、在代码中创建模型3、获取要传递给模型的输入值4、使用模型进行预测…

vue2踩坑记录:el-select如何绑定对象

页面上的下拉框&#xff1a; 选中人员之后&#xff0c;需要使用人员的其它信息做后续操作。所以不能只绑定用户Id&#xff0c;需要绑定整个item(用户对象)&#xff0c;这样每次change事件所获取到的newValue是整个对象&#xff0c;而且v-model中的变量指向的也是整个对象&#…

Windows bat脚本学习五(函数)

一、简介 使用bat脚本时&#xff0c;经常要使用函数&#xff0c;而函数的传参和返回值也是非常重要的&#xff0c;今天就总结一下bat中函数相关的使用方法。 二、函数 1.函数调用 :函数名 在Bat中&#xff0c;使用“:函数名”来定义一个函数。 见如下代码&#xff1a; echo o…

安装KataGo+Sabaki围棋引擎

1.由于我的用户名是中文名,修改TMP和TEMP为SystemRoot(后续修改回来) 2.加入xx.bin文件后,cmd输入katago.exe genconfig -model model.bin -output gtp_custom.cfg 3.KataGo下载完成 4.配置GUI界面

ubuntu录屏解决ubuntu下无法播放MP4格式文件的方法

参考 gnome gnome是系统自带的录屏&#xff0c;通过ctrlshiftaltr触发 保存到了视频目录下&#xff0c;webm格式文件。 screencastify 这是一个chrome扩展&#xff0c;&#xff0c;一般不推荐使用 recapp 比gnome自由一些&#xff0c;可以自由屏幕录制。但是无法修改录制…

利用流水线实现版本一键发布

目录 1. 背景2. 实现步骤3.1 前置条件3.2 更新版本号和拉出发布分支3.3 生成 diffCommit3.4 自动触发联动编译3.5 让通知更友好 3. 总结 1. 背景 通常我们发布版本时会有这样几个步骤&#xff0c;更改版本号&#xff0c;拉出 release 分支&#xff0c;生成发布包。但是博主所在…

关于ThinkPHP 5 框架开启自动搜索控制器 无法访问的问题坑

假如当前有一个登陆接口功能 因为后续会有不同版本的 登陆接口 这时候 我们可以在控制器中 新建文件夹 做区分 方便管理即 新建了一个 api 模块 文件路径是 api/controller/V1/Login 正常情况下 controller 目录下 是 控制器文件 login.php 文件&#xff0c;由于我们有多个…

地下车库电车这样充电,必须改

文 | AUTO芯球 作者 | 响铃 我是真的害怕啊 你们看&#xff0c;我住的小区&#xff0c; 都是这样子啊 一个这样子的塑料盒&#xff0c;最多一个铝板盒子&#xff0c;就给车充电了 你看看 这样的塑料电槽 就这样裸露在外面了 有些摸一下 电线都在发烫 现在我们小区内这…

【NCom】:通用负压退火方法构建超高负载单原子催化剂库

摘要&#xff1a;由高密度单原子填充的催化剂系统对于提高催化活性和选择性至关重要&#xff0c;这可以最大限度地发挥异质单原子催化剂 (SAC) 的工业前景。然而&#xff0c;实现金属含量超过 10 wt% 的高负载 SAC 仍然具有挑战性。在这里&#xff0c;我们描述了一种通用的负压…

【UCB CS61C】Lecture 4 - C Memory Management Usage

目录 C 的内存布局&#xff08;Memory Layout&#xff09;栈&#xff08;Stack&#xff09;静态数据&#xff08;Static Data&#xff09;代码&#xff08;Code&#xff09; 寻址&#xff08;Addressing&#xff09;地址&#xff08;Address&#xff09;字节序&#xff08;Endi…

电脑缺少dll文件怎么解决?Dll文件修复工具使用教程(方法合集)

众所周知&#xff0c;dll文件是计算器中的一类文件。占据了Windows操作系统的重要地位&#xff0c;主要作用就是可以让多个程序在运行时加以使用。dll文件包含了数字、文本、界面的等内容。 电脑缺少dll文件怎么解决&#xff1f;如果你启动某个程序时&#xff0c;发现电脑提示缺…

如何共享EC2 AMI给其他AWS账户

在本篇文章中&#xff0c;我们将详细介绍如何通过Amazon Web Services (AWS) 的Elastic Compute Cloud (EC2) 平台&#xff0c;将自定义AMI&#xff08;Amazon Machine Image&#xff09;共享给其他AWS账户。接下来&#xff0c;我们九河云将一步步引导您完成整个过程&#xff0…

数据驱动,智领办公!陀螺匠·企业助手 v1.7公测版发布

在数字化转型浪潮中&#xff0c;企业对办公自动化系统的需求愈加强烈&#xff0c;追求高效、灵活、智能的办公管理解决方案成为行业共识&#xff0c;我们深知&#xff0c;只有不断创新和完善&#xff0c;才能满足企业日益增长的需求。此次&#xff0c;我们带来陀螺匠企业助手 v…

【GeoScenePro】知识图谱

视频教程: ArcGIS/GeoScene知识图谱入门篇_哔哩哔哩_bilibili 所需软件: GeoScene Pro桌面端产品 Geoscene Enterprise四大组件(GeoScene_Server、GeoScene_DataStore、GeoScene_Portal、GeoScene_Web_Adaptor) 安装 【GeoScenePortal】安装和部署-CSDN博客

2024年下半年软考备考前的注意细节点

一、备考教材信息——选择官方正版&#xff08;电子、纸质都可以&#xff09; 中项 书名&#xff1a;《系统集成项目管理工程师教程》&#xff08;第三版&#xff09; 出版社&#xff1a;清华大学出版社 书籍类型&#xff1a;全国计算机技术与软件专业技术资格&#xff08;…

centos安装docker并配置加速器

docker安装与卸载&#xff1a; 1、检查当前是否安装docker yum list installed | grep docker2、卸载docker 根据yum list installed | grep docker查询出来的内容&#xff0c;逐个进行删除 yum remove docker.x86 64 -y3、启动与关闭docker 4、删除/etc/docker文件夹 如果…

少走弯路,ESP32 读取Micro SD(TF)播放mp3的坑路历程。

这个坑采的非常冤枉和巨大&#xff0c;非常大的冤枉路&#xff0c;只能一声叹息 说一下我是如何踩坑的&#xff0c;原本是打算用esp32 读取SD卡播放mp3,在esp32 读取自己打的SD卡已经踩了无数坑了&#xff0c;详情见&#xff1a; 少走弯路&#xff0c;ESP32 使用Micro SD(TF)…

Java学习第五天

数组 数组适合做一批同类型数据的存储。 静态初始化数组&#xff1a; 注意&#xff1a;数组变量名中存储的是数组在内存中的地址&#xff0c;数组是引用类型。 数组的访问 动态初始化数组&#xff1a; 数组的遍历&#xff1a; 注意左边和右边的区别&#xff0c;一个是改变数组…

日元升值,日股遇冷:出口商的烦恼

最近&#xff0c;日元汇率的走强让不少日本企业感到头疼。日元升值就像一把双刃剑&#xff0c;既能带来好处&#xff0c;也能带来坏处。 为什么日元升值会让日本企业头疼&#xff1f; 出口受阻&#xff1a; 当日元升值时&#xff0c;日本商品在国际市场上的价格就会变得相对较…