Implement pull to refresh and infinite scrolling on Swift

    Take the article Meet Swift! , where it’s shown how to make a simple Swift application, and add some famous and useful things like pull to refresh and infinite scrolling using the built-in language features. To make it even more interesting, add a bit of asynchrony, otherwise the application will freeze each time during the update.



    Training


    As a basis, we take the above example, so we will just supplement it. First, add 2 variables to the controller class, which will be responsible for the number of cells and for the text displayed in the cells
    class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
        var countRow = 20
        var text = "Habrapost"

    And modify the cell generation code using these variables
        func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int
        {
            return countRow
        }
        func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!
        {
            let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "MyTestSwiftCell")
            cell.text = "\(text) \(indexPath.row)"
            cell.detailTextLabel.text = "Hi, \(indexPath.row)"
            cell.detailTextLabel.textColor = UIColor.purpleColor()
            return cell
        }

    Now we will bind the TableView to the controller so that we can make the necessary manipulations on it. In the interface builder, select the TableView, press cmd + alt + enter and right-click on the window that appears.


    Drive the name we will use. We


    also add the View component to the TableView, where we will place 2 elements to get the following.


    This View is needed to display a notification that an update is in progress, and we need it to be visible only when new data is being downloaded (for this we will use the tableFooterView.hidden property), so we need to hide it at the beginning, and only show it later. You will also need to manually start the animation UIActivityIndicatorView, for this, similarly as above, add the binding




    For preliminary preparation of these actions will be enough.

    Pull to refresh


    Now you can go directly to the implementation of pull to refresh. Add a new variable of the special class UIRefreshControl to the controller class
        var refreshControl:UIRefreshControl!

    In viewDidLoad, add code that initializes this variable and binds it to tableView
        override func viewDidLoad() {
            super.viewDidLoad()
            refreshControl = UIRefreshControl()
            refreshControl.attributedTitle = NSAttributedString(string: "Идет обновление...")
            refreshControl.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
            tableView.addSubview(refreshControl)
        }

    Now we need to define the refresh function, which will be called every time when the pull to refresh action is performed. In order for the update to occur in asynchronous mode, we use the following scheme (I will not go into the description of the details, it’s not difficult to figure out the code yourself)
        func refresh(sender:AnyObject) {
            refreshBegin("Refresh",
                refreshEnd: {(x:Int) -> () in
                    self.tableView.reloadData()
                    self.refreshControl.endRefreshing()
                })
        }
        func refreshBegin(newtext:String, refreshEnd:(Int) -> ()) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
                println("refreshing")
                self.text = newtext
                sleep(2)
                dispatch_async(dispatch_get_main_queue()) {
                    refreshEnd(0)
                }
            }
        }
    

    As a result, we get


    UPD: If you use the UITableViewController (or better to use it in this and similar cases), then the code will be even simpler. The UITableViewController already has the tableView and refreshControl properties, so you do not need to bind the UITableView manually and you do not need to declare refreshControl in the class. It is enough to write the following code in viewDidLoad and everything will work

        override func viewDidLoad() {
            super.viewDidLoad()
            refreshControl = UIRefreshControl()
            refreshControl.attributedTitle = NSAttributedString(string: "Идет обновление...")
            refreshControl.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
        }


    Infinite scrolling


    With infinite scrolling is a little more complicated, but not by much. In the controller class, add a new variable loadMoreStatus, which will be responsible for protection against repeated updates, if it is already running
        var loadMoreStatus = false

    Add a code to viewDidLoad that will initially hide the View with information about loading new data
        override func viewDidLoad() {
            super.viewDidLoad()
            refreshControl = UIRefreshControl()
            refreshControl.attributedTitle = NSAttributedString(string: "Идет обновление...")
            refreshControl.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
            tableView.addSubview(refreshControl)
            self.tableView.tableFooterView.hidden = true
        }

    Add a definition for the special function scrollViewDidScroll, which is called every time any scrolling occurs. If we dominate to the end of the list, the loadMore function is called, which implements asynchronous loading of new data
        func scrollViewDidScroll(scrollView: UIScrollView!) {
            let currentOffset = scrollView.contentOffset.y
            let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
            let deltaOffset = maximumOffset - currentOffset
            if deltaOffset <= 0 {
                loadMore()
            }
        }
        func loadMore() {
            if ( !loadMoreStatus ) {
                self.loadMoreStatus = true
                self.activityIndicator.startAnimating()
                self.tableView.tableFooterView.hidden = false
                loadMoreBegin("Load more",
                    loadMoreEnd: {(x:Int) -> () in
                        self.tableView.reloadData()
                        self.loadMoreStatus = false
                        self.activityIndicator.stopAnimating()
                        self.tableView.tableFooterView.hidden = true
                    })
            }
        }
        func loadMoreBegin(newtext:String, loadMoreEnd:(Int) -> ()) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
                println("loadmore")
                self.text = newtext
                self.countRow += 20
                sleep(2)
                dispatch_async(dispatch_get_main_queue()) {
                    loadMoreEnd(0)
                }
            }
        }
    

    As a result, everything works, the application does not freeze, and the data is added successfully. In


    such a simple way, you can implement pull to refresh and infinite scrolling, and of course, due to asynchronous updating, you can, for example, make JSON requests to the server in a simple, synchronous way, and this does not interfere with the application.

    Also popular now: