iOS开发笔记--iOS7中弹簧式列表的制作

本文详细介绍如何使用UIKit Dynamics在iOS7中实现类似信息App的弹性列表效果,包括UIAttachmentBehavior的配置及 UICollectionView 的集成。

这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看这篇总览。本文仅作为个人记录使用,也欢迎在许可协议范围内转载或使用,但是还烦请保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用RSS邮件方式订阅本站,这样您将能在第一时间获取本站信息。

本文涉及到的WWDC2013 Session有

  • Session 206 Getting Started with UIKit Dynamics
  • Session 217 Exploring Scroll Views in iOS7

UIScrollView可以说是UIKit中最重要的类之一了,包括UITableView和UICollectionView等重要的数据容器类都是UIScrollView的子类。在历年的WWDC上,UIScrollView和相关的API都有专门的主题进行介绍,也可以看出这个类的使用和变化之快。今年也不例外,因为iOS7完全重新定义了UI,这使得UIScrollView里原来不太会使用的一些用法和实现的效果在新的系统中得到了很好的表现。另外,由于引入了UIKit Dynamics,我们还可以结合ScrollView做出一些以前不太可能或者需要花费很大力气来实现的效果,包括带有重力的swipe或者是类似新的信息app中的带有弹簧效果聊天泡泡等。如果您还不太了解iOS7中信息app的效果,这里有一张gif图可以帮您大概了解一下:


这次笔记的内容主要就是实现一个这样的效果。为了避免重复造轮子,我对这个效果进行了一些简单的封装,并连同这篇笔记的demo一起扔在了Github上,有需要的童鞋可以到这里自取。

iOS7的SDK中Apple最大的野心其实是想用SpriteKit来结束iOS平台游戏开发(至少是2D游戏开发)的乱战,统一游戏开发的方式并建立良性社区。而UIKit Dynamics,个人猜测Apple在花费力气为SpriteKit开发了物理引擎的同时,发现在UIKit中也可以使用,并能得到不错的效果,于是顺便革新了一下设计理念,在UI设计中引入了不少物理的概念。在iOS系统中,最为典型的应用是锁屏界面打开相机时中途放弃后的重力下坠+反弹的效果,另一个就是信息应用中的加入弹性的消息列表了。弹性列表在我自己上手试过以后觉得表现形式确实很生动,可以消除原来列表那种冷冰冰的感觉,是有可能在今后的设计中被大量使用的,因此决定学上一学。

首先我们需要知道要如何实现这样一种效果,我们会用到哪些东西。毋庸置疑,如果不使用UIKit Dynamics的话,自己从头开始来完成会是一件非常费力的事情,你可能需要实现一套位置计算和物理模拟来使效果看起来真实滑润。而UIKit Dynamics中已经给我们提供了现成的弹簧效果,可以用UIAttachmentBehavior进行实现。另外,在说到弹性效果的时候,我们其实是在描述一个列表中的各个cell之间的关系,对于传统的UITableView来说,描述UITableViewCell之间的关系是比较复杂的(因为Apple已经把绝大多数工作做了,包括计算cell位置和位移等。使用越简单,定制就会越麻烦在绝大多数情况下都是真理)。而UICollectionView则通过layout来完成cell之间位置关系的描述,给了开发者较大的空间来实现布局。另外,UIKit Dynamics为UICollectionView做了很多方便的Catagory,可以很容易地“指导”UICollectionView利用加入物理特性计算后的结果,在实现弹性效果的时候,UICollectionView是我们不二的选择。

如果您在阅读这篇笔记的时候遇到困难的话,建议您可以看看我之前的一些笔记,包括今年的UIKit Dynamics的介绍和去年的UICollectionView介绍

话不多说,我们开工。首先准备一个UICollectionViewFlowLayout的子类(在这里叫做VVSpringCollectionViewFlowLayout),然后在ViewController中用这个layout实现一个简单的collectionView:

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //ViewController.m  
  2.   
  3. @interface ViewController ()<UICollectionViewDataSource, UICollectionViewDelegate>  
  4. @property (nonatomicstrongVVSpringCollectionViewFlowLayout *layout;  
  5. @end  
  6.   
  7. static NSString *reuseId = @"collectionViewCellReuseId";  
  8.   
  9. @implementation ViewController  
  10. - (void)viewDidLoad  
  11. {  
  12.     [super viewDidLoad];  
  13.   // Do any additional setup after loading the view, typically from a nib.  
  14.   
  15.   self.layout = [[VVSpringCollectionViewFlowLayout alloc] init];  
  16.     self.layout.itemSize = CGSizeMake(self.view.frame.size.width44);  
  17.     UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:self.layout];  
  18.   
  19.     collectionView.backgroundColor = [UIColor clearColor];  
  20.   
  21.     [collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseId];  
  22.   
  23.     collectionView.dataSource = self;  
  24.     [self.view insertSubview:collectionView atIndex:0];  
  25. }  
  26.   
  27. #pragma mark - UICollectionViewDataSource  
  28. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section  
  29. {  
  30.     return 50;  
  31. }  
  32.   
  33. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath  
  34. {  
  35.     UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseId forIndexPath:indexPath];  
  36.   
  37.     //Just give a random color to the cell. See https://gist.github.com/kylefox/1689973  
  38.     cell.contentView.backgroundColor = [UIColor randomColor];  
  39.     return cell;  
  40. }  
  41. @end  

这部分没什么可以多说的,现在我们有一个标准的FlowLayout的UICollectionView了。通过使用UICollectionViewFlowLayout的子类来作为开始的layout,我们可以节省下所有的初始cell位置计算的代码,在上面代码的情况下,这个collectionView的表现和一个普通的tableView并没有太大不同。接下来我们着重来看看要如何实现弹性的layout。对于弹性效果,我们需要的是连接一个item和一个锚点间弹性连接的UIAttachmentBehavior,并能在滚动时设置新的锚点位置。我们在scroll的时候,只要使用UIKit Dynamics的计算结果,替代掉原来的位置更新计算(其实就是简单的scrollView的contentOffset的改变),就可以模拟出弹性的效果了。

首先在-prepareLayout中为cell添加UIAttachmentBehavior

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //VVSpringCollectionViewFlowLayout.m  
  2. @interface VVSpringCollectionViewFlowLayout()  
  3. @property (nonatomicstrongUIDynamicAnimator *animator;  
  4. @end  
  5.   
  6. @implementation VVSpringCollectionViewFlowLayout  
  7. //...  
  8.   
  9. -(void)prepareLayout {  
  10.     [super prepareLayout];  
  11.   
  12.     if (!_animator) {  
  13.         _animator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self];  
  14.         CGSize contentSize = [self collectionViewContentSize];  
  15.         NSArray *items = [super layoutAttributesForElementsInRect:CGRectMake(00, contentSize.width, contentSize.height)];  
  16.   
  17.         for (UICollectionViewLayoutAttributes *item in items) {  
  18.             UIAttachmentBehavior *spring = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:item.center];  
  19.   
  20.             spring.length = 0;  
  21.             spring.damping = 0.5;  
  22.             spring.frequency = 0.8;  
  23.   
  24.             [_animator addBehavior:spring];  
  25.         }  
  26.     }  
  27. }  
  28. @end  

prepareLayout将在CollectionView进行排版的时候被调用。首先当然是call一下super的prepareLayout,你肯定不会想要全都要自己进行设置的。接下来,如果是第一次调用这个方法的话,先初始化一个UIDynamicAnimator实例,来负责之后的动画效果。iOS7 SDK中,UIDynamicAnimator类专门有一个针对UICollectionView的Category,以使UICollectionView能够轻易地利用UIKit Dynamics的结果。在 UIDynamicAnimator.h 中能够找到这个Category:

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @interface UIDynamicAnimator (UICollectionViewAdditions)  
  2.   
  3. // When you initialize a dynamic animator with this method, you should only associate collection view layout attributes with your behaviors.  
  4. // The animator will employ thecollection view layout’s content size coordinate system.  
  5. - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout*)layout;  
  6.   
  7. // The three convenience methods returning layout attributes (if associated to behaviors in the animator) if the animator was configured with collection view layout  
  8. - (UICollectionViewLayoutAttributes*)layoutAttributesForCellAtIndexPath:(NSIndexPath*)indexPath;  
  9. - (UICollectionViewLayoutAttributes*)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;  
  10. - (UICollectionViewLayoutAttributes*)layoutAttributesForDecorationViewOfKind:(NSString*)decorationViewKind atIndexPath:(NSIndexPath *)indexPath;  
  11.   
  12. @end  

于是通过 -initWithCollectionViewLayout: 进行初始化后,这个UIDynamicAnimator实例便和我们的layout进行了绑定,之后这个layout对应的attributes都应该由绑定的UIDynamicAnimator的实例给出。就像下面这样:

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //VVSpringCollectionViewFlowLayout.m  
  2. @implementation VVSpringCollectionViewFlowLayout  
  3.   
  4. //...  
  5.   
  6. -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {  
  7.     return [_animator itemsInRect:rect];  
  8. }  
  9.   
  10. -(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {  
  11.     return [_animator layoutAttributesForCellAtIndexPath:indexPath];  
  12. }  
  13. @end  

让我们回到-prepareLayout方法中,在创建了UIDynamicAnimator实例后,我们对于这个layout中的每个attributes对应的点,都创建并添加一个添加一个UIAttachmentBehavior(在iOS7 SDK中,UICollectionViewLayoutAttributes已经实现了UIDynamicItem接口,可以直接参与UIKit Dynamic的计算中去)。创建时我们希望collectionView的每个cell就保持在原位,因此我们设定了锚点为当前attribute本身的center。

接下来我们考虑滑动时的弹性效果的实现。在系统的信息app中,我们可以看到弹性效果有两个特点:

  • 随着滑动的速度增大,初始的拉伸和压缩的幅度将变大
  • 随着cell距离屏幕触摸位置越远,拉伸和压缩的幅度

对于考虑到这两方面的特点,我们所期望的滑动时的各cell锚点的变化应该是类似这样的:



现在我们来实现这个锚点的变化。既然都是滑动,我们是不是可以考虑在UIScrollView的–scrollViewDidScroll:委托方法中来设定新的Behavior锚点值呢?理论上来说当然是可以的,但是如果这样的话我们大概就不得不面临着将刚才的layout实例设置为collectionView的delegate这样一个事实。但是我们都知道layout应该做的事情是给collectionView提供必要的布局信息,而不应该负责去处理它的委托事件。处理collectionView的回调更恰当地应该由处于collectionView的controller层级的类来完成,而不应该由一个给collectionView提供数据和信息的类来响应。在UICollectionViewLayout中,我们有一个叫做-shouldInvalidateLayoutForBoundsChange:的方法,每次layout的bounds发生变化的时候,collectionView都会询问这个方法是否需要为这个新的边界和更新layout。一般情况下只要layout没有根据边界不同而发生变化的话,这个方法直接不做处理地返回NO,表示保持现在的layout即可,而每次bounds改变时这个方法都会被调用的特点正好可以满足我们更新锚点的需求,因此我们可以在这里面完成锚点的更新。

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //VVSpringCollectionViewFlowLayout.m  
  2. @implementation VVSpringCollectionViewFlowLayout  
  3.   
  4. //...  
  5.   
  6. -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {  
  7.     UIScrollView *scrollView = self.collectionView;  
  8.     CGFloat scrollDelta = newBounds.origin.y - scrollView.bounds.origin.y;  
  9.   
  10.     //Get the touch point  
  11.     CGPoint touchLocation = [scrollView.panGestureRecognizer locationInView:scrollView];  
  12.   
  13.     for (UIAttachmentBehavior *spring in _animator.behaviors) {  
  14.         CGPoint anchorPoint = spring.anchorPoint;  
  15.   
  16.         CGFloat distanceFromTouch = fabsf(touchLocation.y - anchorPoint.y);  
  17.         CGFloat scrollResistance = distanceFromTouch / 500;  
  18.   
  19.         UICollectionViewLayoutAttributes *item = [spring.items firstObject];  
  20.         CGPoint center = item.center;  
  21.   
  22.       //In case the added value bigger than the scrollDelta, which leads an unreasonable effect  
  23.         center.y += (scrollDelta > 0) ? MIN(scrollDelta, scrollDelta * scrollResistance)  
  24.                                       : MAX(scrollDelta, scrollDelta * scrollResistance);  
  25.         item.center = center;  
  26.   
  27.         [_animator updateItemUsingCurrentState:item];  
  28.     }  
  29.     return NO;  
  30. }  
  31.   
  32. @end  

首先我们计算了这次scroll的距离scrollDelta,为了得到每个item与触摸点的之间的距离,我们当然还需要知道触摸点的坐标touchLocation。接下来,可以根据距离对每个锚点进行设置了:简单地计算了原来锚点与触摸点之间的距离distanceFromTouch,并由此计算一个系数。接下来,对于当前的item,我们获取其当前锚点位置,然后将其根据scrollDelta的数值和刚才计算的系数,重新设定锚点的位置。最后我们需要告诉UIDynamicAnimator我们已经完成了对冒点的更新,现在可以开始更新物理计算,并随时准备collectionView来取LayoutAttributes的数据了。

也许你还没有缓过神来?但是我们确实已经做完了,让我们来看看实际的效果吧:


当然,通过调节dampingfrequencyscrollResistance的系数等参数,可以得到弹性不同的效果,比如更多的震荡或者更大的幅度等等。

这个layout实现起来非常简单,我顺便封装了一下放到了Github上,大家有需要的话可以点击这里下载并直接使用。


转自:http://www.onevcat.com/2013/09/spring-list-like-ios7-message/

内容概要:本文提出了一种基于改进扩散模型的高海拔地区新能源高波动出力场景生成方法,并提供了完整的Python代码实现。该方法针对高海拔地区风能、光伏等新能源出力波动剧烈、不确定性高的特点,通过优化扩散模型的结构与训练策略,有效捕捉历史数据的概率分布特征与时序相关性,从而生成高质量、多样化的出力场景。文中详细阐述了模型的数学推导、网络架构设计、损失函数优化及采样算法改进,并通过实验证明其在拟合精度、场景多样性与稳定性方面优于传统生成模型,为电力系统在高比例新能源接入下的规划、调度与风险评估提供了可靠的场景输入支持。; 适合人群:具备一定Python编程能力和机器学习基础,从事新能源发电预测、电力系统分析、智能优化、场景生成等方向研究的科研人员、高校研究生及工程技术人员。; 使用场景及目标:①用于高海拔地区风电、光伏出力的不确定性建模与多场景生成;②支撑含高渗透率新能源的电力系统随机优化调度、鲁棒决策与风险评估;③为相关学术研究、论文复现与算法改进提供可运行的技术方案与代码基础; 阅读建议:建议读者结合所提供的完整资源(代码、数据集、说明文档)进行实践操作,重点关注扩散模型的前向加噪与反向去噪过程的设计细节,以及如何将其适配于新能源时序数据的生成任务,通过参数调优与对比实验深入理解模型的生成机制与性能边界。
内容概要:本文围绕基于静态约束法的配电网电动汽车接入容量评估展开研究,提出了一种在新型电力系统背景下评估主动配电网对电动汽车承载能力的方法。研究通过构建数学模型,结合潮流计算与关键约束条件(如电压越限、线路过载等),量化分析配电网可承受的最大电动汽车充电负荷容量,旨在识别规模化电动汽车接入带来的潜在运行风险,并为电网规划与运行提供科学依据。文中配套提供了完整的Matlab代码实现,便于仿真验证与结果复现。此外,该研究与分布光伏承载力评估、电动汽车可调能力分析等方向形成技术联动,展现了多主题协同的研究体系。; 适合人群:具备电力系统分析基础理论知识及Matlab编程能力的高校研究生、科研机构研究人员,以及从事新能源并网、智能配电网规划与运行等相关领域的工程技术人员。; 使用场景及目标:①用于学术研究中的模型复现与论文撰写支撑;②评估实际配电网中电动汽车大规模接入的可行性与安全边界,指导充电基础设施布局;③作为高校教学案例,帮助学生深入理解电网承载力评估的核心原理、建模方法与仿真技术; 阅读建议:建议结合文中提及的相关研究方向(如二阶锥规划、多面体聚合方法等)进行对比学习,充分利用所提供的Matlab代码与网盘资料开展仿真实验,重点关注约束条件的设定逻辑与潮流计算模块的实现细节,以深化对评估模型机理与工程应用价值的理解。
内容概要:本文围绕“考虑隐私保护的分布联邦学习电力负荷预测研究”展开,提出了一种基于Python实现的联邦学习框架,旨在解决居民或行业电力负荷预测中用户电表数据隐私泄露的风险。该研究通过构建分布机器学习模型,使各参与方在不共享原始数据的前提下协同训练全局模型,有效实现了数据“可用不可见”。文中详细阐述了联邦学习的整体架构设计、本地模型训练流程、参数加密传输与安全聚合机制,并结合差分隐私等技术进一步增强系统的隐私保护能力。同时,研究利用真实电力负荷数据集进行了实验验证,展示了方法在预测精度与隐私保障之间的良好平衡,并提供了完整的代码实例与复现指南,便于后续研究与应用拓展。; 适合人群:具备一定机器学习基础和电力系统背景知识,从事智慧能源、隐私计算或人工智能相关方向研究的研究生、科研人员及工程技术人员。; 使用场景及目标:① 实现跨区域、跨主体的电力负荷协同预测,打破数据孤岛;② 在确保用户用电数据隐私安全的前提下提升负荷预测准确性;③ 推动联邦学习在智能电网、需求响应、虚拟电厂等场景中的实际部署与应用。; 阅读建议:建议结合文中提供的Python代码与网盘资料进行动手实践,重点关注联邦学习的通信轮次设计、模型聚合算法(如FedAvg)的实现细节以及差分隐私噪声添加策略,深入理解其对模型性能与隐私强度的影响,为进一步优化与创新奠定基础。
VDA_Band_19.1_3rd edition_2026 English Inspection of Technical Cleanliness 内容概要:本文档为德国汽车工业协会(VDA)发布的第三版《技术清洁度检验:功能相关汽车部件的颗粒污染检测》(VDA 19.1),系统规范了汽车行业中零部件技术清洁度的检测方法与流程。文件涵盖从取样、提取、过滤到分析的全流程标准化操作,重点更新了干法提取(如 Stamp Test 和刷吸法)、小于50µm颗粒的检测、光学子系统和SEM/EDX标准分析方法,并引入统一材料分类体系以提升结果可比性。同时明确了“标准分析”与“自由检验”的区别,前者用于高兼容性检测,后者允许客户与供应商协商定制参数。文档还强化了对非可测组件的技术清洁保障、测量不确定度评估及方法验证的要求,并提供了多个实际案例支持应用落地。; 适合人群:适用于汽车制造业中从事质量控制、工艺开发、供应商管理及相关检测实验室的技术人员和管理人员,尤其适合具备一定质量管理或洁净度检测基础的专业人员。; 使用场景及目标:①用于制定和执行零部件清洁度检测标准;②指导 incoming/outgoing 检验及生产过程监控;③支持失效分析与质量改进项目;④作为企业内部审核和技术交流的依据; 阅读建议:建议结合VDA 19.2及其他相关标准配套使用,重点关注各章节中的起始参数设定、方法选择逻辑及附录中的检查表示例,在实际操作中同步开展方法验证与人员培训,确保检测结果的有效性和可追溯性。
内容概要:本研究聚焦于电动汽车(EV)作为灵活可调资源的能力评估,提出了一种基于多面体聚合与闵可夫斯基和的数学建模方法,用以精确刻画大规模电动汽车集群的可调能力范围。通过将单辆电动汽车的充放电行为抽象为几何空间中的多面体可行域,并利用闵可夫斯基和实现对群体可行域的高效聚合,从而构建出能够反映整体调节潜力的统一数学表达。该方法在Matlab平台上实现了完整的仿真代码,支持对不同场景下电动汽车集群的上、下调能力进行量化评估,为电网调度、需求响应及电力市场机制设计提供了理论依据与工具支撑。; 适合人群:具备电力系统分析、优化理论及Matlab编程基础的研究生、科研人员及从事新能源、智能电网相关工作的工程师。; 使用场景及目标:①用于评估大规模电动汽车接入背景下系统的灵活性资源潜力;②支撑含高比例可再生能源的电力系统调度与规划决策;③为需求响应项目中EV聚合商参与电力市场提供能力申报的技术手段;④作为教学案例帮助理解集合运算在电力系统中的应用。; 阅读建议:学习者应结合Matlab代码深入理解多面体建模与闵可夫斯基和的实现细节,建议从单车模型入手逐步过渡到集群聚合,并尝试调整参数(如数量、SOC分布、功率限制)观察聚合结果的变化,以增强对模型特性的掌握。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值