INTRODUCING A LIBRARY FOR DECLARATIVE LIST USER INTERFACES ON MOBILE DEVICES - CASPER HEDBRANDH STRÖMBERG - DIVA

Page created by Christine Dennis
 
CONTINUE READING
INTRODUCING A LIBRARY FOR DECLARATIVE LIST USER INTERFACES ON MOBILE DEVICES - CASPER HEDBRANDH STRÖMBERG - DIVA
UPTEC IT 20015

                                Examensarbete 30 hp
                                          Juni 2020

Introducing a library for declarative
list user interfaces on mobile
devices

Casper Hedbrandh Strömberg

            Institutionen för informationsteknologi
               Department of Information Technology
INTRODUCING A LIBRARY FOR DECLARATIVE LIST USER INTERFACES ON MOBILE DEVICES - CASPER HEDBRANDH STRÖMBERG - DIVA
Abstract
                                      Introducing a library for declarative list user interfaces
                                      on mobile devices
                                      Casper Hedbrandh Strömberg

Teknisk- naturvetenskaplig fakultet
UTH-enheten                           Developing user interfaces that consist of lists on native mobile platforms is complex.
                                      This project aims at reducing the complexity for application developers when
Besöksadress:                         developing dynamic interactive lists on the iOS-platform by creating an abstraction
Ångströmlaboratoriet
Lägerhyddsvägen 1                     that lets the application developer write code on a higher abstraction level. The result
Hus 4, Plan 0                         is a library that creates an abstraction that developers can use to build list user
                                      interfaces in a declarative way.
Postadress:
Box 536
751 21 Uppsala

Telefon:
018 – 471 30 03

Telefax:
018 – 471 30 00

Hemsida:
http://www.teknat.uu.se/student

                                      Handledare: Jesper Sandström, Spotify Technology S.A.
                                      Ämnesgranskare: Anca-Juliana Stoica
                                      Examinator: Lars-Åke Nordén
                                      UPTEC IT 20015
                                      Tryckt av: Reprocentralen ITC
INTRODUCING A LIBRARY FOR DECLARATIVE LIST USER INTERFACES ON MOBILE DEVICES - CASPER HEDBRANDH STRÖMBERG - DIVA
Sammanfattning

Långa listor med dynamiskt innehåll är idag en viktig del av användargränssnittet
i många applikationer. Vi använder dem varje dag i olika sammanhang, vare sig
det är för att visa en lista med låtar i favoritspellistan på musikstreamingappen,
eller för att hitta det rätta tåget i kollektivtrafiksapplikationen. Sådana listor har
historiskt sett varit svåra att utveckla, vilket har resulterat i felaktiga program
eller en långsam utvecklingstakt. Den här rapporten presenterar en lösning för
smidigare utveckling av listor. Detta görs genom ett bibliotek, vars syfte är att göra
det enklare för applikationsutvecklare att programmera dessa listor. Lösningen
innebär ingen skillnad för användaren, men minskar tiden för utveckling, vilket i
sin tur kan ge mer tid till utveckling av nya funktioner.
Acknowledgements
I thank Jesper Sandström and the rest of the App Architecture team at Spotify
for all the guidance and great feedback I received from you. I would also like to
thank Anca-Juliana Stoica for all the feedback and input that made this thesis
possible.
   I would like to thank my fellow students, colleagues and best friends Erik Eng-
berg, Lowe Eklund, Fabian Haglund and Jonathan Wahlgren for all the moments
we shared and for the ones that we will continue to share in the future.
   Finally, I would like to thank all of my friends and my family for being there
for me both during this thesis work, but more importantly in times of need.
Contents
1 Introduction                                                                        1
  1.1   Lists in User Interfaces . . . . . . . . . . . . . . . . . . . . . . . . .     1
  1.2   Aim and Method . . . . . . . . . . . . . . . . . . . . . . . . . . . .         3
  1.3   Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .       4
  1.4   Scope   . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .    5
  1.5   Research Question . . . . . . . . . . . . . . . . . . . . . . . . . . .        5

2 Background                                                                           5
  2.1   Diffing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .    6
  2.2   Reconciliation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .     7
  2.3   Viewport . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .     8
  2.4   Virtualization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .     9
  2.5   Recycling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
  2.6   UIKit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
  2.7   UIKit lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
        2.7.1   Responding to data changes . . . . . . . . . . . . . . . . . . 12
        2.7.2   Source of truth . . . . . . . . . . . . . . . . . . . . . . . . . 13
        2.7.3   Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . 13
        2.7.4   Index Paths . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
        2.7.5   Recycling . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
  2.8   Separation of Concerns . . . . . . . . . . . . . . . . . . . . . . . . . 16

3 Result                                                                              16
  3.1   Application Programming Interface . . . . . . . . . . . . . . . . . . 17
        3.1.1   TableRender . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
        3.1.2   TableContext . . . . . . . . . . . . . . . . . . . . . . . . . . 17
        3.1.3   Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.1.4   Custom Rows . . . . . . . . . . . . . . . . . . . . . . . . . . 19
        3.1.5   Headers and Footers . . . . . . . . . . . . . . . . . . . . . . 19
        3.1.6   Automatic Section Creation . . . . . . . . . . . . . . . . . . 20
  3.2   Implementing Table Renderer . . . . . . . . . . . . . . . . . . . . . 21
        3.2.1   Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
        3.2.2   Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
        3.2.3   Renderer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
        3.2.4   Reconciler . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

4 Discussion and Evaluation                                                        24
  4.1   Composability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
  4.2   Declarative . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
  4.3   Type Safety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
  4.4   Coupling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
  4.5   Cohesion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
        4.5.1   Example: UIKit Implementation . . . . . . . . . . . . . . . 29
        4.5.2   Example: TableRender implementation . . . . . . . . . . . . 30
  4.6   Mixed Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
        4.6.1   Example: Mixed lists . . . . . . . . . . . . . . . . . . . . . . 32
        4.6.2   Example: Mixed list with UIKit . . . . . . . . . . . . . . . . 32
        4.6.3   Example: Mixed list with TableRender . . . . . . . . . . . . 34
  4.7   Flexibility and Extension Points . . . . . . . . . . . . . . . . . . . . 35
  4.8   Data updates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

5 Related Work                                                                     36
  5.1   SwiftUI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
        5.1.1   Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
  5.2   React   . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.3   Cycler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
  5.4   Di↵able Data Source . . . . . . . . . . . . . . . . . . . . . . . . . . 39
  5.5   Litho . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
  5.6   ComponentKit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

6 Conclusion                                                                       40
  6.1   Future Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
        6.1.1   Collection View . . . . . . . . . . . . . . . . . . . . . . . . . 41
        6.1.2   Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
        6.1.3   Declarative UI . . . . . . . . . . . . . . . . . . . . . . . . . . 42

References                                                                         42
Glossary
Application Programming Interface An interface, generally specified as a set
     of operations, that allows access to an application program’s functionality.
     This means that this functionality can be called on directly by other pro-
     grams and not just accessed through the user interface. 4, 5, 40

cell A unit that is responsible for the user interface of a single piece of data in a
     list (e.g. rows, headers, and footers). 1

cell-type A class of cells that has similar behaviour and therefore can be reused
     together by re-configuring the containing elements. For example, a list can
     have a song cell-type that displays songs and an album cell-type that displays
     albums. 1

iOS A mobile operating system developed by Apple Inc used exclusively on its
     mobile devices. . 3–5, 10, 11, 16, 36, 39–42

library A set of reusable concrete and abstract classes that implement features
     common to many applications in a domain (e.g. user interfaces). 1, 3, 4, 8,
     16, 19–21, 23, 27–29, 35, 37, 38, 40–42

Swift A modern general-purpose programming language used as the default lan-
     guage on the iOS-platform. 3
List of source codes
  1    The result of a naive diffing algorithm with false positives. . . . . .   7
  2    The result of a diffing algorithm with maximum preciseness. . . . .       7
  3    Example of a declarative API that requires the use of reconciliation
       and diffing (from TableRender library) . . . . . . . . . . . . . . . .    8
  4    Example of the usage of index paths when deciding what action to
       perform based on an event-triggered from a row. . . . . . . . . . . . 15
  5    Example of a registration of a table view cell. . . . . . . . . . . . . 15
  6    Example of the dequeue and binding process for a table view cell. . 16
  7    Example of the initiation of table renderer . . . . . . . . . . . . . . 18
  8    Example of the usage of TableContext to construct a list. . . . . . . 18
  9    Example of a selectable row. . . . . . . . . . . . . . . . . . . . . . . 19
  10   Example of a custom row provided by the application developer . . 20
  11   Example of a simple header . . . . . . . . . . . . . . . . . . . . . . 20
  12   Example of sections that are created based on headers and footers.
       In the example 4 sections are created. The first one with a section
       header, footer and one row. Sections 2 will only contain a header
       while section 3 will contain a header and a Footer but no rows. The
       last section will only contain a row . . . . . . . . . . . . . . . . . . 21
  13   Example of composition using the Context API . . . . . . . . . . . 23
  14   Example of a simple list with one section that has a header and a
       footer which contains a row. . . . . . . . . . . . . . . . . . . . . . . 25
  15   Composition is achieved through functions. . . . . . . . . . . . . . . 26
  16   Common implementation of a method that provides cells in the
       table view data source. Note the unsafe typecasting on line 4 and
       the risk of invalid access to arrays on line 1. . . . . . . . . . . . . . 28
  17   Example data model . . . . . . . . . . . . . . . . . . . . . . . . . . 32
18   Example: An implementation using the pure UITableView imple-
       mentation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
  19   Example: Multiple cell-types with TableRender . . . . . . . . . . . 34
  20   Example of a cell that is configured to be editable. . . . . . . . . . . 36
  21   Example of a list with two sections built with SwiftUI . . . . . . . . 37

List of Figures
  1    A typical scrollable list that demonstrates the usage of di↵erent
       cell-types and multiple sections with headers . . . . . . . . . . . . .    2
  2
       (a) Example of a heterogeneous list where di↵erent cell-types are
       used to display multiple types of data on the same list.
       (b) Example of a homogeneous list where only one type of data is
       displayed in the same way trough-out the list. . . . . . . . . . . . .     3
  3    Overview architecture of TableRender . . . . . . . . . . . . . . . . . 22
  4    Implementation of a list with di↵erent cell-types using pure UIKit. . 29
  5    Implementation of the same list from Figure 4 using TableRender. . 30
1     Introduction
The development of the user interface is a large part of many applications. Many
features of modern mobile applications depend on long lists with dynamic rows of
di↵erent types. Displaying long lists of dynamic data in a highly efficient manner
presents a challenge for many application developers. In the traditional imperative
method of programming, the application programmer is responsible for adding,
deleting, and moving items as the underlying data are changing. Additionally,
several performance optimizations that increase the complexity are required to
make the user interface responsive. By using a library that allows the application
developer to write declarative code many of the intricacies can be abstracted away.
The project will try to utilize methods such as reconciliation and diffing to be able
to construct this abstraction.
    Spotify Technology S.A. is an audio streaming service that relies on having
great scrolling user interfaces. This project is done in collaboration with Spotify
to try to fix the issues with the current solutions.

1.1    Lists in User Interfaces

A list is in the context of user interface development a user interface component
that displays a collection of data. The collection of data can be homogeneous and
only describing one type of repeating data e.g. a list with all of the users’ favourite
songs or it can be heterogeneous and displaying di↵erent types of content on the
same list. A single piece of content is called a cell. Di↵erent types of content often
have di↵erent layout and behaviour, this is when the concept of cell-types is used.
A cell-type is a class of cells that look and behave in similar ways often mapped
to one specific data type.
    Many lists can grow dynamically and therefore grow to be larger than what

                                          1
Figure 1: A typical scrollable list that demonstrates the usage of di↵erent cell-types
and multiple sections with headers

a device can display at the same time. To show larger lists than the display can
fit the list can be made scrollable so that a select part of the list can be shown
to the user and the user can choose to see more if needed. A common pattern in
applications is to show multiple lists in the same scrolling surface with di↵erent
content, the multiple lists are often considered to be one list with multiple sections.
List with deeper hierarchical nesting exists but will not be explored in this thesis.

                                          2
(a) Heterogeneous List                   (b) Homogeneous List

Figure 2:
(a) Example of a heterogeneous list where di↵erent cell-types are used to display
multiple types of data on the same list.
(b) Example of a homogeneous list where only one type of data is displayed in the
same way trough-out the list.

1.2    Aim and Method

The goal of the project is to implement a prototype of a library that should be able
to take a list of data and specifications of each list item and render it to screen as
well as keep the interface updated as the data changes. The prototype will focus
on mobile applications in general and iOS devices in particular. Mobile devices
are focused due to two factors that make the prototype more useful than on other
platforms; mobile devices have small screens and scrollable content is therefore
used extensively, many mobile devices are low powered and applications that run
on the need to be efficient to provide a good user experience. The project will be
using Swift as the implementation programming language [15]. The project will

                                           3
follow a building methodology as defined by [27]Amaral et al.
   The project requirements are to provide an abstraction that is easier to use
than the platform default while still be powerful enough to allow for complex
user interfaces to be built on top of it. The library that is presented should be
flexible enough that it can be used in existing projects that use di↵erent idioms
and frameworks. The library should work on large applications where many teams
are involved in developing the same feature.

1.3    Requirements

The requirements of the library can be summarized as follows:

   • The Application Programming Interface should be declarative.

   • It should be possible to customize the rows and their behaviour as much as
      the underlying system allows.

   • It should make it easier to produce lists with di↵erent cell-types.

   • It should make it easier for di↵erent teams to work on the same list without
      interference.

   • It should enable rows, headers, and footers to be reusable, not coupled to a
      specific list.

   • It should enable updates in data to be propagated into the user interface
      automatically.

   • The solution should not only work on the most recent operating system (iOS
      13) but also the work on devices that has older versions of the operating
      systems installed (iOS 11+).

                                        4
1.4    Scope

Although the techniques and findings of this project are not specific to a particular
platform or type of device, the scope of the project has been limited to mobile
devices running the iOS-platform. The thesis is done in collaboration with a large
technology company and the solution is therefore geared towards solving problems
that occur in large organizations with large code-bases rather than problems that
occur in smaller systems and organizations.

1.5    Research Question

The project aim is to provide a more productive declarative Application Program-
ming Interface for lists as an alternative to the native list Application Programming
Interfaces. To investigate and develop the abstraction needed to achieve this goal
the following research question is formulated:

      Can an abstraction be constructed to provide a declarative interface to
      native list user interfaces on the iOS-platform?

2     Background
The iOS-provided Application Programming Interface for build user interface lists
are in general not declarative but instead, are built to facilitate imperative object-
oriented approaches to user interface development [25]. While imperative and
object-oriented approaches have proven to be valuable when developing user inter-
faces they provide low-level abstractions on which great responsibility on how the
user interface should be constructed is handed o↵ to the application developer. As
a contrast, declarative approaches to user interface design focuses on what should
be rendered to the screen and not how. The declarative model lets the application

                                          5
code not have to consider every state transition that needs to occur to update
the interface to the desired state [7]. Instead, the application only has to consider
what the current state should look like and the underlying system takes care of
what has to change for the interface to match that expectation. For the platform
to take on this responsibility, techniques for calculating changes and syncing those
changes to the underlying systems are required. The most important techniques
used in this project are summarised below.

2.1    Diffing

Diffing is the process of working out the di↵erence between two data sets. There
exists a myriad of diffing algorithms aimed at solving the problem for di↵erent data-
structures, with di↵erent performance characteristics, and with varying precision.
With precision, the number of false positives is usually the metric considered, and
algorithms that can produce any false negatives are considered to not be a valid
solution. Some diffing algorithms, however, relax the requirements of finding all
changes to speed up the algorithm thus producing false negatives.
   To see the relationship between preciseness and compute time we can consider
the most basic diffing algorithm we can think of: remove all the old data followed
by adding all the new data, the algorithm will result in the correct result but will
for most cases result in a lot of false positives and is thus a low precision algorithm
(see Listing 1). With this example, we can also notice that consideration of how
the data usually change needs to be done: if it is common for all of the data to
change at once the algorithm where all the old data are removed and all the new
data are inserted might be a viable diffing algorithm. The most precise solution
is considered to be a solution with no false positives and no false negatives (see
Listing 2). This can also be framed as the solution with the fewest possible changes,
such an algorithm can often be constructed as a graph or path solving algorithm

                                          6
which is the case for one of the most widely used diffing algorithms: The Mayers
Diffing algorithm [24].
   More deep and complex data structures are in general slower to di↵ than shal-
lower structures, however, it is sometimes possible to use some heuristics to relax
the exact diffing requirements in favour of execution time [4].
   In the context of user interface programming with declarative methods, diffing
is used to figure out the di↵erence between the current state of the user interface
and the desired state of the user interface. Di↵erences in the current state and the
desired state arise from changes in the data that the view is trying to represent.
Diffing is often used as a part of a Reconciliation algorithm.
diff( ["A","B","C"], ["A","B"] ) =

remove ["A","B","C"]
insert ["A","B"]

      Listing 1: The result of a naive diffing algorithm with false positives.

diff( ["A","B","C"], ["A","B"] ) =

remove ["C"]

      Listing 2: The result of a diffing algorithm with maximum preciseness.

2.2    Reconciliation

Reconciliation is, in general, the process of reacting to changes in requirements and
updating a system so that it matches the desired state. In declarative user interface
systems, reconciliation is used to synchronize the application state with the view.
This enables declarative user interface code to not have to specify what has changed
but instead only have to consider how the user interface should look and behave

                                         7
with the current state, the reconciliation will take care of the necessary mutations
to the view. In this thesis, only one-dimensional data are considered which reduces
the complexity of the diffing and reconciliation phases. When building libraries
that should render trees with views more complex methods may be needed[22]. The
tableRenderer.render {
    $0
of the window that the user can see. The window is in this context the entire
view with its coordinate system sometimes referred to as the world coordinate
system, in the case of lists this is the entire list. The viewport can change based
on user actions like scrolling or zooming. This mapping between the window to
the viewport is called the window to viewport transformation.

2.4    Virtualization

Virtualization or windowing is a method that is used to reduce the memory foot-
print and reduce the time taken to render the list for the first time. It is especially
e↵ective on lists that extend far beyond the viewport. The technique works by
taking advantage of the fact that in cases where lists are long a large portion of the
items in the data set are not visible on screen at a given single point in time. The
platform therefore only needs to consider items that should be on screen. When
the viewport changes i.e the user scrolls and new rows should be rendered, new
views are allocated just before they are needed. Instead of allocating and rendering
views for all the items in the data on the initial render, the application only has
to create views for the data that is visible in the current viewport and can then
lazily render rows as they are needed.
   To show the proper layout of the list the platform requires that we know the
size for all items a priori. The sizes are required to show an adaptable scrollbar and
scroll to a specific index without requiring the system to render all cells before the
index[11]. On some platforms, this requires the developer to specify a function that
takes in data and returns the size of the cell. This measurement step can lead to
duplication of coupled layout code which can be error-prone. On other platforms,
this step can be done internally but can, in turn, result in a negative performance
impact. Other drawbacks of virtualization are that if the virtualization engine is
using asynchronous rendering and the user is scrolling faster than new views can

                                          9
be created, blank spaces will appear on the screen and the illusion that all the
items are always rendered will be lost.

2.5     Recycling

While virtualization provides a good optimization for large lists new views have to
be constructed for every new item that should appear on the screen. If the user is
scrolling fast a large number of views have to be constructed and disposed of when
they leave the screen. A large amount of allocation and deallocation will result in
higher pressure on the garbage collection. Recycling is a method that is used to
combat this problem. Recycling works as the name suggests by reusing views that
already are allocated. When a row is considered inactive i.e when it moves out of
the viewport, instead of deallocating the view, it is moved to a queue. Later, when
a new row should be rendered an inactive row can be dequeued, reconfigured with
new data, and re-positioned. By using a queue the number of new views needed
to be constructed is reduced which will decrease the time retrieving views.
   To support multiple di↵erent row types, platforms typically use some sort of
identifier [13] that relates a row to a row type. Each row type is then given its own
queue with inactive rows that conforms to that particular row type. This is useful
if the application requires rows in the same list to have di↵erent requirements, for
example, a list that is composed of image and text rows would have a separate
queue for the text and image rows. This would allow the image rows to reuse the
image view and the text rows to reuse the text views that are contained in the
rows.

2.6     UIKit

UIKit is a framework provided by Apple that is aimed at constructing user inter-
faces for iOS and tvOS apps[8]. UIKit is built with object-oriented approaches

                                          10
in mind. UIKit provides building blocks for UI-development in the form of view-
controllers and views. UIKit is currently the main way developers develop user
interfaces on iOS, iPad OS, and tvOS.

      ”The structure of UIKit apps is based on the Model-View-Controller
      (MVC) design pattern, wherein objects are divided by their purpose.
      Model objects manage the app’s data and business logic. View objects
      provide the visual representation of your data. Controller objects act
      as a bridge between your model and view objects, moving data between
      them at appropriate times.”

2.7    UIKit lists

UIKit provides two interfaces that developers can use to build lists interfaces.
UITableView and UICollectionView. The main di↵erence between the two is that
UICollectionView is more flexible than UITableView while the table view has a
simplified API, for example, UICollectionView can be configured to render more
esoteric layouts like grids and carousels. This project will concentrate its e↵ort
into providing an abstraction over UITableView however it should be relatively
straight forward to extend the solution to also include UICollectionView as a target
enabling the developer to take advantage of the flexibility that UICollectionView
provides. The prototype will use UITableView instead of UICollectionView due to
the UITableView API being less complex and thus less work is needed to provide
an abstraction for the API.
   UITableView provides a set of classes and protocols that can be leveraged by
the developer to create scrolling single column user interfaces. UITableView itself
is a class that provides the view where the content should be rendered upon.
The view is a subclass of UIScrollView which allows the viewer to be scrollable
if needed. UITableView also provides two protocols that define properties that

                                        11
can be used to configure the behaviour of the table view: UITableViewDataSource
and UITableViewDelegate. UITableView requires an object which conforms to
UITableViewDataSource, the object provides the specification of the number of
items in the data as well as the specific views for each item in the data. The
UITableViewDelegate protocol defines methods that can be overridden to capture
events from the table view.

2.7.1   Responding to data changes

When the data that the table relies on changes, the table view has to be notified
to update the user interface. There are several ways to notify the table view to
update the user interface, all with their specific characteristics. The most simple
although naive solution is to call the method reloadData() on the table view [12].
The reloadData() method will cause the table view to reload all the data from
its data source, all the rows will be re-rendered not considering if the data that
the rows were represented had changed or not. While reloading data is the most
straight forward way of updating the list it does not enable transition-animations
and it can be inefficient on larger lists with small changes. The other way of
notifying the table view of changes is to specify exactly how and what changed,
this enables transition animations and lets the table view only have to recreate
the rows that had changed from the previous data. This tactic can be used if you
know or can determine what changed from the previous render, then the developer
calls the methods corresponding to inserts, deletes, and moves on the table view.
These method calls can be grouped by wrapping the calls in a closure and pass
that to performBatchUpdates() [9]. Then the updates are done in batch, hence the
animations will occur together.
   It is often not trivial to know exactly what changed in the data. If the change is
due to a direct user interaction e.g. user deleted a row the solution is given however

                                         12
this is not the most common case in modern applications. In many situations the
changes can not be trivially extracted; a common feature in modern apps is to
have a search field which filters the data in the list below depending on the input
value if the user input changes the data should be filtered and replace the old
data in the table, in this case, we only know what the result is and not what
changed. To solve this problem application developers are forced to implement a
diffing solution, comparing the old data with the new data.

2.7.2     Source of truth

The architecture that is forced upon the developer when using raw UITableViews
to build lists is a view and a controller where the controller is responsible for
updating the view as state or backing data is changing [14]. This architecture is
keeping one explicit copy of the state in the controller but the architecture requires
the controller to also maintain another implicit copy of the current state in the
view. When the state changes the controller has to figure out what changed and
synchronize the new changes to the view, essentially implementing an own version
of a reconciliation algorithm. The lack of a single source of truth makes it harder
for the developer to reason about and test the application.

2.7.3     Configuration

The main way of configuring and providing data to table views is to use delegation.
Delegation is a patterned widely used in object-oriented programming to provide
dynamic object composition. The table view can be configured with two di↵erent
delegates that can alter the behaviour of the view in di↵erent ways.

   • UITableViewDataSource: A data source is an object with the purpose of
        managing the data that the table view is representing. The data source
        provides information about the data for the table view, this includes the

                                         13
number of sections that the table should render and the number of cells in
        each section. The data source also provides the views for each row in the
        table view [17].

   • UITableViewDelegate: The delegate is used to configure the behaviour of
        the table view when responding to user-initiated events like selections, dele-
        tions, and reordering. The delegate is also responsible for configuring section
        headers and footers [18].

2.7.4     Index Paths

When developing applications using UITableView the method of identifying spe-
cific rows in the table is to use index paths [1]. Index paths are data structures
that encode the path to the specific row at a specific time using two integers: one
to specify which section the row is in and one integer which encodes wherein the
section the row is. Application developers use the index paths to identify and
configure individual rows e.g. they are used in the cell-configuration phase when
populating rows with content from the backing data or when an event occurs and
one of the UITableViewDelegate methods is called and the application decides how
to act based on which data the row that triggered the event is representing. The
problem with index paths is that they are ephemeral and changing, the identity
of the row is related to the position of the row. The index path to a particular
item in the data can change if the preceding data changed e.g. a row is inserted
or removed. The usage of ephemeral IndexPaths is the source of many bugs where
the data changed between the time that the index path was collected and the index
path was used.

                                           14
func tableView(
    _ tableView: UITableView,
    didSelectRowAt indexPath: IndexPath) {
        let item = data[indexPath.section].rows[indexPath.row]
        switch item {
           case .Artist(let name):
                print("pressed on artist " + name)
           case .Song(let title):
                print("pressed on song " + title)
       }
    }

Listing 4: Example of the usage of index paths when deciding what action to
perform based on an event-triggered from a row.

2.7.5   Recycling

UITableView provides an interface that developers can use to recycle table view
cells. The table view provides a queuing system where the developer can register
views that can be recycled. To utilize the recycling behaviour a two-step process
is required. The first step is to register all the view types that can be recycled,
this is often done in the view controllers viewDidLoad() method. The registration
call consists of two parameters: the class that should be instantiated if the queue
is empty and an identifier that allows the system to maintain separate queues for
di↵erent view types The second step is performed when the table view requires an

func viewDidLoad() {
    self.tableView.register(
        SongCell.self,
        forCellReuseIdentifier: "song-cell"
    )
}

            Listing 5: Example of a registration of a table view cell.

                                        15
item to be rendered. The application then calls the dequeueReusableCell() method
which will try to find a disposed view for the view type specified, if the queue is
empty the table view will instantiate a new view based on the class registered in
the step before and return the newly instantiated view. The view is then ready to
be configured and rendered to screen.

let cell = tableView.dequeueReusableCell(
                    withIdentifier: "song-cell",
                    for: indexPath) as! SongCell
cell.textLabel?.text = title

    Listing 6: Example of the dequeue and binding process for a table view cell.

2.8     Separation of Concerns

The delegation pattern that UITableViews utilizes allows for easy separation of
the event handling and the data handling but makes it hard to separate multiple
types of rows or cells. The programming model works reasonably well when only
one team is responsible for the entire table. It is common for user interfaces to
present many di↵erent types of cells in the same list, the cells can be managed
by di↵erent teams in the organizations. It is therefore essential that a row can be
changed by the team owning that view, while not a↵ecting the other cells.

3      Result
The result that is presented in this report is a library that aims to solve many
problems that developers encounter when developing user interface lists on iOS.
The library called TableRender provides a declarative API that lets the application
describe the desired state of the list. Each time the render method on TableRender

                                        16
is called the library will use diffing and reconciliation to determine what changed
and what needs to update.

3.1     Application Programming Interface

Table Renderer provides a declarative API that wraps UITableView allowing the
developer to write declarative, cohesive, and flexible code.

3.1.1   TableRender

To use Table Renderer the application developer creates an instance of the TableRen-
der class. The TableRender takes a tableView as arguments to its constructor
method, the tableView provided will be managed by the TableRender object (see
Listing 7). TableRenderer will take on the responsibility of providing a data source
and a delegate as well as keeping the data source in sync with the view. The ap-
plication developer should no longer try to configure these properties of the table
view directly but instead use the abstraction that TableRenderer provides. The
TableRender object has one public method: render((ctx: TableContext) -> TableContext)
which is used to render rows, headers and footers to the screen. To keep the ap-
plication data and state in sync with the user interface the developer should call
the render method whenever any of the data changes. The render method takes a
closure as the argument that will provide a table context and expects to return a
table context that is populated with the rows, headers, and footers that describes
the table.

3.1.2   TableContext

The context module aims to collect and compose rows, headers, and footers into a
stack-like data structure. The context has two public methods: one that adds an
item to the end of the stack and a method that appends the contents of another

                                        17
let tableView = UITableView()
let tableRender = TableRender(tableView: tableView)
tableRender.render { ctx in
    ...
}

               Listing 7: Example of the initiation of table renderer

context to the end of its stack, this can be used to compose di↵erent logical parts
of a list. The context is the main user-facing API which developers use compose
lists with TableRenderer (see Listing 8).
func Artists() -> TableContext   {
    var ctx = TableContext()
    ctx
Cell and provide that to the onRender() method. The model is used for diffing
and must, therefore, conform to the Hashable protocol which is a built-in protocol
in Swift. The conformance to the Hashable protocol means that the library can
determine identity as well as equality when running the diffing algorithm. Since
the model is used in the diffing algorithm all the data that is used to configure the
cell must be included in the model. If the application developer fails to do so can
result in stale data rendered to the screen.
Row(model: "model")
    .onRender { (cell, model) in cell.textLabel.text = model }
    .onSelected { print("pressed")}

                      Listing 9: Example of a selectable row.

3.1.4   Custom Rows

For most non-trivial applications the rows need to be customized further than the
standard UITableViewCell. TableRender handles this gracefully by allowing the
user to pass in a subclass of a UITableViewCell to the onRender method. The
custom cell will be automatically registered on the table view with a reuse identifier
based on the class identity thus creating a reuse queue that will contain instances
of the CustomCell type. An instance of the custom cell will be provided to the
render block in the onRender method allowing the application developer to bind
the model data to the view.

3.1.5   Headers and Footers

Headers and footers are constructed in the same manner as Rows however they
di↵er slightly in the modifier methods that they provide due to the underlying
system. Just as rows, headers, and footers can also be configured to use custom

                                         19
public class CustomCell: UITableViewCell {
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.backgroundColor = .darkGray
        textLabel?.textColor = .white
    }
}
ctx
instruction is encountered the current section will be ended, if a row is added after
a new section will be automatically created and the following rows will be included
in that section.

ctx
Figure 3: Overview architecture of TableRender

into di↵erent contexts can be made arbitrarily as long as it is done in the right
order i.e. a section can span multiple table contexts.
   The architecture of the table context makes it easy to split up a list into multiple
modules and later on compose them together without any coupling.

3.2.2   Parser

The parser will take a table context and compute useful properties based on the
contents of the context. It is the job of the parser to turn the one-dimensional data
structure that the context is into a two-dimensional data structure with sections
that can have a header, a footer, and multiple rows. The parser also extracts all
views and reuse identifiers that are needed to render the list, these views are later
registered in the UITableView.

                                         22
let listContext = new TableContext()

let Weekdays = new TableContext()
weekdays
imperative methods that indicate the change, the old backing data is replaced with
the new data. Certain combinations of updates will cause the table view to crash,
for example, a reload and insert command on the same row will cause a crash, to
mitigate this the reconciler has to split up the reconciler phase into smaller pieces
and apply the changes in multiple batch updates and apply them incrementally.
The diffing and reconciliation algorithm that is used can easily be changed to
another implementation, the default implementation is using Di↵erenceKit [3].

4     Discussion and Evaluation
The thesis implements an approach to improve the developer experience for de-
velopers developing complex dynamic scrolling user interfaces on mobile devices.
This chapter will from the application developers’ point of view, evaluate the solu-
tion that was described in the results. The project requirements that were posed
on the project in the introduction will be used as the base of discussion. The
provided solution (TableRenderer) will be compared to the traditional imperative
modes of programming (UIKit).
    Based on Figure 1 - overview architecture of TableRender, we present the
following code example.

4.1    Composability

The library provides a way of splitting up lists into smaller parts and later combin-
ing the parts into the final list. The composition can be used to split up a feature
into pieces where di↵erent teams are responsible for di↵erent pieces or to organize
code for maintainability and re-usability. The composition is achieved through
functions, where each function creates a new TableContext object. The callee can
use the returned context and merge it with its context via the infix operator. The

                                         24
let tableView = UITableView()
let tableRender = TableRender(tableView: tableView)
func renderTable() {
    tableRender.render {
        $0
func Artists(model: [Artist]) -> TableContext {
    var context = TableContext()
    for artist in model {
        context  TableContext {
    var context = TableContext()
    for song in model {
        context
of this library is designed to be more declarative than using the platforms’ native
APIs. By the definition of declarative code above we can conclude that more
declarative code can be measured by observing how much of the code is describing
how the user interface should be constructed respectively how much of the code is
describing what the user interface should look like.
   The main part of writing a list with TableRender is to implement a render
function. The render function (see Figure 3) is the complete description of how
the list should be rendered. The application developer does not have to consider
the lists of previous states. The only way to modify the list is to call the render
method with a completely new description of how the list should look like. This
will result in a highly declarative code that does not have to consider insertions,
deletions, registrations, and disposals.

4.3    Type Safety

When creating a raw UITableView the splitting of the registration of views and
the binding of views create an implicit relationship that is not covered by the type
system instead the relationship is defined by opaque strings namely reuse iden-
tifiers. The method that is used to dequeue a new cell dequeueReusableCell() is
always returning a UITableViewCell that needs to be cast into the actual imple-
mentation of the cell, this is not a type-safe operation. If the application developer
changes the string in one place and not the other or if the developer forgets to
register the cell the error would be caught at run time. In TableRender no such
implicit relationship exists which enables the API to be fully type-safe. The library
also solves another common adjacent problem that often occurs when implement-
ing table views: invalid access to arrays. In the native APIs, the user does not
configure individual rows to customize them instead the user overrides methods
that get index paths as arguments and can return in di↵erent ways to provide

                                           27
the correct behaviour. When implementing these methods array lookups are often
used to know what behaviour should apply for that specific row. Invalid array
look-ups are common errors that developers using the native table view API often
stumble upon, in TableRender code the developer links the user interface to the
data directly often by the use of a for therefore no array lookups are required thus
eliminating a whole set of bugs.
let item = data[indexPath.section].rows[indexPath.row]
let cell = tableView.dequeueReusableCell(
                withIdentifier: "song-cell",
                for: indexPath) as! ArtistCell
cell.textLabel?.text = name
return cell

Listing 16: Common implementation of a method that provides cells in the table
view data source. Note the unsafe typecasting on line 4 and the risk of invalid
access to arrays on line 1.

4.4    Coupling

The level of coupling can be explored by asking the question: ”How much of one
module must be known in order to understand another module?” [26]. When a
system is built in a way so that the modules are loosely coupled each module can be
understood in separation. One goal of the TableRender library is to decouple rows,
headers, and footers from the table view implementation. The component-oriented
API provided by TableRender lets each row to be independently implemented
without considering the rest of the table. The decoupling allows teams to work on
di↵erent components without interference, it also allows for greater reuse due to
the component not being coupled to a specific list.

                                        28
4.5     Cohesion

The library aims to provide a way for developers to write more cohesive code when
developing lists. In traditional methods provided by the platform, it is common
to group the construction of a list into two stages: creation and binding. The two
are there because the table view is reusing rows to increase the efficiency of the
application. The problem with this solution is that the definition of a list item is
divided into two separate locations in the code. In computer programming, the
aim is usually to have a high level of cohesion, which is to say dependent code
should live close to each other in the same module [5]. Another way to define
cohesion is that code that is usually modified together should be close to each
other. Splitting up code for a single list item results in low cohesion using both of
these definitions of cohesion. This project provides an API where a row, header,
and footer is always defined in a single place which in most cases will result in
higher cohesion.

4.5.1   Example: UIKit Implementation

  Figure 4: Implementation of a list with di↵erent cell-types using pure UIKit.

The definition of both of the cells are in this case split up in two methods:
cellForRow()   and didSelectRow(). The cellForRow() method is used by the table

                                         29
view to obtain a view to render and is therefore required while the didSelectRow()
is an optional override-able method on the delegate. Due to the configuration API
being focused on the whole list it is hard to work on a single cell-type in isolation
- this can make it hard for di↵erent teams to own cell-types on the same list.

4.5.2   Example: TableRender implementation

  Figure 5: Implementation of the same list from Figure 4 using TableRender.

All the configurations of the di↵erent cells are specified in the same place. The
onRender()   method is similar to the cellForRow() method in the UIKit implementa-
tion, however each cell-type needs to specify its own onRender() method unlike the
per-list approach that pure UIKit-lists use. The didSelect() method is mapped
directly to the didSelectRow() method on UIKit but is as all other configuration
options on TableRender configured per cell-type, this can be seen as an implemen-
tation of a component-oriented architecture [2]. The component-oriented model
allows for lower coupling and stronger cohesion.

                                         30
4.6    Mixed Lists

In modern applications, lists with many di↵erent cell-types are a common feature.
Di↵erent cell-types can be mixed in the same section or more commonly split
up inside di↵erent sections. To make use of the reuse mechanism in place on
table views, separate queues for di↵erent cell-types are needed. To implement this
with raw table views the developer would utilize multiple di↵erent reuse identifiers
[13]. Further many of the methods overridden by the delegate would also need
to consider the di↵erent cell-types to provide the correct configuration, this often
leads to conditional code in these methods that are hard to reason about and not
particularly cohesive.
   In TableRenderer the reuse identifiers are automatically inferred based on the
identity of the class that the cell is instantiated from however the application
developer can choose to override the reuse identifier if, for example, a single class
can produce two di↵erent types of cells.

                                         31
4.6.1   Example: Mixed lists

Suppose that we start with the following data model and want to create a list that
wants to perform di↵erent actions when the user presses the artist and song rows.
We also want the ability to customize the behaviour of the song and artist rows
di↵erently (Listing 17).

enum Cell: Hashable {
    case Artist(name: String)
    case Song(title: String)
}
struct Section: Hashable {
    let title: String
    let rows: [Cell]
}
let data: [Section] = [
    Section(
        title: "Artist and Songs I Like",
        rows: [.Artist(name: "David Bowie"), ...]
    ),
    ...
]

                           Listing 17: Example data model

4.6.2   Example: Mixed list with UIKit

In Listing 18 we can see that the specification of how the two table cells should
behave is spread out in di↵erent parts of the code. On lines 5 - 9 we start by
registering the cells to the table view, this will allow us to later use the reuse-
queue provided by UITableView. On lines 14 - 19 we handle the select event: here
we need to retrieve the item from the data to know what type of item it is and then
perform the action we want it to perform. On lines 21-38 the rows are dequeued
and configured with the data that they should represent.

                                        32
1   class ArtistCell: UITableViewCell { ... }
 2   class SongCell: UITableViewCell { ... }
 3

 4   class TableViewController: UITableViewController {
 5       func viewDidLoad() {
 6           super.viewDidLoad()
 7           self.tableView.register(SongCell.self, forCellReuseIdentifier: "song")
 8           self.tableView.register(ArtistCell.self, forCellReuseIdentifier: "artist")
 9       }
10       func numberOfSections(in tableView: UITableView) -> Int { return data.count }
11       func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
12           return data[section].rows.count
13       }
14       func tableView( _ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
15           let item = data[indexPath.section].rows[indexPath.row]
16           switch item {
17              case .Artist(let name): print("pressed on artist " + name)
18              case .Song(let title): print("pressed on song " + title)
19          }
20       }
21       func tableView(_ tableView: UITableView, cellForRowAt ip: IndexPath) -> UITableViewCell {
22           let item = data[ip.section].rows[ip.row]
23           switch item {
24               case .Artist(let name):
25                   let cell = tableView.dequeueReusableCell(
26                       withIdentifier: "artist",
27                       for: indexPath) as! ArtistCell
28                   cell.textLabel?.text = name
29                   return cell
30               case .Song(let title):
31                   let cell = tableView.dequeueReusableCell(
32                       withIdentifier: "song",
33                       for: indexPath) as! SongCell
34                   cell.textLabel?.text = title
35                   return cell
36           }
37       }
38   }

     Listing 18: Example: An implementation using the pure UITableView implemen-
     tation.

                                           33
4.6.3   Example: Mixed list with TableRender

     The same functionality could be implemented by using TableRender in the follow-
     ing way:
 1   class ViewController: UITableViewController {
 2       var tableBinder: TableRender!
 3       override func viewDidLoad() {
 4           tableBinder = TableRender(tableView: tableView)
 5

 6           tableBinder.render { ctx in
 7               for section in data {
 8                   ctx
to a function. Another detail to notice is that no opaque strings are required to
dequeue table cells, the user of the library does not have to consider registration,
dequeuing, and disposal cells at all.

4.7    Flexibility and Extension Points

Table Render allows for easy access to common table view API’s. When using the
platform native API’s the developer implements delegates on a per list basis that
handles events and styling. This means that the configuration methods are the
same for all rows. Individual styling and event handling can be implemented by
using the index path that the configuration method was called with and with that
information decide how the specific row should behave.
   Instead of implementing delegates and handling events and styling on a per
list level, Table Render allows the developer to specify callbacks and customise
appearance on a per-cell level. All customization options that are traditionally
configured trough UITableViewDelegate and UITableViewDataSource are speci-
fied trough methods on the cell. The class that will produce a view can also be
provided by the user to create rows with custom layouts. The API allows for all
aspects of a cell to be specified in the same place.

4.8    Data updates

A common feature of lists in modern applications is that the data is dynamic
i.e the contents of the list change over time. The library makes it easier for
the application programmer to develop lists that need to update based on data
changes. When developing lists with raw UITableViews the application developer
needs to know how the data changed from the previous dataset and notify the table
view based on that. To make it easier for the developer table renderer provides
a solution that handles diffing and reconciliation internally and let the developer

                                         35
tableRender.render { context in
    context
struct ContentView: View {
    var body: some View {
        List {
            Section(header: Text("Important tasks")) {
                TaskRow()
                TaskRow()
                TaskRow()
            }

              Section(header: Text("Other tasks")) {
                  TaskRow().onPress {
                      alert()
                  }
                  TaskRow()
                  TaskRow()
              }
          }
      }
}

          Listing 21: Example of a list with two sections built with SwiftUI

5.1.1     Lists

SwiftUI features a List component that can be used to create a list with sec-
tions. The SwiftUI API is based on closures and function builders. The way that
TableRenderer handles configuration via builder-pattern like methods on the rows
are inspired by Swift UI. In the example above we can see how to set up an onPress
handler which is similar to the way that the same action is done in TableRenderer.

5.2       React

React is a library initially developed at Facebook that later became open-sourced
[21]. React was created for use in web-development but since its inception, the
library has evolved to operate on other platforms as well. The library aims to

                                         37
provide a declarative and component-oriented application programming interface
for developing user interfaces. To build user interfaces with react, the developer
constructs small components which are independent reusable pieces of the user
interface. The smaller components like buttons, text fields, and headers can then
be composed into larger components, and these components can be composed into
even larger components and so forth ultimately forming an application. These
components form a tree, usually with smaller components in the nodes and larger
components near the root. To structure changes in requirements based on user
interaction and other events, user interfaces written with React utilize a one-way
data flow i.e. data is passed down through the tree while events bubble up through
the tree, every component in the tree can hold state but it can only be passed to
that components children’s, the parent can only receive events from the child
nodes.
   TableRender is like many other libraries developed to provide declarative user
interfaces somewhat inspired by the React way of thinking, although this project
is much more specialized both in scope and execution while the React project is
highly generic.

5.3      Cycler

Cycler is an open-source library developed by Square [23]. The goal of this project
and Cycler is in many ways similar. The library acts as a wrapper around Androids
platforms RecyclerView and provides a declarative API for application developers.
The library in its authors’ words claims that it ”allows you to easily configure an
Android RecyclerView declaratively in a succinct way”. While this project and
Cycler share many of the same goals the structure of cycler di↵ers from that of
this project. Cycler works by first creating the definition of every type of cell.
Each definition has a method forItemsWhere which decides what cell a particular

                                        38
row should render, that gets called for each row in the list. The data are provided
later, this is the main di↵erence between cycler and TableRender. In table render
the data and definition of the cell are provided at the same time, this means that
no mapping function has to be provided.

5.4    Di↵able Data Source

In iOS 13 Apple released a new method of managing the data source on table and
collection views. UITableViewDi↵ableDataSource replaces the UITableViewData-
Source and comes with a few new features that make it easier for the application
developer to update the underlying data and have it synced to the user interface.
Instead of having to calculate the changes in data and calling methods on the
table view based on that information the di↵able data source is utilizing a more
declarative approach. To apply an update with a di↵able data source the developer
creates an empty snapshot. The snapshot is then filled with the desired sections
and rows and can later be applied to the table. When the snapshot is applied to
the table view the platform will di↵ and reconcile the user interface with the new
snapshot. This technique requires the developer to not have to reason about the
previous state of the list. This is similar to the way that TableContext works in
TableRenderer. Di↵able data sources are only available in iOS 13 and later.

5.5    Litho

Litho is a framework developed at Facebook for building efficient Android user
interfaces in a declarative way [20]. The framework was built to implement complex
scrolling user interfaces. Litho provides a declarative API similar to that of React.
Litho provides interfaces for building lists that are called Sections. The problems
that Sections are aiming to solve are complex lists with multiple view types, data
sources, and nesting of lists. Sections are an abstraction over Androids Recycle

                                         39
You can also read