Infinite UITableView Scroll – Special Case

Reading Time: 6 minutes

When we are working with loading large data, if we load all the available items and try to display everything at once it will cause a big delay and the app will not work smoothly. The solution in cases like this is a combination of back-end and in-app solution. We should adjust the BE to work with pagination responses. The BE should give us only chunks of data and we should control the size of these chunks from the app sending the batch size. I’ve made research on this topic on the net and there are solutions but all of this is going in one direction. Using pagination, but every time starting from page 1 and loading the next pages after that. One of the best things was discovering the iOS Prefetching Protocol that I’ve never used before. This protocol is a piece of this solution.

Prerequisites

From the BE we need at least two APIs:

The first one is an API that will return all the necessary data: starting page, optionally which element from the starting page to be focussed and the total number of elements in the database. Why is the total number of elements needed? This is because if we don’t know it we won’t know how many rows we should present. Things will become more clear when we will start coding. The suggested JSON response should look like this:

{
	"total_elements": 480,
	"data": [
		{
			"id": 1,
			"name": "Test1"
		},
		{
			"id": 3,
			"name": "Test1"
		}
		//elements from 60..89

	],
	"first_page": 3,
	"per_page": 30
}

The second API is the API that will return the data and we will send a page number as an argument. The example JSON response is provided below:

{
	"data": [
		{
			"id": 1,
			"name": "Test1"
		},
		{
			"id": 3,
			"name": "Test1"
		}
		//elements from 1..29

	],
	"page_number": 1,
	"per_page": 30
}

Solution explained

I read a lot of articles about infinite scrolling UITableView’s but none of this is solving my special case – an option to start in the middle, and optionally to focus on a particular row from the table inside that page. Here is how I solved this issue:

First, I’m defining few variables in the code, some static integer values – the batch size (number of elements per page), start page value that will be variable and we will fetch it from the BE, and the total number of elements – variable that will be fetched also from the BE:

In my example, I will work with a list of integer values instead of using some objects, but this could be easily adjusted with any kind of objects/models.

Also, I will use a list of used pages, and I will keep track of the already fetched pages from the BE. Here is also one useful boolean flag “isNewDataLoading”. This flag will prevent us from calling the BE if the previous BE call is not finished.

    let batchSize: Int = 30
    let totalElements: Int = 480
    var startPage: Int = 5
    var elements: [Int?] = []
    var isNewDataLoading: Bool = false
    var loadedPages = [Int]()

The first call is the initial load method. Here, I will call the BE API that will return all the necessary data to pre-populate the code variables:

    func initialLoad() {
                
        for _ in 0..<totalElements {
            elements.append(.none)
        }
        
        loadedPages.append(startPage)
        
        for value in startPage*batchSize..<(startPage+1)*batchSize {
            elements[value] = value
        }
        
        mainTableView.reloadData()
        let toIndex = startPage*batchSize + ((startPage+1)*batchSize - startPage*batchSize)/2
        mainTableView.scrollToRow(at: IndexPath(row: toIndex, section: 0), at: .middle, animated: true)
    }

After the initial loading is done we have to explain the UITableView data source methods.

The “cellForRow” method has a simple logic. If we don’t have fetched the value for one of the cells, the cell will show a spinner (UIActivityIndicator); in case the value for the cell is already loaded we are showing into a text label (UILabel):

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TestTableViewCell
        if isLoadingCell(for: indexPath) {
            cell.configure(with: .none)
        } else {
            cell.configure(with: elements[indexPath.row])
        }
        return cell
    }

The Magic

Historically all of the older solutions use the UIScrollView delegate methods and inspect the current y-offset of the table. If the user reaches the maximum y-offset the API is called with the next page.

I made research on the topic and I recognized that the Prefetch Protocol should be useful in this situation. Some of the solutions on the net used the prefetch protocol in their solutions, but it needs some modifications if we want to make our code work with different starting pages. Let’s see how it looks into the code:

    func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        if indexPaths.contains(where: isLoadingCell) {
            
            let index = indexPaths[0]
            let page = index.row/batchSize
            if !loadedPages.contains(page) {
                //fetch new
                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) {
                    self.getNewData(page: page)
                }
            }
        }
    }

Since iOS 10 Apple introduced the API for prefetching data in UITableView and UICollectionView. More details about the prefetching protocol can be found on the Apple Developer site: https://developer.apple.com/documentation/uikit/uitableviewdatasourceprefetching

Little explanation of the code above: If the IndexPath of the cell that should be prefetched is an index that is not yet downloaded using the row value of the IndexPath we will determine the page to which the index path belongs to. If this page is not downloaded, we will download it. The code for downloading a new page will be shown below:

    func getNewData(page: Int) {
                        
        if isNewDataLoading {
            return
        }
        
        isNewDataLoading = true
        
        var temp: [Int] = [Int]()
        for num in page*batchSize..<(page+1)*batchSize {
            elements[num] = num
            temp.append(num)
        }
        
        loadedPages.append(page)
        
        let indexes: [IndexPath] = temp.map {
            return IndexPath(row: $0, section: 0)
        }
        
        mainTableView.reloadRows(at: indexes, with: .none)
        isNewDataLoading = false
    }

What is important in this method? The most important is to add the page to the list of already loaded pages, and the second thing is to reload only the rows in the table that belongs to the actual page.

The full code can be downloaded by clicking on the “Download” button below:

Feel free to add your comments or suggestion.

Tool Showcase: Node-RED

Reading Time: 5 minutes

Node-RED is a flow-oriented tool to wire together hardware devices, APIs and online services. It is mainly targeting the IoT market but can be used for a lot of other things as well. Because of its easy to use browser-based UI and drag and drop programming system, it’s really beginner-friendly with a steep learning curve.

Even though it was developed by IBM in 2013, it’s not really known to most of the IT community. At least none of my colleagues knew it. That’s worth for me to write this tool showcase. Enjoy reading!

Getting started

Instead of just reading along, I encourage everybody to just start Node-RED and try it yourself. If docker is installed, this is just a matter of seconds. Use the following command to start a Node-RED instance locally:

docker run -it -p 1880:1880 --name mynodered nodered/node-red

That’s it. You are ready to go! Open your browser and go to localhost:1880 to access the Node-RED UI.

One of the simplest flows is the following one:

Drag an “http in”, “template” and “http out” node into the flow and connect them. After clicking the deploy button you can access localhost:1880/<whateverPathYouConfiguredInYourHttpInNode> to see whatever you’ve configured in your template node. Congratulations, you have just created your first Node-RED flow!

Of course, rendering static content on an endpoint is not the most exciting thing to do, but between the HTTP in and out nodes, you’re free to do whatever you want. Nodes to make HTTP calls to different URLs, reading and writing files and much more are provided by Node-RED by default.

The power of the community

Node-RED uses Node.js for its nodes (yes, the terminology “node” is overused in the Node-RED context 🙂 ). This has a big advantage, that new nodes can be added easily from the node package manager (npm). You can find these nodes by searching for “node-red-contrib” in the npm repository. An even simpler option is to install these nodes using the “Manage Palette” option in the UI. There you can install new nodes with a single click.

There are nodes for almost everything. Need support for slack? Yep, there’s a node for that. Tired of pressing light switches in your house to turn off and on your Philips Hue lights? Yep, there’s a node for that as well. Whatever you can imagine, there’s a node for it.

A slightly more advanced flow

To test some Node-RED features, I tried to come up with a slightly more complicated example. I own some Philips Hue lamps and a LaMetric Time. After searching some nodes for these devices, I was really surprised that somebody already built some for these two devices (I was especially surprised about the support for the not so well-known LaMetric Time).

My use case was pretty straight forward. Turn on the lights when it gets dark and display a message on my LaMetric near my TV. At midnight, turn off the lights and display a different message. Additionally, I wanted some web endpoints that I could call to trigger both actions manually.

After only a few minutes, I had the following flow:

And it works! I found a node that sends an event as soon as the sun goes down at my particular location. Very cool. All the other nodes (integration for Philips Hue and LaMetric) can also easily be added with the “Manage Palette” option in the GUI. All in all, the implementation of my example use-case was pretty straight forward and required no programming know-how.

Expandability

Even though there are almost 3000 community-contributed nodes available to use, you might have some hardware or API that does not (yet) have some pre-made nodes. In that case, you can implement your own nodes pretty easily. The only thing required is a text editor and some node.js know-how.

The Node-RED documentation provides a good guide on how to create custom nodes: https://nodered.org/docs/creating-nodes/first-node

It is highly recommended to push your custom nodes to the npm repository to be used by the community.

Additional Resources

There are a whole lot more features that are not described in this blogpost.

  • Flows are just .json files and can easily be imported or exported or added to a git repository
  • Flows can be converted to subflows and used like nodes in other flows
  • Multiple flows can run in parallel and trigger each other
  • There are special nodes for error handling or low-level TCP communication
  • There are keyboard shortcuts for everything
  • … and much more!

Feel free to have a look yourself:

Thanks for reading!

Experiences of FrontendConnect 2019 conference Warsaw, Poland

Reading Time: 4 minutes

INTRODUCTION

Everybody has an open lifetime book full of blank pages, waiting to be filled. We write the story as we go, so back in November 2019, I have started the chapter ‘Frontend conferences’ by attending the FrontendConnect2019 in Warsaw, Poland, thanks to my company N47.

My motivation to choose this conference was the fact that I will gain new knowledge, and exchange practical ways of using frontend frameworks. Despite this, given the fact that there were great speakers from the IT world, I had no doubt choosing this tech event. Duration of the event was three days, one workshop day and two speaking conference days.

WHICH WORKSHOP DID I ATTEND TO?

As I was experienced with Vue.js, I wanted to upgrade the knowledge with Nuxt as their workshop description was “It may take it to the next level, thanks to its convention over configuration approach.” I got a certificate of attendance and completion of “My first Nuxt.js application” by the Vue.js Core Team member Darek ‘Gusto’ Wędrychowski. Coding under the eye of ‘Gusto’ and having a wonderful panorama view of Warsaw in my horizon, was definitely a day well spent.

WHICH PRESENTATION DID I ATTEND TO?

Rich agenda with scheduled talks, thoughts about which ones to choose, moreover similar questions were going through my mind. I attended the ones that caught my eye and were mostly within my interests.

At the beginning of each day, there was a high valued speaker opening the day with their talks. The first day I had to meet and listen to the very appreciated, Douglas Crockford with his JSON Saga.

The second day, there was Minko Gechev, a Google engineer working on the Angular framework with the talk ‘The Future of Front-End Frameworks’.

Some other topics that I attended to were about the state management in a world of hooks, some optimizations of the modern JavaScript applications and loading them instantly, as well as Angular and Vue.js 3.0 topics.

WHAT CAUGHT MY MIND?

Two of my favourite talks were ‘The JSON Saga’ – Douglas Crockford and ‘Vue 3.0 for Library Authors’ – Damian Dulisz.

The JSON Saga

Douglas was retelling the story about how he discovered JSON (JavaScript Object Notation). He explained how he did not invent, but found it in the early 2000s, named it and described its usefulness. JSON is a format for storing data and establishing communication between the servers. He explained how some companies complained and did not want to accept JSON because they were used to XML, and could not consider anything else, at that moment. He mentioned that some of the people denied its usage because of it not being a standard. So, what he did next was buying JSON.org, a website which after a few years spread among the users. After a while, JSON got the support of all languages. He announced that there will be no more changes to JSON because for him there is no feature more important than the stability of JSON.

Vue 3.0 for Library Authors

Getting more in details about this topic and Vue 3.0-alpha version will be covered in my next blog.

THE CULTURE AND ENVIRONMENT IN THE CONFERENCE

Frontend Connect was happening in the theatre of the Palace of Culture and Science in Warsaw, Poland where the history and modern world meet at the same time. It is one of the symbolic icons of Warsaw and the place of the city`s rebirth. There were people from all over the world, and the atmosphere was really friendly. Everybody was discussing the topics and shared their work ethics.

CONCLUSION

Visiting conferences is a really good way to meet new friendly people that you have a lot in common with, as well as having an opportunity to reach out to the speaker if you enjoyed the talk, and discuss what you found interesting. We should always strive for more experiences like this and face new challenges within modern technologies. With that being said, we need to nurture our idea to reach our full potential, in order to make a bigger impact in the IT world.