The Myth That SwiftUI Will Replace UIKit
Let's get this straight right away: if someone tells you SwiftUI is the future and UIKit is legacy, don't believe them. Apple itself continues writing critical parts of its apps in UIKit. Open Settings on your iPhone - that's UIKit. Music? UIKit with sprinkles of SwiftUI. Photos? Same story.
Why is this happening? Because SwiftUI, despite all its advantages, can't handle certain tasks as efficiently as UIKit. I've seen projects where teams tried to write everything in SwiftUI "because it's trendy," then spent three months fighting navigation bugs and ended up rewriting in UIKit.
Over the past few years, SwiftUI has grown significantly. The Navigation API got better with NavigationStack and NavigationSplitView. We got proper scroll position tracking support. But the fundamental limitations haven't gone anywhere. When you need pixel-perfect control over layout or complex animation with precise synchronization between elements, SwiftUI starts struggling.
The truth is that in 2026, both frameworks have a right to exist. The question isn't what to choose, but when to use what. And most importantly - do you even need to pick just one?
Where SwiftUI Really Wins
SwiftUI shines when you need to quickly assemble UI from standard components. Declarative syntax isn't just pretty marketing words from Apple, it actually works.
Compare:
// UIKit
let label = UILabel()
label.text = "Hello"
label.textColor = .systemBlue
label.font = .systemFont(ofSize: 16, weight: .bold)
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
// SwiftUI
Text("Hello")
.font(.system(size: 16, weight: .bold))
.foregroundColor(.blue)
The difference is obvious. But it's not just about lines of code.
Live previews in SwiftUI - that's what made me actually fall in love with this framework. When you're writing a registration form and see changes in real-time without rebuilding the project, that saves hours. In UIKit you either launch the simulator every time or struggle with Interface Builder, which periodically breaks storyboards.
Yes, previews crash sometimes. Yes, after an Xcode update they might stop working for a couple days. But when everything works - it's incredibly convenient. You can set up multiple preview configurations with different data, screen sizes, dark/light mode simultaneously. Try doing the same in UIKit - you'll have to either write a playground or create separate view controllers for each case.
Cross-platform development - another SwiftUI ace. The same code works on iOS, macOS, watchOS, and - importantly in 2026 - visionOS. Apple is clearly betting on Vision Pro, and if you want to port your app there without a complete rewrite, SwiftUI gives you a head start.
Real-world example: I made a habit tracking app. The main screen was a list with checkboxes and statistics. Wrote the entire UI in SwiftUI in one day. Then the client asked for a macOS version. Added a couple #if os(macOS) checks to adapt the menu, and done. If this were UIKit, I'd have to write the entire macOS interface from scratch using AppKit.
Here's another point many people miss: state management in SwiftUI is built into the framework. @State, @Binding, @ObservableObject - these aren't just property wrappers, they're a whole philosophy of data management. In UIKit you have to build architecture yourself: MVVM, VIPER, Coordinator pattern. In SwiftUI basic cases work out of the box.
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
This code just works. No delegates, target-action, KVO, or notification center. Pressed button - state updated - UI redrawn. In UIKit the same task requires more code and explicit update management.
Personally, I use SwiftUI for:
- App settings (forms with toggles, sliders)
- Onboarding (five screens with animations between them)
- Simple forms (registration, login)
- Prototypes that need to be shown to clients in a couple days
- Dashboards with charts and statistics
- Any new features in existing apps where there are no strict performance requirements
When UIKit Is Still Irreplaceable
UIKit is older, which means more stable. There are no unexpected surprises that SwiftUI loves to deliver in every Xcode release.
Take gestures. SwiftUI has .gesture(), but try doing something more complex than a simple tap - for example, simultaneous pan and pinch recognition with different priorities. In UIKit it's UIPanGestureRecognizer + UIPinchGestureRecognizer with delegates, everything's clear and works predictably. In SwiftUI you'll have to build workarounds through UIViewRepresentable.
I had a project with a custom image editor. Users could zoom photos, rotate them, apply filters in real-time. On SwiftUI this turned into a nightmare. Gesture modifiers conflicted with each other, priorities didn't always trigger correctly. Rewrote it in UIKit in two days, and everything worked like clockwork.
Libraries. Almost the entire powerful iOS ecosystem is written for UIKit. Want to integrate a cool charting library like Charts (not Apple Charts, but third-party)? Most likely it's UIKit. Yes, you can wrap it through representable, but that's an additional layer of abstraction and potential lifecycle problems.
Performance control. In UIKit you know exactly when layout happens, when a view renders, you can optimize layoutSubviews, use drawRect for custom drawing. In SwiftUI the framework decides when to redraw something, and sometimes does it too often.
I encountered a situation where a complex SwiftUI view was redrawing 60 times per second for no visible reason. Profiling showed the problem was implicit state changes - somewhere deep in the hierarchy a @State was updating, and the entire subtree was redrawing. Debugging that was quite the quest. In UIKit this doesn't happen: if a view updates, someone explicitly called setNeedsDisplay or setNeedsLayout.
Another point: memory management. In SwiftUI it's very easy to catch a retain cycle through capturing self in closures. All those @StateObject and @ObservedObject add magic that sometimes doesn't work as you expect. In UIKit everything's explicit: there's weak, unowned, and you know exactly who's holding whom.
Collection views with custom layout. UICollectionView with custom UICollectionViewLayout is an incredibly powerful tool. You can do Pinterest-style masonry grids, circular layouts, complex transition animations between cells. SwiftUI has LazyVGrid and LazyHGrid, but they only cover basic cases. As soon as you need something non-standard - back to UIKit.
Accessibility. Strangely enough, in UIKit the accessibility API is more mature and predictable. In SwiftUI modifiers like .accessibilityLabel() work, but when you start doing something more complex - custom rotor actions, adjustable traits, grouped elements - UIKit gives more control.
I choose UIKit for:
- Custom controls with non-standard behavior (slider with non-uniform scale, circular progress with animations)
- Apps where every millisecond matters (video editors, games)
- Projects with minimum iOS 13-14 (SwiftUI there is too raw)
- Complex animations with
CALayer and Core Animation
- Working with large tables (thousands of rows with good performance)
- Integration with complex third-party SDKs
Hybrid Approach: Best of Both Worlds
The most reasonable solution - combine them. Apple itself does this, why shouldn't we? A hybrid approach lets you use SwiftUI where it's convenient and UIKit where it's necessary.
UIKit in SwiftUI via UIViewRepresentable
Say you need a rich text editor in a SwiftUI app. The standard TextEditor is too simple - no formatting, can't change fonts or colors. Ready-made SwiftUI solutions either don't exist or cost money. Take the proven UITextView and wrap it:
struct RichTextEditor: UIViewRepresentable {
@Binding var text: NSAttributedString
var placeholder: String
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.font = .systemFont(ofSize: 16)
textView.backgroundColor = .clear
textView.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
// Important: only update if text actually changed
if uiView.attributedText != text {
let selectedRange = uiView.selectedRange
uiView.attributedText = text
// Preserve cursor position
uiView.selectedRange = selectedRange
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: RichTextEditor
init(_ parent: RichTextEditor) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
// Update binding only when text changes
parent.text = textView.attributedText
}
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == parent.placeholder {
textView.text = ""
}
}
}
}
It works. But there are nuances. The most important - state synchronization between SwiftUI and UIKit can be painful. Make sure not to create unnecessary updates in updateUIView, otherwise you'll get an infinite redraw loop or lose focus in text fields.
Another example - custom map view with annotations. MKMapView from MapKit works great, but natively it's a UIKit component. Wrap it:
struct MapView: UIViewRepresentable {
@Binding var region: MKCoordinateRegion
var annotations: [MKPointAnnotation]
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
mapView.setRegion(region, animated: true)
// Update annotations only if changed
if mapView.annotations.count != annotations.count {
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotations(annotations)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
parent.region = mapView.region
}
}
}
SwiftUI in UIKit via UIHostingController
Reverse task: you have a large UIKit app, but a new settings screen is easier to write in SwiftUI. Or you need to add a beautiful dashboard with charts, and doing it in UIKit is pure hell.
class MainViewController: UIViewController {
func showSettings() {
let settingsView = SettingsView(onDismiss: { [weak self] in
self?.dismiss(animated: true)
})
let hostingController = UIHostingController(rootView: settingsView)
hostingController.modalPresentationStyle = .formSheet
present(hostingController, animated: true)
}
}
struct SettingsView: View {
@AppStorage("notifications") private var notifications = true
@AppStorage("darkMode") private var darkMode = false
var onDismiss: () -> Void
var body: some View {
NavigationView {
Form {
Section("Preferences") {
Toggle("Enable Notifications", isOn: $notifications)
Toggle("Dark Mode", isOn: $darkMode)
}
}
.navigationTitle("Settings")
.toolbar {
Button("Done", action: onDismiss)
}
}
}
}
This is simpler than UIViewRepresentable. Main thing - properly handle lifecycle to avoid memory leaks. SwiftUI view inside hosting controller holds strong references to all its @ObservedObject and @StateObject, so be careful with closures.
Another important point: sizing. UIHostingController doesn't always correctly determine its size. If adding a SwiftUI view as a child view controller, explicitly set frame or constraints:
let hostingController = UIHostingController(rootView: mySwiftUIView)
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
hostingController.view.heightAnchor.constraint(equalToConstant: 300)
])
hostingController.didMove(toParent: self)
I use a hybrid approach in most projects. The main architecture can be UIKit, but individual screens - SwiftUI. Or vice versa: SwiftUI base, but custom controls through UIViewRepresentable. Main thing - don't obsess over one technology and choose the right tool for the job.
I ran tests on iPhone 15 Pro, iPhone 12 mini, and iPhone SE 2020. Here's what I got.
Startup time:
- SwiftUI app (clean project): ~180ms
- UIKit app (clean project): ~140ms
SwiftUI is slightly slower on startup due to additional runtime and framework initialization. On new devices the difference is imperceptible, on old ones - you feel it. If your app needs to launch instantly (camera, calculator), every millisecond matters.
Memory footprint: When displaying a list of 1000 elements with images:
- SwiftUI
List: ~45MB
- UIKit
UITableView: ~38MB
Again SwiftUI loses, but not critically. The 7MB difference for modern devices with 6-8GB RAM is insignificant. But if you're making an app for emerging markets where users are on old iPhones, those megabytes can become a problem.
Scroll performance: Here it gets more interesting. Complex list with images, text, and buttons:
- SwiftUI on iPhone 15 Pro: stable 60 FPS (120 on ProMotion)
- SwiftUI on iPhone 12 mini: drops to 45-50 FPS during fast scrolling
- SwiftUI on iPhone SE 2020: drops to 40 FPS
- UIKit on all devices: stable 60 FPS
SwiftUI works better on new hardware, but if your audience is on old iPhones, UIKit gives more predictable results. The difference is especially noticeable when cells have asynchronous image loading or complex calculations.
Navigation stacks: Deep navigation (7-8 screens) in SwiftUI sometimes glitches - screens don't dismiss correctly, especially with custom transitions or when combining sheets, fullScreenCover, and NavigationStack. In UIKit navigation controller works like a Swiss watch for 15 years already.
Animations: Simple animations (fade, scale, rotation) in SwiftUI are written in seconds and work smoothly:
@State private var isAnimating = false
Circle()
.scaleEffect(isAnimating ? 1.5 : 1.0)
.animation(.spring(response: 0.5), value: isAnimating)
Beautiful, simple, works. But try making a complex sequence of animations with dependencies - for example, element A moves, then element B fades in, then both rotate. In SwiftUI you'll have to use withAnimation, DispatchQueue.asyncAfter and pray the timings align.
In UIKit through UIView.animate or CAAnimation you have full control. You can set exact curves, completion handlers, group animations. For simple cases SwiftUI is more convenient, for complex ones - UIKit is more reliable.
Concrete case: Instagram-like feed Made a test: feed with posts (image, author name, likes, comments). 500 posts, scroll through the entire list.
SwiftUI version:
- Memory: 65MB
- Frame drops: 15-20% on iPhone 12
- Code: ~150 lines
UIKit version:
- Memory: 52MB
- Frame drops: 2-3% on iPhone 12
- Code: ~300 lines
UIKit wins on performance but requires twice as much code. What to choose? Depends on priorities. If development speed matters more than the last few percent of performance - SwiftUI. If the app needs to fly even on old hardware - UIKit.
Decision-Making Checklist
Here's how I make decisions for each new feature or project:
Use SwiftUI if:
- Minimum iOS version of the project is 15+ (better 16+)
- Need a quick prototype or MVP
- UI consists of standard components (buttons, text, lists, forms)
- Planning to support visionOS, macOS, or watchOS
- Team is ready to deal with bugs and unstable API
- Performance on old devices isn't critical
- No dependency on complex UIKit libraries
- Project is new or you can isolate the feature into a separate module
Use UIKit if:
- Need to support iOS 13-14 (or even older)
- Performance on old devices is critical
- Complex custom gestures or animations required
- Strong dependency on third-party UIKit libraries
- No time for experiments with unstable API
- Need pixel-perfect control over layout
- Working with large data volumes (thousands of elements)
- App needs to be maximally stable (fintech, medicine)
Use hybrid approach if:
- Migrating from UIKit to SwiftUI gradually
- Have legacy code that works and you're scared to touch it
- Different screens have different requirements
- Want new features in SwiftUI but base in UIKit
- Need custom UIKit components in SwiftUI app
- Team knows both frameworks
Red flags for SwiftUI:
- Client requires iOS 13 support
- App lags even on new devices
- Xcode previews haven't worked for a week
- Spending more time fighting the framework than on features
- Need to integrate 10+ UIKit libraries
Red flags for UIKit:
- Writing 500 lines of code for a simple form
- Spending an hour on layout constraints for one screen
- Junior developer can't figure out storyboards
- Every UI change requires full rebuild
Conclusion: It's About Trade-offs, Not Trends
I manage several projects simultaneously. In one - pure SwiftUI, because it's an internal team tool with minimal performance requirements. In another - UIKit with small SwiftUI inclusions for simple screens like settings or onboarding. In the third - pure UIKit, because the client requires iOS 14 support and performance on old devices is critical.
Choosing a framework is about context, not hype. SwiftUI is a great tool for certain tasks. But it's not a silver bullet. UIKit isn't going anywhere for a very long time, and that's okay.
If SwiftUI solves your problem faster and more reliably - use it. If not - don't hesitate to write in UIKit. If you need both - combine them. Your job is to deliver a product that works, not follow trends from conferences.
Final advice: learn both frameworks. Even if you're a SwiftUI fanatic, knowing UIKit will make you a better developer. And vice versa. Understanding how UIKit works under the hood helps write more efficient SwiftUI code. And knowing SwiftUI patterns can improve your UIKit architecture.
In 2026, the best iOS developer isn't the one who only knows SwiftUI or only UIKit. It's the one who knows both and can choose the right tool for the specific task.