实现UIRefreshControl

前几天我在和朋友聊天,他提到了一些很有趣的事情。 他正在一个项目中,他们根本不使用任何开源代码(即,没有直接依赖项)。 这让我开始思考,如果我在相同的约束下运作,我的项目会是什么样子。 因此,当我最近被要求实施“按需刷新”功能时,我认为我可以在不咨询开源霸主的情况下试一试。

总览

实现可刷新的样式刷新控件,该控件可以附加到UITableView上,而不会泄漏太多实现细节。 需要使用可响应状态更改的文本和图像进行自定义。

源代码可以在这里找到。

由于很快就把它放在一起,所以还没有经过充分的测试,但是似乎可以正常工作。 如果您有任何改进方法,请告诉我。

第一站,UIRefreshControl

我在这里写了很长的篇幅谈论股票更新控制,但是我看到了它们的利弊时选择了:

  • +易于使用,可直接放入解决方案
  • –难以定制
  • –非全萤幕检视画面上的视觉错误,例如跳动的动画

如果您对股票动画没问题,正在使用全尺寸的表格视图以及使用UITableViewController,那么这将是一个完美的控件。 不幸的是,这些都不适用于我试图做的事情。 在使用UIRefreshControl进行了一些修改之后,我放弃了自己的程序并推出了自己的程序。

我真正喜欢的是Apple的刷新控制界面。 它们仅公开一个动作(UIControlEventValueDidChange)和一组方法(beginRefreshing和endRefreshing)。 这使得管理表视图编码时如何与控件交互以及将所有实现精巧地保留在刷新控件本身中变得非常容易。 因此,我从他们的设计中得到了启发,以启发我自己的版本。 但是在我们开始之前

UIRefreshControl如何工作?

UIRefreshControl的核心极其简单,但与其他所有细节一样,魔鬼也是如此。 我无法确切地说出苹果实施的工作原理,但我必须想象流程如下。 要记住的关键是我们将使用tableView子类的scrollView —因此,当我在下面引用tableView时,它实际上是其scrollView超类的属性。

  • 观察tableView内容的偏移量
  • 当contentOffset.y> pullDistance时,触发刷新
  • 调整tableView contentInset和contentOffset以解决以下状态:
  • 默认值(用户完全没有与tableview进行交互)
  • 拉(用户已拖动表格视图,并且控件尚未完全显示)
  • 拉动(用户已拉动表格视图,并且控件已完全显示,但尚未达到刷新距离)
  • 拉动和刷新(用户已拉出足够的距离以达到刷新距离)
  • 已发布(用户在刷新距离之前已释放表)
  • 已发布(用户在刷新距离后已释放表)

我们实际上并不会专门针对所有这些状态进行编程,但是最好记住控件将负责处理哪些状态,并为我们提供如何对这些状态做出反应的良好框架。

另一个有趣的地方是来自Apple的文档:“拥有刷新控件的UITableViewController对象还负责设置该控件的框架矩形。 因此,您不需要直接在视图层次结构中管理刷新控件的大小或位置。”因此,我们将尝试做同样的事情,并使它对于最终视图控制器尽可能地无缝。

自定义刷新控件

设置此控件将是了解滚动视图的contentOffset和contentInset如何工作的问题,然后如上所述适当地调整为不同的状态。 让我们遍历每种状态并了解视图的外观,以便我们对需要做的事情有很好的了解。

内容偏移

苹果将​​其描述为“内容视图的原点与滚动视图的原点之间的偏移量”,这实际上是很有意义的。 contentOffset只是任何滚动视图上的一个属性,它是一个精确描述了此CGPoint的属性。 contentOffset可以告诉我们用户将滚动视图从其原始位置拖动(或偏移)了多远。 该点将从(0,0)开始,并在您向下拖动滚动视图时,偏移量将开始增大(0,-1),(0,-2)。一直到用户拖动的程度。 回想一下,负数表示用户在视图上向下拖动。

因此,这样做的目的是提供一种简单的方法来查看滚动视图被拖动到多远,并且一旦有了,我们就可以通过执行类似这样的操作来响应contentOffset.y。 我们使pullDistance保持正值,以便以后使用时更容易。

  -(void)scrollViewDidScroll:(UIScrollView *)scrollView 
{
CGFloat pullDistance = MAX(0.0,-scrollView.contentOffset.y);
//在这里回应pullDistance
...
}

大。

内容插图

苹果将​​其描述为“内容视图与封闭的滚动视图之间的距离。”再次,另一个很好的描述。 在深入解释之前,需要快速做的一件事是,contentInset是任何滚动视图上的一个属性,它是UIEdgeInsets,它只是一个具有4个值,描述4个距离的结构的奇特名称。 它与CGRect相似,只是它不描述原点和大小,而是描述视图插入的量。 这是结构定义:

  typedef struct {CGFloat顶部,左侧,底部,右侧;  } UIEdgeInsets; 

因此,如果我们假设屏幕的高度为500点(忽略宽度,因为我们的表视图将为全角,但宽度也是如此),并且内容视图的高度为1000点,默认contentInset为0(0 ,0,0,0(又名UIEdgeInsetsZero),那么我们将首先在滚动视图中显示contentView的上半部分。 然后,用户可以向上拖动以浏览内容的下半部分。

那么,如果我们想要在内容视图的前50个点中隐藏一些特殊内容,并且希望将其隐藏在默认视点之上怎么办? 好了,我们可以通过向滚动视图添加contentInset(50,0,0,0)轻松实现这一点。 现在默认显示将显示我们的contentView的50–550,用户可以向上拖动以查看550–1000,向下拖动以查看0–50。 因为我们告诉滚动视图这应该是一个插图,所以当用户停止拖动时,它也会很好地反弹并将内容视图放回0.50。

要记住的重要一件事是contentOffset是一个“实时”属性,当滚动滚动视图时,它将不断更新,而contentInset是“惰性”,因为它描述了一个插入点,除非告知,否则不要更改。

在我们回到讨论不同状态之前,请快速注意一下。 我们将通过刷新控件中的两种方法来完成大部分工作:

  -(void)scrollViewDidScroll:(UIScrollView *)scrollView; 
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView;

我将它们设置为与UIScrollView委托方法非常相似,因此我们可以将所有滚动事件从表视图的控制器转发到刷新控件,而不必在刷新控件中劫持表视图的委托属性。

默认

我们希望将视图隐藏在可见区域上方。 可以通过将初始帧设置如下来轻松实现:

  self = [self initWithFrame:CGRectMake(0,-50.0,tableView.bounds.size.width,50.0)]]; 

拉(contentOffset.y <50)

我们不需要在这里做任何事情,因为如果用户停止在此范围内拖动,我们希望视图回弹,因为我们的框架原点是-50.0。

拉(contentOffset.y> 50,contentOffset.y <refreshDistance)

我们希望视图保留在屏幕顶部,但仍使用户能够继续拉动以达到刷新距离

最简单的几种方法是通过将框架rect设置为具有新pullDistance的y坐标来调整视图的原点。 它看起来像这样:

  CGRect框架= self.frame; 
如果(pullDistance> frame.size.height){
frame.origin.y = -pullDistance;
}
self.frame =帧;

拉(contentOffset.y> = refreshDistance)

现在,除了在scrollViewDidEndDragging内部侦听并触发刷新之外,我们实际上不需要在这里做任何新的事情。 重要的是要跟踪我们是否正在刷新,而不是尝试刷新两次(如果它们先拉然后快速再拉),并且还要更新我们的用户界面,以便标签文本可以指示他们是否在这里释放了东西。 在scrollViewDidEndDragging内部看起来像这样:

 如果(pullDistance> = self.refreshDistance){ 
如果(self.refreshing)返回;
[self sendActionsForControlEvents:UIControlEventValueChanged];
[自我开始刷新];
}

其余代码只是管理刷新控件的内部状态,并根据需要更新视图,还管理表视图的插入和偏移,因此我们使刷新控件在所有情况下均表现良好。 有很多小的情况需要考虑,但是大多数(希望)应该在我的代码中考虑,我将留给您阅读以了解其完成方式(提示:scrollViewDidScroll和scrollViewDidEndDragging内的更多工作) 。

那很有趣!

现在已经有了基础知识,您可以轻松地添加任何添加的视图元素。 只需了解刷新控件状态的变化,然后对视图元素进行适当的动画处理和插值即可。 您可以通过获取scrollView contentOffset并对状态变化做出反应,来做几乎可以想到的任何动画。 颜色可以改变,箭头可以旋转,图像视图可以进行动画处理和收缩/扩展等。

顺便说一句,我只能猜测Apple隐藏了诸如拉动距离之类的细节,因此使用此控件的应用将具有相似的外观,但对我而言似乎很愚蠢。 他们可以随时编辑并强烈劝阻我们不要更改默认值。 任何想改变它的自尊的开发人员都会想出一种方法-为什么不让我们的生活更轻松?

所以你有它。 现在,您和我可以看到为什么对我们的代码进行细粒度的控制( ..ahem .. )令人耳目一新。 认真地说,这是一个很好的解决方案,显示了仅使用Apple提供的基本UIKit元素即可轻松完成很多工作。

在测试此职位期间,没有任何开源控件受到损害。