Switching to Swift: The iOS Migration

Posted by on 14 August 2025

Apple released the SwiftUI framework at their Worldwide Developers Conference (WWDC) 2019. The new framework was a significant shift in iOS app development, promising faster development and more reactive user interfaces (UI). At the time, the FreeAgent iOS codebase was in much the same state as other iOS apps – using UIKit, an entirely different framework that had been in existence since 2008.

Whilst UIKit was, and still is, a reliable framework with a large developer community, FreeAgent made the decision to begin the migration to SwiftUI in 2022. Here are some examples of how the two frameworks differ and how SwiftUI is working well for us.

The benefits of SwiftUI

1. Declarative Syntax

Imagine someone is cooking dinner for you. If the person cooking were a professional chef, you would simply describe what you want to eat. If they had never cooked before, you might want to give them a recipe.

SwiftUI is the chef in this situation. Declarative syntax means that the framework focuses on the overall goal of the programme, and doesn’t require specific instructions. On the other hand, UIKit (the person new to cooking) uses imperative syntax – meaning instructions must be dictated for it to work. As such, SwiftUI is able to produce the same result with fewer instructions.

Essentially, declarative syntax allows you to do a lot more in a lot less time. It is also less overwhelming for beginners, and far easier to maintain – for the simple reason that declarative syntax is far more succinct.

2. Reactive UI

SwiftUI is a modern framework with simplified state management allowing for reactive changes to UI using features such as state variables and data binding.

State variables cause the UI to change when they change. Binding links two variables together, so that when one updates, so does the other. Combining these, you create multiple locations in which you can update the variable, and the UI will change reactively.

Here’s an example: you want to make a toggle switch. When it is on, you want to display the text “on”, and when off, you want to show the text “off”. To achieve this you need a state variable and a toggle (a built-in component to SwiftUI), where the isOn field is bound to the state variable.

@State private var isToggleOn = false 

public var body: some View { 
  Toggle("Toggle Switch", isOn: $isToggleOn)

 if isToggleOn { 
   Text("Toggle is on") 
 } else { 
  Text("Toggle is off") 
 } 
}

You have a reactive UI with just two components. When you switch the toggle, it will automatically trigger the text field to update because it is linked to the state variable. Using the dollar sign ($) ensures SwiftUI creates the binding to the state variable.

Contrast the simplicity to UIKit, where you must manually handle updating the UI. If you switch the toggle switch, you must then tell all other components to update manually.

3. Live Previews

Another nice feature is live previews. In your code editor you can view your UI in real time for minor changes like text colour or size. Not having to rebuild the entire FreeAgent mobile app in order to check for minor changes saves a huge amount of development time.

One of the most important features in SwiftUI is that it can operate alongside UIKit. Small sections of the code can be updated view by view, making future development in those areas significantly faster. This means a large upheaval to the codebase is unnecessary, and we can adopt the benefits of SwiftUI without immediately and fully committing to it.

An example from the iOS codebase

My project is titled Share to Smart Capture. Sharing general files to the mobile app from outside sources (e.g. the Photos app) is a pre-existing feature, but it is written in UIKit. This new feature means that users have the option to share files to Smart Capture – an area of the app that auto-extracts information from files such as images of receipts. 

The Share to Smart Capture feature has been a perfect opportunity to migrate the Share Extension to a SwiftUI implementation. Here are some of the differences between the old and the new screens.

1. The File Structure

The UIKit implementation has several files for different purposes: providers, which lay out the components; view models, which represent the state and the data of components; and the controller, which responds to events that may occur in the UI. There are multiple providers, to break down the code to a manageable level, and multiple view models – one for each component and one for the overall screen.
In contrast, the SwiftUI view and its logic are contained in one file (aside from the dependencies).

2. Reactive UI

We saw the toggle example above, where the state variable drove the UI responsiveness. When it was switched, the state variable automatically updated. In the new Share Extension, whether the file is private or shared is controlled by a similar toggle.

In UIKit, it isn’t quite that simple. First, the toggle is tapped. This triggers an event which calls the provider’s delegate (an object that allows it to notify another object about events). This tells a ViewModel that the toggle has been tapped. The ViewModel then updates its state, which triggers a ViewController to handle the change.

As you can tell, SwiftUI is a lot simpler with regards to implementing a reactive UI.

3. Readability

It’s also a lot more readable. Consider that the UIKit code explanation I gave was a simplified version. You still have to search multiple files, multiple functions and have a solid understanding of UIKit to understand the code. In contrast, you can understand the SwiftUI implementation just by looking at the toggle and anywhere the state variable linked to that toggle is accessed or updated. Below is an example of SwiftUI vs UIKit code for implementing a list:

import SwiftUI

struct FruitGridView: View {
    let fruits = ["Apple", 
                  "Banana",
                  "Cherry"]

    var body: some View {
        NavigationView {
            List(fruits, id: \.self) { fruit in
                Text(fruit)
                    .onTapGesture {
                        print("Item \(fruit) tapped")
                    }
            }
            .navigationTitle("Fruits")
        }
    }
}
import UIKit

class FruitViewController: UIViewController, 
			   UICollectionViewDataSource, 
			   UICollectionViewDelegateFlowLayout {
    
    @IBOutlet weak var collectionView: UICollectionView!

    let fruits = ["Apple", "Banana", "Cherry"]

    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "Fruits"
        
        collectionView.dataSource = self
        collectionView.delegate = self
    }

    // MARK: - UICollectionViewDataSource

    func collectionView(_ collectionView: UICollectionView, 
			numberOfItemsInSection section: Int) -> Int {
        return fruits.count
    }

    func collectionView(_ collectionView: UICollectionView, 
			cellForItemAt indexPath: IndexPath) 
				-> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(
				        withReuseIdentifier: "FruitCell"
				      , for: indexPath)
        cell.contentView.subviews.forEach { $0.removeFromSuperview() }

        let label = UILabel(frame: cell.contentView.bounds)
        label.text = fruits[indexPath.item]
        label.textAlignment = .center
        cell.contentView.addSubview(label)

        return cell
    }

    // MARK: - UICollectionViewDelegate

    func collectionView(_ collectionView: UICollectionView, 
			didSelectItemAt indexPath: IndexPath) {
        let fruit = fruits[indexPath.item]
        print("Item \(fruit) tapped")
    }

    // Optional: Layout
    func collectionView(_ collectionView: UICollectionView, 
		        layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: collectionView.bounds.width, height: 44)
    }
}

And this further emphasises the readability aspect: The Share Extension SwiftUI implementation is 184 lines shorter than the UIKit one, despite adding saving to Smart Capture, varying based on API results and showing more information on screen.

Challenges of migration

The migration to SwiftUI from UIKit is highly beneficial in terms of production speed and efficiency. However, balancing adding new features with migrating pre-existing ones is an ongoing challenge. Whilst it is quicker to code in SwiftUI, migrating from UIKit and simultaneously adding new features can be a slow process – but adding more UIKit that in the future will have to be changed isn’t ideal or any faster in the long term. So, while new features may be slower to develop temporarily, once the codebase is migrated the full benefits of SwiftUI should be evident.

Leave a reply

Your email address will not be published. Required fields are marked *