Introduction
WWDC2017, Apple introduced Codable, a powerful and convenient tool for working with data in Swift, and it is widely used in a variety of applications and contexts. A variety of applications, right ? SwiftUI, introduced in WWDC2019, as an intuitive framework for building user interfaces and provides a declarative syntax for creating user interfaces in a way that is easier to read and write than traditional imperative approaches. View in SwiftUI can conform to Codable, means that a view can be serialized and/or deserialized for storing, caching and ... networking.
What if we can request a View from a server ?
It's what we call Server-driven UI, it allows building user interfaces that are dynamic and responsive to changes in data on the server. This is particularly useful in modern applications, where data is often generated or updated in real-time and needs to be reflected in the user interface as quickly as possible.
Server-driven UIs also make it easier to build applications that are scalable and maintainable. Basically, the server is responsible for managing and updating the data that is displayed in the user interface, it is easier to make changes to the user interface without having to update the client-side code. This can save time and effort, and it can also help to prevent inconsistencies between the data on the server and the data displayed on screen.
In this blog post, I will introduce you to some key aspect and pseudo-implementation of Server-Driven UI using SwiftUI.
Approach
First and foremost, implementations differs based on real word problems we intended to solve, the context of its usage and the trade-offs to consider. This project was intended to explore the de-serialization
of a view, it’s properties and user-interactions that are retrieved from the server. A JSON content are grabbed from the server as a model structure of the view and rendered on-screen.
The class diagram below is used to model the objects that make up the serialization and rendering engine, to display the relationship between the components, and to describe what kind of data, configurations is used.
Let’s briefly define each object model:
Window: used as a parent container, holds the view hierarchy and implements different global configuration, ex: Device Orientation, Scroll wrapper, etc...
Container: defined by a frame, a configuration, an action, and wraps one global Layout
.
Layout: Main content layout of type `Z`(aka ZStack), `V`(aka VStack), …
Content View: An array of sub-views that are recursively rendered inside the container.
To summarize, a Window object is requested from the server, wraps a container made of a global layout and sub-containers. Each layout contains a set of primitive SwiftUI.View
with a configuration and a set of actions.
Fundamentals
This approach only made possible thanks to the new Layout type introduced in SwiftUI 3. The container object will hold layouts conforming to the new Layout protocol, which offers containers based on server configuration.
Views conforms to Codable
protocol and have multiple configuration properties that are handled by SwiftUI.ViewModifier
.
Implementation
After defining each object model conforming to the diagram, we need to properly decode and build a SwiftUI View based on the API response, render it correctly on the screen using given configuration.
Configurations will be implemented using native or customViewModifier
.
Window
A server response is wrapped in aWindow
components that mimic SwiftUI.WindowGroup
, handles global view configurations and call the render method of its component to generate the view's hierarchy.
Container
Each container have its own layout grouper, a set of primitive Views that conform to ViewRenderer
protocol. Containers can also implement different actions.
Layout
First window’s container, implements the method protocol LayoutRenderer.render()
function to tell how they are drawn on screen based on the configuration and returns any Layout
conforming object.
Using LayoutRenderer
will return a generic composable view of type HStackLayout
, VStackLayout
, ZStackLayout
that will hold, arrange the sub-views.
View
Represent a primitive SwiftUI.View
with a configuration. Configuration such .frame
, Padding
, cornerRadius
are handled by SwiftUI.Modifier
.
List of supported Views are currently limited for testing purposes, but It should be easy to implement more.
Screen rendering
An engine acts as a view model, responsible for data request from server and decode it, returns a window, and it’s sub-views that are rendered on screen.
RequestAndRenderView()
function actually runs a basic URLSession.request
and decode data using a JSONDecoder
as follows:
let decoder = JSONDecoder()
let window = try decoder.decode(Window.self, from: data)
return window
In this section, a pseudocode is presented to keep this post about the approach clear and understandable. In the following section, we will show on-screen basic UI Components requested from an API.
Hands On
For the examples below, a side by side comparison between the API JSON
response and the resulting view shown on screen.
Example 1: Basic
Let’s have a vertical aligned Image with a text description.
Example 2: Scrollable content
Let's slightly adopt the above basic view and show it in a horizontal scrollable list.
Example 3: Multiple views layout
For this last example, let’s combine multiple views requested each from separate APIs.
Challenges
Server driven UI have its own benefits and its drawbacks. First, let’s talk about benefits
Benefits
- Improved scalability: examples shown above demonstrates how an application can be extended, scaled up or down to accommodate changes or user demand.
- Easy to maintain, easy to deploy: making changes and updates to the interface can happen without requiring a new submit to the App Store and wait for a review or even users to download new versions of the application, it’s an instant deployment.
- A/B Testing: can run simultaneous number of tests based on criteria’s, this will fuel data analytics.
- Security: Application doesn’t hold any kind of data or view hierarchy in its source code, reverse engineer it’s binary will actually return an empty container.
Drawbacks
- Performance: While the backend has to process each user request and generate a new page or a fragment, servers under heavy load can alter the user experience and make the application less responsive.
- Performance 2: Response payload can return a big amount of data, which causes a high memory footprint used by our application to decode, especially if there are many components to render.
- Data flow: Most of the views rendered serves as an output, but managing user inputs such as actions, data input or interactions can be quite challenging and complex.
- Complexity: Can be more complex to implement and maintain, since they require the development and deployment of both server-side and client-side code. Moreover, Each native UI component has to be extended, correctly adapted to conform to API response.
Summer up
Server-driven UI is an approach that relies on the server to generate and deliver the user interface, rather than the client’s application. This approach allows for the creation of dynamic and interactive apps, as well as the ability to reuse the same UI code across different clients. In this article, we discussed the basics and the fundamentals of server-driven UI, showed pseudo-implementation, provided examples of how it can be used in code using SwiftUI, and talked about the faced challenges, including its advantages and disadvantages.
Conclusion
In this post, we introduced a new emerging paradigm, explained one of many approaches and used native Apple frameworks to achieve the result. As mentioned earlier, implementations can differ from one to another, based on real word problem. This approach made it easy to process, build and render correctly, however, it also has its challenges and drawbacks, including potential performance issues and complex implementations.
Thank you for tuning in, I hope it provided some useful information and insights about this approach. As a software engineer with a passion for writing about complex computer science subjects, I strive to make technical topics accessible and interesting to a wide audience. I welcome any feedback or questions you may have, and I look forward to sharing more of my knowledge and experiences with you in the future.
Reach me out on Twitter.