# A custom pull-to-refresh control in Swift

Language: en
Canonical: [A custom pull-to-refresh control in Swift](<https://allluckly.cn/en/journal/2016-02-17-a-custom-pull-to-refresh-control-in-swift/>)
中文：[中文 HTML](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tuogao22>)
English: [English HTML](<https://allluckly.cn/en/journal/2016-02-17-a-custom-pull-to-refresh-control-in-swift/>)
Published: 2016-02-17T00:00:00+08:00
Modified: 2026-10-23T10:34:13+08:00
Author: 威權在B612等着那阵温柔的风

Creates a text view in a xib file to cover UIRefreshControl's default appearance, then rotates, recolours and scales the letters in stages. Simulated loading and row insertion connect the beginning and end of the custom refresh workflow.

Originally credited to 威權在B612等着那阵温柔的风; edited and archived by Bison.

[Source listed in the original](<http://www.jianshu.com/p/61094c18ffd9>)

English translation of the [Chinese original](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tuogao22>), translated on 2026-10-23. The article body uses automated translation; app descriptions, historical reading notes and video subtitles receive separate editorial checks. Original publication dates, attribution, code and version notes are retained.

**Before you read** This technical note dates from 2015–2017. The examples have not been rerun as part of this archive update. Published in 2016, using the Swift and NSTimer example syntax of that period.

Editor: [Bison](<https://allluckly.cn/en/>)  
 Contributor: [威權在B612等着那阵温柔的风](<http://www.jianshu.com/p/61094c18ffd9>)

> "Swift is accomplishing something remarkable: changing how we program Apple devices by introducing modern paradigms such as functional programming and richer type checking than a purely object-oriented language like Objective-C."

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/1.gif>)

### Introduction

This article will teach you:

- Custom pull-to-refresh
- Create the animation shown.
- Insert data into UITableView.

### Pull-to-refresh

First, set up a TableView like the one shown. Do that yourself; this article focuses on pull-to-refresh.

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/2.png>)

Connect the TableView outlet to ViewController as tblDemo.

```
@IBOutlet weak var tblDemo: UITableView!
```

Pull-to-refresh requires UIRefreshControl. Add a variable to viewController:

```
var refreshControl: UIRefreshControl! <br> 然后在viewDidLoad()添加：<br>

refreshControl = UIRefreshControl()
//设置背景颜色
refreshControl.backgroundColor = UIColor.redColor()
//设置菊花转的颜色
refreshControl.tintColor = UIColor.yellowColor()

//往tableView添加刷新控件
tblDemo.addSubview(refreshControl) <br>
```

Running it gives:

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/3.gif>)

Custom pull-to-refresh  
 The principle: UIRefreshControl is a UIView subclass, so add a view directly on top to cover the default spinning refresh indicator.

### 1. Create a view with a xib

First, create the custom view:

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/4.png>)

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/5.png>)

Name the file RefreshContents.  
 ![1](<https://allluckly.cn/images/blog/tuogao/tougao22/6.png>)

Then set the view's size in the right sidebar.

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/7.png>)

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/8.png>)

Then drag in each Label and set its constraints:

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/9.png>)

Give the Labels tags: first “我” is Tag 1, “是” is 2, and so on:

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/10.png>)

### 2. Cover UIRefreshControl's default view

```
//存储定制好的view和label
var customView: UIView!
var labelsArray: Array<UILabel> = []

func loadCustomRefreshContens(){
    //读取xib里的view
    let refreshContents = NSBundle.mainBundle().loadNibNamed("RefreshContents", owner: self, options: nil)

    customView = refreshContents[0] as! UIView
    //按照refreshControl的大小设置view的大小
    customView.frame = refreshControl.bounds

    //读取view 里 label 到labelsArray
    for var i=0; i < customView.subviews.count; ++i{
        labelsArray.append(customView.viewWithTag(i + 1) as! UILabel)
    }

    //覆盖默认view   
    refreshControl.addSubview(customView)
} <br> 接着在viewDidLoad()添加<br>

loadCustomRefreshContents() <br> 运行，下拉就能看到<br>
```

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/11.png>)

### 3. Add animation effects

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/12.gif>)

The animation can be broken into the following two steps:

- Starting with the first character, rotate it 45 degrees, change its color, then straighten it and turn it black. Repeat with the next character until the last one.
- Enlarge all the letters, then return them to normal.

First, define these variables in viewController:

```
//用来判断是否可以执行动画，防止动画未完时又启动了动画
var isAnimating = false

var currentColorIndex = 0

var currentLabelIndex = 0 <br> 第一步的实现如下：<br>

func animateRefreshStep1(){
    isAnimating = true

    UIView.animateWithDuration(0.1, delay: 0.0, options: .CurveLinear, animations: {
    self.labelsArray[self.currentLabelIndex].transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4))
    self.labelsArray[self.currentLabelIndex].textColor = self.getNextColor()


    }, completion: {(finished) -> Void in
        UIView.animateWithDuration(0.05, delay: 0.0, options: .CurveLinear, animations: {
        self.labelsArray[self.currentLabelIndex].transform = CGAffineTransformIdentity
        self.labelsArray[self.currentLabelIndex].textColor = UIColor.blackColor()

        }, completion: {(fin) -> Void in
            ++self.currentLabelIndex

            if self.currentLabelIndex < self.labelsArray.count{
                self.animateRefreshStep1()
            }
            else{
                self.animateRefreshStep2()
            }

        }
       )
    })
}

//返回下个颜色
func getNextColor() -> UIColor{
    var colorsArray: Array<UIColor> = [UIColor.magentaColor(),UIColor.brownColor(),UIColor.yellowColor(),UIColor.redColor(),UIColor.greenColor(),UIColor.blueColor(),UIColor.orangeColor()]
    if currentColorIndex == colorsArray.count{
        currentColorIndex = 0
    }

    let returnColor = colorsArray[currentColorIndex]
    ++currentColorIndex

    return returnColor
}
```

Implementation of the second step:

```
func animateRefreshStep2(){
    UIView.animateWithDuration(0.35, delay: 0.0, options: .CurveLinear, animations: {
        self.labelsArray[0].transform = CGAffineTransformMakeScale(1.5,1.5)
        self.labelsArray[1].transform = CGAffineTransformMakeScale(1.5,1.5)
        self.labelsArray[2].transform = CGAffineTransformMakeScale(1.5,1.5)
        self.labelsArray[3].transform = CGAffineTransformMakeScale(1.5,1.5)
        self.labelsArray[4].transform = CGAffineTransformMakeScale(1.5,1.5)
        self.labelsArray[5].transform = CGAffineTransformMakeScale(1.5,1.5)
        self.labelsArray[6].transform = CGAffineTransformMakeScale(1.5,1.5)


    }, completion: {(fin) -> Void in
    UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveLinear, animations: {
        self.labelsArray[0].transform = CGAffineTransformIdentity
        self.labelsArray[1].transform = CGAffineTransformIdentity
        self.labelsArray[2].transform = CGAffineTransformIdentity
        self.labelsArray[3].transform = CGAffineTransformIdentity
        self.labelsArray[4].transform = CGAffineTransformIdentity
        self.labelsArray[5].transform = CGAffineTransformIdentity
        self.labelsArray[6].transform = CGAffineTransformIdentity

    }, completion: {(fin) -> Void in
        //如果还未结束刷新则回到第一步再来一次
        if self.refreshControl.refreshing{
            self.currentLabelIndex = 0
            self.animateRefreshStep1()
        }else{
            //完成，设置为原始状态
            self.isAnimating  = false
            self.currentLabelIndex = 0
            for var i = 0; i<self.labelsArray.count; ++i{
                self.labelsArray[i].textColor = UIColor.blackColor()
                self.labelsArray[i].transform = CGAffineTransformIdentity
            }
        }
    })

    })
} <br> 接着就是设置当下拉刷新时启动动画：<br>

func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    if refreshControl.refreshing{
        if !isAnimating{
            animateRefreshStep1()
        }
    }
} <br> 运行就能见到动画效果了。<br>
```

### 4. Insert data and finish refreshing

To simulate loading network data, use NSTimer to introduce a 4-second delay. Then end refreshing and insert the data.

Define the following function:

```
func doSomething(){
    //4秒后触发endOfWork
    timer = NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: "endOfWork", userInfo: nil, repeats: true)
}

func endOfWork(){
    //结束下拉刷新
    refreshControl.endRefreshing()

    timer.invalidate()
    timer = nil

    //插入数据
    insertLoveData()
}

func insertLoveData(){
    let lastIndexpath = NSIndexPath(forRow: dataArray.count, inSection: 0)

    //往数据里添加新的数据
    dataArray.append("其实我也喜欢你")

    //更新tableView数据
    tblDemo.beginUpdates()
    tblDemo.insertRowsAtIndexPaths([lastIndexpath], withRowAnimation: .Middle)
    tblDemo.endUpdates()
}
```

Then insert doSomething into scrollViewDidEndDecelerating as follows:

```
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    if refreshControl.refreshing{
        if !isAnimating{
            doSomething()
            animateRefreshStep1()
        }
    }
} <br> 运行，如下效果：<br>
```

![1](<https://allluckly.cn/images/blog/tuogao/tougao22/1.gif>)

### Finally

It happened to be Valentine's Day when I wrote this, so I chose a fitting theme. Happy Singles-Torture Day! :)

#### (End And Thank U)

[Reference links](<http://www.appcoda.com/custom-pull-to-refresh/>)

---

Recommended reading: [Integrating LianLian Pay into an iOS app](<https://allluckly.cn/en/journal/2016-01-20-integrating-lianlian-pay-into-an-ios-app/>)

