SwiftUI or UIKit for legacy apps: how to choose now
SwiftUI or UIKit for a legacy product depends on screen type, team experience, and custom UI work. Use this breakdown to pick a practical mix.

Why this choice gets messy in legacy apps
Choosing between SwiftUI or UIKit looks simple until you open a real legacy app. Most older products are mixed by default. One part was built five years ago, another was patched last quarter, and a few screens still depend on code nobody wants to touch before a release.
That is why teams almost never move everything at once. A full rewrite sounds neat, but it can wreck the roadmap. The team stops shipping product work, QA gets buried, and people spend weeks rebuilding screens that already worked well enough.
The hard part is usually not the visible UI. It is the old structure under it. A screen may seem like a good SwiftUI candidate, then you find custom navigation, shared UIKit views, delegate chains, old state logic, and special cases added after years of bug fixes. Rebuilding that stack can cost far more than expected.
Custom views make the choice even messier. Some legacy screens do strange but important things: nested scrolling, gesture-heavy interactions, dynamic sizing, or old animation code tied to business rules. UIKit often keeps those screens stable with less risk, even if SwiftUI looks nicer for new work.
The answer also changes from one screen to another. A settings page and a complex checkout flow do not belong in the same bucket. One may move fast with little downside. The other may break analytics, edge cases, or conversion if the rewrite goes wrong.
A simple example makes this clear. A team might rebuild a profile screen in a week, then lose a month on an old booking flow because it relies on custom containers and years of tiny fixes. That is why a screen by screen UI plan usually beats a clean-slate decision.
Legacy apps get messy because the framework choice is rarely about taste. It is about risk, speed, and how much old behavior the app still needs to keep.
Sort screens before you pick a framework
Start with an inventory of the app you already have. The SwiftUI or UIKit debate gets much smaller when every screen is on paper and each one has a clear job.
Most teams skip this step and talk about frameworks too early. That usually leads to broad rules like "move everything new to SwiftUI" or "keep the old app in UIKit," and both rules break down once you look at real screens.
A simple sheet is enough. Give each screen a few tags:
- type: form, list, dashboard, settings, media, checkout, admin
- business weight: earns money, affects conversion, or creates support work
- technical baggage: old SDKs, custom navigation, custom gestures, unusual views
- change risk: stable, fragile, or already known to be messy
- timing: planned for this year, later, or not planned at all
That sorting step reveals which screens are actually similar. A settings page and a profile form often belong in one group. A camera screen, a map screen, and a screen with deep custom gestures belong in another. A dashboard may look simple, but if it depends on old chart code or special transitions, it should not sit in the same bucket as a basic list.
Money and support load matter more than design taste. If a billing screen, onboarding flow, or account recovery page causes tickets every week, mark it early. Even if it is ugly under the hood, the team will touch it soon, so it deserves a clear migration choice.
Do the same for screens tied to old libraries or special views. Those are the places where mixed stacks often make sense.
One more filter saves a lot of time: separate screens you will change this year from screens you will leave alone. A legacy app might have 70 screens, but only 10 to 15 may matter for the next release cycle. That smaller set should drive the plan.
Screens that usually fit SwiftUI first
Start with screens that mostly show and edit data in familiar patterns. Settings pages, profile pages, and simple edit forms are good first picks because they use controls Apple already gives you. They also tend to have clear rules: load data, change a field, save, dismiss.
SwiftUI usually pays off sooner on screens built from standard lists, toggles, pickers, sheets, alerts, and simple detail views. When the state is easy to describe - loading, ready, saving, failed - the code often stays shorter and easier to update. In a legacy app, that matters more than chasing a full rewrite.
New flows are also strong candidates. A fresh onboarding path, a feedback form, or a lightweight subscription screen may change a lot in the next few months. SwiftUI makes layout changes faster, which helps when product decisions are still moving.
Good early candidates often include:
- account and settings screens
- profile and preferences pages
- simple create or edit forms
- new flows that the team expects to tweak often
- screens with modal sheets and standard navigation
Change rate matters as much as screen type. If the team expects to adjust copy, spacing, field order, or empty states every sprint, SwiftUI can save time. That is often the practical answer to the SwiftUI or UIKit question in an older app.
Keep the first test small. Add one SwiftUI screen inside the current app, ship it, and watch what happens during real work. You will learn how well the team handles state changes, reviews, and bug fixes without betting the whole app on a theory.
Screens that usually stay in UIKit longer
Some screens are expensive to move, even when the rest of the app is ready for change. If a UIKit screen already handles messy gestures, timing, and edge cases well, keep it there. Rewriting touch logic that already works often creates bugs users notice right away.
UIKit still makes sense for screens that lean hard on older Apple APIs or deep view customization. Camera flows, map screens with overlays, rich text editors, and collection views with unusual layouts often take less effort in UIKit, especially when your team already knows that code well.
- Screens with layered gestures, drag handles, or custom scrolling
- Camera and map flows with lots of system interaction
- Rich text editing screens with custom toolbars or selection behavior
- Dense grids and lists with complex layout rules
- Older flows with custom transitions between screens
Custom transitions need extra care. If your app has a checkout, booking, or sign-up flow with animations that already work, leave it alone during a packed release period. Those screens often affect revenue or support volume, so even a small regression can cost more than the rewrite saves.
A common mistake in the SwiftUI or UIKit debate is treating every old screen as technical debt. Some old screens are simply finished. If a checkout screen is stable, fast, and easy enough to maintain, the better choice this quarter may be to keep it in UIKit and spend time elsewhere.
Move these screens when a rewrite saves real time. That usually means the current code slows every release, blocks new features, or needs the same painful fix again and again. If none of that is true, keeping UIKit is not avoiding progress. It is just good product judgment.
One simple test helps: ask whether users will feel the benefit this year. If the answer is no, keep the stable UIKit screen and migrate something easier first.
Match the plan to your team
A framework choice looks technical, but the team usually decides whether it works. If two people can ship clean SwiftUI code and six people can safely change UIKit, that matters more than what the team likes in theory.
Start with a plain skills check. Count who can build and debug SwiftUI screens today without slowing the sprint. Then count who knows the current UIKit code well enough to change it without breaking old flows, odd navigation rules, or screen-specific fixes that never made it into docs.
If those numbers are far apart, do not force a big jump. The worst plan is learning a new UI framework while rewriting working screens under deadline pressure. Teams burn time on both problems at once, and quality drops fast.
A simple scorecard helps:
- Who can ship SwiftUI code with little review help
- Who can keep UIKit stable in the current codebase
- Who has worked on mixed screens using both frameworks
- Who can mentor others during the first few migrations
For the first mixed screen, pair one SwiftUI developer with one UIKit developer. That setup catches blind spots early. The SwiftUI side moves the new view forward, while the UIKit side protects navigation, lifecycle behavior, analytics hooks, and old edge cases that often get missed.
Hiring also matters. If your current team is UIKit-heavy and your local hiring pool is the same, a SwiftUI-first plan may look modern but still leave you short on people who can deliver this quarter. On the other hand, if you can hire SwiftUI developers faster than UIKit specialists, that changes the plan.
A good screen by screen UI plan follows the team you actually have, plus the team you can realistically add. Preference matters. Shipping matters more.
Look at the custom behavior you need this year
The roadmap matters more than taste. A legacy app can live with two UI frameworks for a while, but your team will feel every hard screen you pick this year.
Start by writing down the UI work product already wants. Do not hide it under "refresh the app." Name the real changes: a new pricing page, an editable dashboard, drag to reorder, live charts, camera input, custom transitions, or a split editor. That list usually settles the SwiftUI or UIKit debate faster than abstract pros and cons.
SwiftUI pays off fastest when screens change often but behave in normal ways. Settings, forms, onboarding, account pages, simple lists, and promo style screens usually fit well. If design wants layout changes every few weeks, SwiftUI often saves time.
UIKit still makes more sense when the roadmap depends on unusual behavior. Teams usually slow down in SwiftUI when they need precise gesture handling, layered animation, complex scrolling effects, strict text input behavior, or a screen that must match an older flow exactly. You can build all of that in SwiftUI, but the effort climbs fast once the interaction stops being standard.
Do not estimate build time alone. Count the full bill:
- QA time on older devices and awkward screen sizes
- accessibility fixes for VoiceOver, Dynamic Type, and focus order
- bug fixing after the first release
- rework if product changes direction in a few months
This part gets missed a lot. A screen that takes five days to build can still cost three extra weeks if QA finds gesture bugs, accessibility breaks, and layout issues late.
Recheck the choice when priorities shift. If product drops the fancy interaction and asks for faster layout updates, move that screen toward SwiftUI. If a simple page turns into a custom editing tool, keeping it in UIKit may save you a painful rewrite.
A simple way to decide screen by screen
Start with an app audit, not a rewrite plan. In a legacy app, one screen can be a clean form with simple state, while the next one has custom gestures, old layout hacks, and years of bug fixes. If you decide SwiftUI or UIKit for the whole product in one shot, you usually miss that difference.
Give every screen a simple score. You do not need a giant spreadsheet. A shared doc with a short note for each screen is enough.
- UI complexity: simple list and form, or heavy custom layout and animation
- Business risk: minor settings page, or revenue and signup flow
- Change rate: rarely touched, or updated every sprint
- Native dependencies: basic controls, or deep use of camera, maps, web views, and custom navigation
That score tells you where to start. Pick one pilot screen with low business risk and medium traffic. A settings page, profile edit screen, or read-only dashboard often works better than checkout, chat, or a screen full of custom interactions.
Keep shared models, networking, and business rules outside the UI layer before you touch the screen. That step matters more than the framework choice. If the view code owns too much logic, every migration gets messy fast. If the screen talks to a clean model layer, you can swap the UI with far less pain.
After the pilot, measure what changed. Track bug count, build time, and review time for the new screen for a couple of sprints. Also watch how fast the team can make a small UI change. If the new screen takes less effort to update and does not create fresh bugs, move to the next similar screen.
If the pilot does not save time, stop and adjust. A mixed plan beats a proud rewrite that burns a quarter and leaves the hardest UIKit custom screens untouched.
Example: a legacy app with mixed screens
Picture a wholesale ordering app that has been around for years. It has login, settings, order lists, a dashboard full of charts and filters, and a barcode scan flow used by warehouse staff. This is where the SwiftUI or UIKit choice gets practical fast. One app can need both.
Start with the screens that are stable and boring in a good way. Login, settings, and small edit forms usually move well to SwiftUI first. They have clear layouts, limited state, and fewer edge cases. A team can rebuild those screens, ship them inside the current app, and learn a lot without putting daily operations at risk.
The dashboard is a different story. If it has custom charts, sticky sections, heavy refresh logic, and a lot of gesture handling, keeping it in UIKit for now is often the safer call. The same goes for barcode scanning if the flow depends on camera control, timing, overlays, or hardware quirks. Those screens tend to hide the messy parts.
You also do not need a clean break. Many teams get better results by wrapping new SwiftUI screens inside the current UIKit navigation. Users keep the same app structure. The team avoids a full rewrite. Release risk stays lower.
A realistic plan might look like this:
- Release 1: move login and settings
- Release 2: move profile and simple edit forms
- Keep dashboard and scan flow in UIKit
- Track crash reports, UI bugs, and build speed after each release
After two releases, review what changed. Did the team ship faster? Did designers get fewer layout bugs? Did navigation stay easy to maintain? If the answer is yes, pick the next batch of low-risk screens. If not, slow down and fix the rough spots before you move anything bigger.
Mistakes that waste time
Old code is not the same as bad code. Teams lose months when they rewrite a stable screen just because it looks dated or nobody likes the style. If the screen loads fast, users understand it, and the bug count stays low, leave it alone for now. Spend that time on screens that block new work.
Navigation causes trouble even faster. A mixed app can use SwiftUI or UIKit without drama, but only if one person or one small group owns the rules. If one feature pushes with UIKit coordinators, another uses SwiftUI navigation stacks, and nobody decides where each approach belongs, the app starts to feel random. Deep links break, back behavior gets weird, and small changes take too long.
Business logic is another quiet time sink. A team builds a new SwiftUI screen, keeps the old UIKit screen for some users, and then copies formatting, validation, or state rules into both places. That feels quick for a sprint. It turns into double maintenance for the rest of the year. Put shared logic in one layer and let both UI frameworks call the same code.
QA work often gets treated like cleanup for later. That is a mistake. A migration changes focus order, voice labels, animations, dynamic type, and edge cases on older devices. A simple settings page may move over with little risk. A payment flow or a dense form needs real test time. If QA and accessibility are not in the estimate from day one, the schedule is already wrong.
One hard screen can also distort the whole decision. Imagine a legacy app with twenty list and detail screens, plus one strange editor with custom gestures, offline sync, and years of special cases. If the team tries that editor first and struggles, they may decide the whole migration was wrong. That is bad sampling. Judge the plan on the full mix of screens, not the ugliest one.
A shorter rule works well:
- keep stable screens that already do their job
- pick one navigation owner and one set of rules
- share logic outside the UI layer
- budget time for QA and accessibility from the start
- test the strategy on normal screens, not only the hardest one
That approach saves more time than a full rewrite that looks clean on paper but slows the team for six months.
Quick checks before you commit
If your team cannot answer a few basic questions in one meeting, the choice is still too fuzzy. That is usually the moment when teams argue about SwiftUI or UIKit in the abstract instead of deciding what to ship.
Start with the app you already have, not the app you wish you had. Name a few screens that clearly belong on each side. A simple settings page or read-only account screen often fits SwiftUI first. A screen with heavy gestures, old custom views, or tricky navigation may stay in UIKit for now.
Use this short check before you commit:
- Can the team name 2 or 3 screens that clearly fit SwiftUI, and 2 or 3 that should stay in UIKit?
- Do you know which screens must stay boring and stable this quarter because they affect revenue, support load, or daily use?
- Did you pick one pilot screen, one fallback option, and one stop point where you pause and review the result?
- Can design and QA handle a mixed stack without slowing releases or missing edge cases?
- Will this choice remove work within the next 6 to 12 months, or does it only create more migration work now?
The pilot, fallback, and stop point matter more than most teams expect. A pilot gives you one controlled test. A fallback keeps you from getting trapped if the rewrite drags on. A stop point forces a real review instead of wishful thinking.
Design and QA need a vote early. Mixed stacks can work well, but they add small costs: duplicate patterns, different animation behavior, and more test cases on the same user flow.
A good decision should make the next two releases easier. If it only sounds cleaner on a whiteboard, wait. For a legacy iOS app migration, that simple filter saves time, money, and a lot of rework.
Next steps for a mixed migration plan
Take the screen audit and turn it into a plain yes or no matrix. Keep it boring. For each screen, answer a few direct questions: does it change often, does it need heavy custom behavior, does the team know SwiftUI well enough, and will this screen block other work if you touch it now.
That quick pass usually gives you three buckets:
- Rebuild in SwiftUI now if the screen is simple, active, and likely to change again soon.
- Wrap it if the screen mostly works but the surrounding flow needs newer code.
- Leave it in UIKit if it is stable, deeply custom, or risky to touch before other work ships.
- Delay the decision if the product team still has open questions about the screen.
This is where many teams save time. They stop arguing about SwiftUI or UIKit in general and make a call one screen at a time.
Do not treat the first migration plan as final. Put a review date on the calendar before the first mixed release goes out. Four to six weeks is often enough to see where the team moved faster, where bugs showed up, and which wrapped screens started to feel awkward. Then update the matrix with real evidence, not guesses.
A mixed plan also needs one owner. Someone should keep the rules simple, track exceptions, and stop one-off framework choices from spreading through the app.
If you want an outside view, Oleg Sotnikov can review the app, the team, and the product roadmap as a Fractional CTO. That kind of review helps when the codebase has years of history and nobody wants a rewrite that drags on for months.
A good migration plan is not ambitious on paper. It is clear enough that the team can follow it during a busy release cycle.