、
关于下拉刷新和上拉加载,项目中一直使用MJRefresh(原先还用过EGOTableViewPullRefresh,MJRefresh更好用些),今天就分析下如何用Swift来实现这个功能。
关于如何下拉刷新和上拉加载,认识到两点就可以了:
0.UIScrollViewDelegate
1.UIScrollView中的contentInset
直白一点说,就是在UIScrollView及其子类在下拉或者上拉到一定的偏移量时,设置contentInset,固定住UIScrollView,显示动画加载页面,数据到达之后再恢复contentInset。
UIScrollView很强大,除了上面说的,我们有时用它来显示放大缩小图片。
今天来自定义一个下拉刷新视图,并实现上拉加载功能。
前些天,一篇公众号(Pinapps,里面的文章挺好)文章里推荐了一个iPhone上的RSS阅读器Unread,软件做的很棒,就把全部功能都买了下来(程序内购买)。其中里面的下拉刷新效果挺棒的,正好仿一下:
分析下这里面的效果:
一开始线条是隐藏的,有了偏移后,线条从原先的窄线条慢慢变成宽线条线条,其中线条的颜色和REFRESH颜色也是有变化的。为了能做出连续性的动画我们使用POP(如果不是很了解POP的话,可以看一下我写的这篇文章)
已传至Github(时间仓促,没有加以优化):
https://github.com/iYiming/MyDemo/tree/master/Weibo
下面是代码的一些注释:
前提:添加POP动画,创建一个单视图项目,在ViewController里面添加一个UITableView。
先来实现刷新视图:即那三条红线加上REFRESH。
我们创建Swift文件,继承自UIView并命名为RefreshView:
代码如下:
import UIKit
@IBDesignable class RereshView: UIView {
var topLayer: CALayer?//顶部线
var middleLayer: CALayer?//中间线
var bottomLayer: CALayer?//底部线
var textLayer: CATextLayer?//REFRESH文字
override init(frame: CGRect) {
super.init(frame: frame)
setup()//设置
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//fatalError("init(coder:) has not been implemented")
setup()//设置
}
/**
设置
*/
func setup(){
//顶部线
topLayer = CALayer()
topLayer!.backgroundColor = UIColor(red: 200/255.0, green: 200/255.0, blue: 200/255.0, alpha: 1.0).CGColor
topLayer!.frame = CGRectMake(98, 0, 4, 4)
topLayer!.cornerRadius = 2
self.layer.addSublayer(topLayer)
//中间线
middleLayer = CALayer()
middleLayer!.backgroundColor = UIColor(red: 200/255.0, green: 200/255.0, blue: 200/255.0, alpha: 1.0).CGColor
middleLayer!.frame = CGRectMake(98, 14, 4, 4)
middleLayer!.cornerRadius = 2
self.layer.addSublayer(middleLayer)
//底部线
bottomLayer = CALayer()
bottomLayer!.backgroundColor = UIColor(red: 200/255.0, green: 200/255.0, blue: 200/255.0, alpha: 1.0).CGColor
bottomLayer!.frame = CGRectMake(98, 28, 4, 4)
bottomLayer!.cornerRadius = 2
self.layer.addSublayer(bottomLayer)
//REFRESH
textLayer = CATextLayer()
textLayer!.foregroundColor = UIColor(red: 64/255.0, green: 64/255.0, blue: 64/255.0, alpha: 1.0).CGColor
textLayer!.fontSize = 10
textLayer!.contentsScale = UIScreen.mainScreen().scale
textLayer!.string = "REFRESH"
textLayer!.opacity = 0
textLayer!.frame = CGRectMake(75, 62, 50, 20)
self.layer.addSublayer(textLayer)
}
}
注意我们在Class前面加了一个@IBDesignable,这样我们就能在Storyboard中查看我们自定义的视图(如果想详细了解的话,请看下Onevcat的可视化开发,IB新时代)。
在ViewController.swift中我们添加了如下代码:
import UIKit
class ViewController: UIViewController,UITableViewDataSource,UIScrollViewDelegate {
@IBOutlet weak var tableView: UITableView!//列表视图
@IBOutlet weak var refreshView: RereshView!//下拉刷新视图
@IBOutlet weak var refreshViewTopLayoutConstraint: NSLayoutConstraint!//下拉刷新居上的约束
var showBottomLayer:Bool = true//显示刷新视图的底部线 变宽
var hiddenBottomLayer:Bool = true//隐藏刷新视图的底部线 即变窄
var showMiddleLayer:Bool = true//显示刷新视图的中间线 变宽
var hiddenMiddleLayer:Bool = true//隐藏刷新视图的中间线 变窄
var showTopLayer:Bool = true//显示刷新头部的底部线 变宽
var hiddenTopLayer:Bool = true//隐藏刷新视图的头部线 变窄
var refreshing = false//是否在刷新中
var loadingAcitivityIndicatorView: UIActivityIndicatorView?//上拉加载指示菊花
var showFooterLoadingView:Bool = false//是否显示上拉加载视图
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
settingUI()//设置界面
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//设置界面
func settingUI(){
let screenWidth = CGRectGetWidth(UIScreen.mainScreen().bounds)
let screenHeight = CGRectGetHeight(UIScreen.mainScreen().bounds)
loadingAcitivityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
loadingAcitivityIndicatorView?.hidesWhenStopped = true
loadingAcitivityIndicatorView?.frame = CGRectMake((screenWidth - 20)/2.0,screenHeight - 40, 20, 20)
self.view.addSubview(loadingAcitivityIndicatorView!)
}
/**
添加size动画
:param: layer 要添加动画的layer
:param: size 动画到的size
*/
func addSizeAnimation(layer: CALayer,size: CGSize){
var springAnimation = POPSpringAnimation(propertyNamed: kPOPLayerSize)
springAnimation.toValue = NSValue(CGSize: size);
springAnimation.springBounciness = 18
layer.pop_addAnimation(springAnimation, forKey: "layerSpringAnimation")
}
/**
添加position动画
:param: layer 要添加动画的layer
:param: position 动画到的position
*/
func addPositionAnimation(layer: CALayer,position: CGPoint){
var springAnimation = POPSpringAnimation(propertyNamed: kPOPLayerPosition)
springAnimation.toValue = NSValue(CGPoint: position);
springAnimation.springBounciness = 18
layer.pop_addAnimation(springAnimation, forKey: "layerSpringAnimation")
}
//UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
//UIScrollViewDelegate
//将要开始减速时
func scrollViewWillBeginDecelerating(scrollView: UIScrollView) {
var contentOffsetY = scrollView.contentOffset.y
if contentOffsetY > 0{
return
}
if contentOffsetY < -52{//偏移 52 时 我们显示加载视图
refreshing = true
scrollView.contentInset = UIEdgeInsetsMake(62.0, 0.0, 0.0, 0.0)
refreshViewTopLayoutConstraint.constant = 20//刷新视图居上约束
//移除POP动画
refreshView.topLayer?.pop_removeAllAnimations()
refreshView.middleLayer?.pop_removeAllAnimations()
refreshView.bottomLayer?.pop_removeAllAnimations()
//设定layer位置
refreshView.topLayer?.frame = CGRectMake(121, 0, 4, 4)
refreshView.middleLayer?.frame = CGRectMake(98, 14, 4, 4)
refreshView.bottomLayer?.frame = CGRectMake(75, 28, 4, 4)
refreshView.textLayer?.opacity = 0
self.addPositionAnimation(self.refreshView.topLayer!,position: CGPointMake(121, 14))
self.addPositionAnimation(self.refreshView.middleLayer!,position: CGPointMake(98, 14))
self.addPositionAnimation(self.refreshView.bottomLayer!,position: CGPointMake(75, 14))
let delayTime = dispatch_time(DISPATCH_TIME_NOW,Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime,dispatch_get_main_queue()) {
UIView.animateWithDuration(0.3, animations: { () -> Void in
scrollView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0)
self.refreshViewTopLayoutConstraint.constant = -52
}, completion: { (Bool) -> Void in
self.refreshing = false
self.refreshView.topLayer!.frame = CGRectMake(98, 0, 4, 4)
self.refreshView.middleLayer!.frame = CGRectMake(98, 14, 4, 4)
self.refreshView.bottomLayer!.frame = CGRectMake(98, 28, 4, 4)
})
}
}
}
//滚动时
func scrollViewDidScroll(scrollView: UIScrollView) {
var contentOffsetY = scrollView.contentOffset.y
if contentOffsetY <= 0 {
if refreshing{
return;
}
self.refreshView.hidden = false
refreshViewTopLayoutConstraint.constant = -contentOffsetY - 52
if contentOffsetY < -30{
self.hiddenBottomLayer = true
if showBottomLayer{
showBottomLayer = false
self.addSizeAnimation(refreshView.bottomLayer!, size: CGSizeMake(50, 4));
refreshView.bottomLayer?.backgroundColor = UIColor(red: 64/255.0, green: 64/255.0, blue: 64/255.0, alpha: 1.0).CGColor
}
if contentOffsetY < -46{
self.hiddenMiddleLayer = true
if showMiddleLayer{
showMiddleLayer = false
self.addSizeAnimation(refreshView.middleLayer!, size: CGSizeMake(50, 4));
refreshView.middleLayer?.backgroundColor = UIColor(red: 64/255.0, green: 64/255.0, blue: 64/255.0, alpha: 1.0).CGColor
}
if contentOffsetY < -62{
self.hiddenTopLayer = true
if showTopLayer{
showTopLayer = false
self.addSizeAnimation(refreshView.topLayer!, size: CGSizeMake(50, 4));
refreshView.topLayer?.backgroundColor = UIColor(red: 192/255.0, green: 47/255.0, blue: 46/255.0, alpha: 1.0).CGColor
refreshView.middleLayer?.backgroundColor = UIColor(red: 192/255.0, green: 47/255.0, blue: 46/255.0, alpha: 1.0).CGColor
refreshView.bottomLayer?.backgroundColor = UIColor(red: 192/255.0, green: 47/255.0, blue: 46/255.0, alpha: 1.0).CGColor
refreshView.textLayer?.foregroundColor = UIColor(red: 192/255.0, green: 47/255.0, blue: 46/255.0, alpha: 1.0).CGColor
}
}else{
self.showTopLayer = true
if hiddenTopLayer{
hiddenTopLayer = false
self.addSizeAnimation(refreshView.topLayer!, size: CGSizeMake(4, 4));
refreshView.topLayer?.backgroundColor = UIColor(red: 200/255.0, green: 200/255.0, blue: 200/255.0, alpha: 1.0).CGColor
refreshView.middleLayer?.backgroundColor = UIColor(red: 64/255.0, green: 64/255.0, blue: 64/255.0, alpha: 1.0).CGColor
refreshView.bottomLayer?.backgroundColor = UIColor(red: 64/255.0, green: 64/255.0, blue: 64/255.0, alpha: 1.0).CGColor
refreshView.textLayer?.foregroundColor = UIColor(red: 64/255.0, green: 64/255.0, blue: 64/255.0, alpha: 1.0).CGColor
}
}
}else{
self.showMiddleLayer = true
if hiddenMiddleLayer{
hiddenMiddleLayer = false
self.addSizeAnimation(refreshView.middleLayer!, size: CGSizeMake(4, 4));
refreshView.middleLayer?.backgroundColor = UIColor(red: 200/255.0, green: 200/255.0, blue: 200/255.0, alpha: 1.0).CGColor
}
}
}else{
self.showBottomLayer = true
if hiddenBottomLayer{
hiddenBottomLayer = false
self.addSizeAnimation(refreshView.bottomLayer!, size: CGSizeMake(4, 4));
refreshView.bottomLayer?.backgroundColor = UIColor(red: 200/255.0, green: 200/255.0, blue: 200/255.0, alpha: 1.0).CGColor
}
}
refreshView.textLayer?.opacity = Float(-contentOffsetY/62.0)
}else{
self.refreshView.hidden = true
let screenWidth = CGRectGetWidth(UIScreen.mainScreen().bounds)
}
if scrollView.contentOffset.y + scrollView.frame.size.height < scrollView.contentSize.height{
loadingAcitivityIndicatorView?.stopAnimating()
tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
showFooterLoadingView = false
}else{
tableView.contentInset = UIEdgeInsetsMake(0, 0, 60, 0);
loadingAcitivityIndicatorView?.startAnimating()
showFooterLoadingView = true;
//添加上拉加载数据代码
//...
}
}
}
需要注明的是:
其中下拉刷新视图和上拉加载视图的父级视图是ViewController.view。