Fact Check And Common Misconceptions Around Comparing SwiftUI & Jetpack Compose

Hannah Olukoye
Quick Code
Published in
6 min readSep 14, 2022

--

Developers working with mobile applications have multiple UI options to choose from. There are clear advantages to using a declarative rather than an imperative UI.

Declarative UIs, in which you describe how the UI should look in a given state and the framework responds to changes as needed, have become more popular in recent years because they enable developers to create mobile applications more quickly with less code.

Two examples of such UI frameworks are SwiftUI and Jetpack Compose.

SwiftUI includes multiple APIs and other components for customizable app design across iOS, macOS, tvOS, and watchOS. Meaning, that you can now deploy your code anywhere by learning just one language and one layout framework. Jetpack Compose was designed to simplify development on Android, with automatic updates as an app’s state changes and access to Android APIs.

There are similarities and differences between these two frameworks. This article will compare the two so that you can determine which would be a better fit for your projects.

The comparison between SwiftUI and Jetpack Compose will focus on a few significant factors, such as the platform support they offer, the learning curve required for each, and the functionality they provide.

Jetpack Composable Functions

Composable functions are used to define the UI of your app programmatically. This means you don’t need to use XML files for the app's layout. All you need to make a composable function is to use the @Composable annotation to the function name.

Following is an example of the basic syntax of a composable function.

@Composable
fun HelloWorld() {
Text("Hello, World!")
}

To use this composable function, we call it inside this method as:

setContent {
HelloWorld()
}

Previously, this would have been displayed in an XML layout file (main_layout.xml) like this:

<LinearLayout 
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, World" />
</LinearLayout>

And the layout resource file would have been loaded in this way:

fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
}

SwiftUI Functions

In Swift UI, the code sample generated by Xcode would look something like this:

struct ContentView : View {
var body : some View {
Text("Hello, world!")
}
}

Whereas, previously when using UIKit, the same syntax would be written within the ViewController as:

class ViewController: UIViewController {
@IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
myLabel.text = "Hello, World!"
}
}

Similarities

In addition to their basic functionality, SwiftUI and Jetpack Compose offer other similarities.

  • They are both imperative/declarative — You then respond to user events and manually update the data. As the state changes, you decide how the UI should change. In iOS, this means you don’t have to “connect things” like in UIKit, StoryBoard, or Interface Builder, while in Android, you don’t need to load content from the XML layout file. Overall, you write much less code.
  • Both frameworks also offer better readability in the source code — In-built components of Jetpack Compose like Text and Button are also built out of composable functions, so you can more easily check the source code to see how each param is listed and how they work. In-built views in SwiftUI are similarly presented in Primitive views, though the source code in this case is less readable.

Differences

SwiftUI and Jetpack Compose do differ in notable ways.

  • For example, SwiftUI is packaged with the OS, while Jetpack Compose is a library — The running performance is better with SwiftUI because it’s pre-compiled in the system, but if you need to add a new feature to the framework, you need to wait for your project to configure the appropriate minimum version. In Android, if you want to add a feature to Jetpack Compose, you just need to upgrade your library version.
  • SwiftUI uses protocol implementation, while Compose uses a compiler plugin and the composition — The composition is an elegant way to delegate responsibility to another class, but it isn’t necessary for the SwiftUI approach, in which you add a `Modifier` equivalent to components.
  • Because of the strong coupling of SwiftUI with the system, it is hard to write an application that doesn’t respect the design of iOS. With Jetpack Compose, you can easily create a component which respects Material specifications but you need much more code to build an application with a beautiful UI.
  • SwiftUI isn’t open source and the documentation is slowly growing over time. Jetpack Compose is open source and is easily accessible by the public. One can check what is built inside the library to do the same, or similar, or for inspiration.
  • Even though preview functionality is available for both frameworks, SwiftUI shows you your changes instantly on the preview canvas with no refreshing required. Jetpack Compose, on the other hand, requires you to click a refresh button after making changes so that you can see the preview with the new changes.

Learning Curve Required for SwiftUI

While you could likely start using SwiftUI right away, to avoid limited support, limited acceptance, and limited API coverage, you need to keep UIKit in mind.

For instance, SwiftUI, which is only available on iOS 13 devices and later, was only revealed at WWDC 2019. This suggests that SwiftUI wouldn’t be able to support apps that require compatibility for iOS 12 or earlier. Since UIKit has been around for more than ten years, you should be able to find answers for nearly every issue you might encounter, as well as multiple libraries that offer extensions and customizations.

Still, if you’re currently using UIKit, you can make the switch to SwiftUI for current projects. The UIKit flow is shown below:

Here’s a sample code for a clickable button using this same flow.

class ViewController: UIViewController {  override func viewDidLoad() {class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: UIButton.ButtonType.system) as
UIButton
button.setTitle("Show data", for: UIControl.State.normal)
button.addTarget(self, action: #selector(self.buttonAction), for: .touchUpInside)
self.view.addSubview(button)
}
@objc func buttonAction(_ sender:UIButton!) {
print("Button pressed")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

If you convert this to SwiftUI, the flow changes to the following:

Changing the code for a custom button to look something like this:

struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Button {
print("Button pressed")
} label: { Text("Show data") }
}
}
}

Learning Curve Required for Jetpack Compose

As for Jetpack Compose, rewriting your whole UI for an existing project is one way to dive deep into trying it out. It can get frustrating when some of your legacy code is not fully supported by the current composable functions available, but for the initial setup, it’s tasking to migrate your entire screen to Compose.

@Composable
fun HelloWorld() {
Text("Hello, World!")
}

Compose also enables the addition of a composable to an already-existing view hierarchy. To accomplish this, simply add a ComposeView to your XML file as shown in the following example:

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, World" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

In the Activity, call the setContent method on this view to apply your composable to this part of your UI.

findViewById<ComposeView>(R.id.compose).setContent {
MaterialTheme {
Text("Hello, World!")
}
}

The use of a View in your composable hierarchy is something else you might want to consider. When you must use views for which there isn’t a comparable composable, like a map view, this can be helpful. When supplying these legacy views in your composable UI, you can use the AndroidView composable.

The Android XML setup will most likely never completely disappear, just as it is with SwiftUI and UIKit. The transition from the old standard to the new one will take a few years, similar to the Java to Kotlin switch, and you may still occasionally interact with older code.

Wrapping it up

One may argue that it is a good idea to use Compose for both Android and iOS, others would lean in to say it is important to respect the platform’s UI/UX guidelines, and Compose can’t build an application as SwiftUI does. In this case, none is master over the other.

Declarative UIs promises an intuitive, low-code app solution for mobile developers. Both SwiftUI and Jetpack Compose have a lot to offer. If you’re working with iOS and need instant functionality in your system, SwiftUI could be the right choice. If you’re working with either iOS or Android and you’d prefer an open-source tool with more customizability, then you should consider Jetpack Compose.

--

--