When Apple introduced SwiftUI at WWDC 2019, it promised a declarative, composable future for Apple platform development. For teams maintaining apps with hundreds of thousands of lines of UIKit code, that promise was both exciting and daunting. Five years on, many engineering organizations are now well into—or have completed—their migration journeys. The lessons they have learned are instructive not just for iOS developers, but for any software engineer navigating a major framework transition in a production environment.
This article distills hard-won insights from real-world migrations: what works, what breaks, and what engineering teams consistently underestimate.
The single most important lesson from large-scale UIKit-to-SwiftUI migrations is this: never attempt a full rewrite. The temptation to start fresh is understandable. SwiftUI's paradigm is sufficiently different from UIKit that piecemeal adoption feels awkward. But production applications carry years of institutional knowledge in their code: edge-case handling, accessibility accommodations, localization logic, and hard-learned performance fixes. A full rewrite discards all of that.
Apple's own interoperability APIs—UIHostingController and UIViewRepresentable—exist precisely because gradual adoption is the intended path. Teams that succeeded did so by identifying leaf-node views: self-contained UI components with minimal state dependencies. These are the right starting points. Migrate a custom button, then a card view, then a form section. Confidence and team familiarity grow alongside the SwiftUI surface area.
A useful heuristic: if a UIKit view controller contains more than 400 lines of code, it is not a candidate for direct SwiftUI replacement—it is a candidate for decomposition first.
UIKit is imperative: you tell the view what to do and when to do it. SwiftUI is reactive: the view responds to state. This distinction sounds philosophical until you hit it in practice, at which point it becomes a concrete engineering problem that blocks entire migration tracks.
Legacy UIKit applications often manage state informally—through delegate callbacks, notification observers, or direct property mutation scattered across view controllers. SwiftUI's property wrappers (@State, @ObservedObject, @EnvironmentObject) demand that state be explicit, owned, and unidirectional. Bridging these two models without first cleaning up the UIKit state architecture is a recipe for subtle bugs that are extremely difficult to reproduce.
Teams that fared best invested in a pre-migration phase: refactoring UIKit views to adopt a more explicit state model—often something resembling the Model-View-ViewModel (MVVM) pattern—before introducing SwiftUI at all. This work paid dividends beyond the migration itself, producing UIKit code that was cleaner and easier to test in the interim.
The Observation framework introduced in iOS 17 (leveraging Swift macros) significantly reduces the boilerplate burden of reactive state. For teams targeting iOS 17 and above, it is worth evaluating whether to adopt Observation from the outset rather than building around the older Combine-based patterns.
If state management is where migrations stall, navigation is where they collapse. UIKit's UINavigationController is imperative and highly customizable. Early SwiftUI navigation APIs were declarative but rigid, making it difficult to replicate complex navigation patterns—deep linking, programmatic stack manipulation, conditional flows—that production applications depend on.
Apple significantly improved this situation with NavigationStack and NavigationSplitView in iOS 16. These APIs introduced data-driven navigation paths, enabling programmatic control that was previously impossible in pure SwiftUI. However, a meaningful portion of the active device population still runs iOS 15, and supporting both APIs simultaneously requires careful abstraction.
Practical guidance from teams that navigated this successfully:
SwiftUI's rendering model can introduce performance characteristics that differ materially from UIKit, particularly in scroll-heavy interfaces. LazyVStack and LazyHStack defer view creation but do not recycle cells the way UICollectionView does—a distinction that has material implications for large data sets.
Establish performance baselines in UIKit before migration begins. Measure scroll frame rates, memory footprints, and launch times with instruments. Then re-measure after each significant SwiftUI adoption milestone. Performance regressions that are caught early are addressable; those discovered after a full migration are expensive to diagnose and fix.
The Equatable protocol and the EquatableView wrapper deserve attention here. SwiftUI re-renders views whenever their inputs change. Making view inputs conform to Equatable allows SwiftUI to skip re-renders when inputs are semantically unchanged—a high-leverage optimization in data-dense screens.
SwiftUI's tooling has improved substantially since 2019, but gaps remain. Xcode's SwiftUI previews are powerful when they work and deeply frustrating when they do not. Previews that depend on environment objects, complex data flows, or external services frequently fail without actionable error messages.
Teams with mature UIKit test coverage often discover that their XCTest-based UI tests do not transfer cleanly to SwiftUI surfaces. SwiftUI views do not expose the same accessibility hierarchy as their UIKit counterparts in all configurations, which can cause accessibility identifier-based tests to break silently.
Two practices help here: First, treat ViewInspector (an open-source Swift package for unit testing SwiftUI views) as a first-class testing tool rather than a stopgap. Second, adopt a strict policy of assigning explicit accessibilityIdentifier modifiers to all interactive SwiftUI elements from the first day of migration. Retrofitting identifiers later is tedious and error-prone.
Technical challenges are surmountable. The human challenges—team alignment, knowledge distribution, and morale—are often what determine whether a migration succeeds or stalls.
Engineers with deep UIKit expertise may resist SwiftUI not out of stubbornness, but out of legitimate concern: SwiftUI's framework bugs and API gaps are real, and the productivity advantages do not always materialize immediately. Their caution is a resource, not an obstacle. Involving skeptics in technical decision-making—particularly around state architecture and navigation—produces better outcomes than simply mandating adoption.
Designate SwiftUI migration work explicitly in sprint planning rather than treating it as background work. Invisible migration work is deprioritized the moment a product deadline appears. Explicit allocation signals organizational commitment and gives engineers permission to slow down and learn.
Migrating a large UIKit application to SwiftUI is not a single engineering decision—it is a series of decisions made over months or years, each shaped by the specific characteristics of the codebase, the team, and the user base. There is no universally correct migration strategy.
What the most successful teams share is not a particular technical approach, but a disposition: they treat migration as craft. They measure before and after. They invest in architecture before they invest in UI. They respect the knowledge embedded in their UIKit code. And they give their engineers the time and space to develop genuine fluency in a new programming model.
SwiftUI has matured considerably since its introduction. For most applications, the question is no longer whether to migrate, but how to do so without disrupting the users and teams that depend on the code you are changing. The answer, as it so often is in software engineering, is: carefully and incrementally.
Madhuri Latha Gondi is a Senior Mobile Engineer at Carnival Cruise Line specializing in enterprise iOS development, SwiftUI migration, modular mobile architecture, and scalable application design. With over 14 years of experience in mobile engineering across fintech, healthcare, travel, and consumer technology, Madhuri's focus is on building high-performance and maintainable mobile platforms. Madhuri is also a Senior Member of IEEE and actively contribute to the technology community through technical judging, peer review, and industry initiatives.
Disclaimer: The author is completely responsible for the content of this article. The opinions expressed are their own and do not represent IEEE’s position nor that of the Computer Society nor its Leadership.