Migrating from UIKit to SwiftUI

Norhan BoghdadiNorhan Boghdadi
5 min read

How It All Started

The spark for our migration journey ignited when we set out to revamp our application with a stunning new design. We aimed to not only refresh the user experience but also embrace the latest advancements in iOS development. It was during this creative overhaul that we decided to experiment with SwiftUI, Apple's revolutionary framework for building user interfaces.

Step-by-step Migration Process

Migrating an application isn't an overnight process. It requires careful planning, iterative development, and rigorous testing. Initially, we thought we’d just sprinkle some SwiftUI components into our existing views and call it a day. But then we did our homework and found a better way: embedding full SwiftUI views into our structured views. This allowed us to gradually migrate our codebase without a complete rewrite. Here's the magical utility function we conjured up to embed SwiftUI views within UIKit:

func addSubSwiftUIView<Content>(_ swiftUIView: Content, to view: UIView) where Content: View {
    let childView = UIHostingController(rootView: swiftUIView)
    addChild(childView)
    childView.view.frame = view.bounds
    view.addSubview(childView.view)
    childView.didMove(toParent: self)
}

With this nifty piece of code, we were able to blend the elegance of SwiftUI with the familiarity of UIKit, like peanut butter and jelly—only more technical and way less edible.

The Trials and Triumphs

We faced a lot of difficulties, especially since none of the team members were experts in SwiftUI—far from it, actually. Here are some of the fun (and not-so-fun) hurdles we had to jump over:

  1. Building SwiftUI Views:

    We needed to learn SwiftUI at warp speed. At first, we relied on a tool called Figma to Code, which magically transformed our designs into code. Sounds great, right? Well, it was... until we discovered that it added complications, making our views static with fixed heights and widths. Despite these issues, it was a crucial starting point that helped us understand the SwiftUI view structure. By dissecting the generated code, we quickly learned the basics and gained confidence in building SwiftUI views from scratch.

  2. iOS 13 Compatibility:

    Supporting iOS 13 felt like trying to fit a square peg into a round hole. Many SwiftUI views and features were off-limits. For instance, we had to get creative with text fields since @FocusState wasn’t available. Creating OTP fields was particularly challenging, especially with automatic fill from messages. The best solution was to use legacy text fields conforming to UIViewRepresentable and manually manage focus and move it from one field to the other. Talk about a brain teaser!

  3. Managing State and Data Flow:

    Integrating SwiftUI with UIKit was like trying to herd cats. We used callbacks to manage state and data flow between the two frameworks. In SwiftUI, state management is usually a breeze with ObservableObject and property wrappers like @State, @ObservedObject, and @EnvironmentObject. We used ObservableObject to keep our shared state in sync between the UIKit view controller and the SwiftUI view.

Despite these challenges, we managed to keep our sanity and make significant progress. It wasn't always pretty, but we learned a lot and grew stronger as a team. And hey, what's a good story without a few plot twists?

But Why?

At the start of the revamp, I was diving headfirst into a codebase that was a tangled mess of XIBs—a nightmare for someone who prefers writing code. Drag-and-drop components with endless constraints? No, thank you! SwiftUI came to the rescue, allowing us to write everything programmatically and giving us an interactive preview of our creations.

To give you an example, working with UITableView and UICollectionView in UIKit was always a hassle, especially when handling the DataSource and all the boilerplate code that came with it. In SwiftUI, it’s much simpler. Just throw in a List, a ScrollView, or LazyStacks, and voilà, you have exactly what you need. No more wrestling with delegates and data sources!

Once we got the hang of SwiftUI, implementing features became a breeze—quick, intuitive, and a lot more fun. It turned a laborious process into something we could do in the blink of an eye.

Now What?

As mentioned earlier, we initially used callback functions to communicate between layers. The callback was in the main view, but we needed to inject it into our SwiftUI view for proper communication. Now, we’re sneaking in some publishers and subscribers to replace injecting callbacks in the SwiftUI view. Here's an example of how we’re doing it:

if let id = transaction.id {
    viewModel.transactionsViewModel.detailsPublisher.send(id)
}
// This example shows passing a String through the ViewModel from the SwiftUI view.

viewModel?.transactionsViewModel.detailsPublisher.sink { [weak self] id in
guard let self = self else { return }
self.callback?(.transactionDetails(id: id))
}
.store(in: &cancellables)
// This part happens in the UIKit view, where it listens and then calls the callback function to the next layer.

We are also planning to shift to Combine, not to just improve communication between SwiftUI and UIKit, but also streamline our data flow and state management. This change promises to make our application more modular and easier to maintain. It’s all like adding a dash of magic to our code, making everything more efficient and responsive. The future looks bright, and we can't wait to see where this journey takes us!

Final Thoughts

Migrating to SwiftUI is a journey filled with its own set of challenges and rewards. For our team, the move has led to cleaner, more maintainable code and a faster development cycle. SwiftUI’s declarative nature not only made our UI code more intuitive but also unlocked new possibilities for cross-platform development.

As SwiftUI continues to evolve, we are excited to see how it will shape the future of iOS development. If you’re considering making the switch, we highly recommend starting small, leveraging both UIKit and SwiftUI where they shine best, and embracing the learning process. Remember, it’s a marathon, not a sprint, but the finish line is definitely worth it!

1
Subscribe to my newsletter

Read articles from Norhan Boghdadi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Norhan Boghdadi
Norhan Boghdadi