← Back to DevBytes

When to Choose SwiftUI Over UIKit

When to Choose SwiftUI Over UIKit

Since its introduction in iOS 13, SwiftUI has fundamentally changed how developers build user interfaces for Apple platforms. However, UIKit remains a mature, battle-tested framework that powers millions of existing apps. Deciding between the two is rarely a binary choice — it depends on your project requirements, team expertise, deployment target, and long-term maintenance goals. This tutorial walks you through the key considerations, practical trade-offs, and concrete examples to help you make an informed decision.

What Is SwiftUI?

SwiftUI is Apple's declarative UI framework that lets you describe your interface using simple Swift code. Instead of imperatively configuring views and updating them when state changes, you declare what the UI should look like for a given state, and SwiftUI handles the rendering and updates automatically.

UIKit, by contrast, is an imperative, object-oriented framework built around UIView and UIViewController. You create views, add them to a hierarchy, configure their properties, and manually respond to lifecycle and layout events.

Why the Choice Matters

Choosing the right framework affects more than just syntax. It influences development speed, onboarding time, code maintainability, and your ability to leverage new Apple features. SwiftUI can dramatically reduce boilerplate, but it also introduces constraints around customization, backward compatibility, and access to lower-level APIs. Understanding these trade-offs early prevents costly rewrites later.

When SwiftUI Is the Right Choice

New Projects Targeting iOS 15+

If you are starting a new app and your minimum deployment target is iOS 15 or later, SwiftUI is generally the best starting point. By iOS 15, most of the early rough edges had been smoothed out, and critical APIs like List, Form, navigation, and async life cycle support had matured significantly.

import SwiftUI

struct ContentView: View {
    @State private var items: [String] = ["Apple", "Banana", "Cherry"]

    var body: some View {
        NavigationStack {
            List {
                ForEach(items, id: \.self) { item in
                    Text(item)
                }
                .onDelete { indexSet in
                    items.remove(atOffsets: indexSet)
                }
            }
            .navigationTitle("Fruits")
            .toolbar {
                EditButton()
            }
        }
    }
}

The same screen in UIKit would require a UITableViewController, data source methods, editing delegate callbacks, and manual navigation controller setup — easily three to four times the code.

Rapid Prototyping and MVPs

SwiftUI excels when speed matters. Live previews in Xcode let you iterate on designs without running the full app. This makes it ideal for prototypes, internal tools, and minimum viable products where visual feedback loops are critical.

#Preview {
    ContentView()
        .preferredColorScheme(.dark)
}

Cross-Platform Apple Ecosystem Apps

If your app needs to run on iOS, iPadOS, macOS, watchOS, and tvOS, SwiftUI is the clear winner. A significant portion of your view code can be shared across platforms, something that is nearly impossible with UIKit (which does not exist on watchOS or tvOS in the same form).

Forms and Settings Screens

SwiftUI's Form container makes building settings and data-entry screens trivial. What used to require static table view cells and custom cells in UIKit is now a few lines of declarative code.

struct SettingsView: View {
    @AppStorage("notificationsEnabled") private var notificationsEnabled = true
    @AppStorage("theme") private var theme = "Light"

    var body: some View {
        Form {
            Section("Preferences") {
                Toggle("Enable Notifications", isOn: $notificationsEnabled)
                Picker("Theme", selection: $theme) {
                    Text("Light").tag("Light")
                    Text("Dark").tag("Dark")
                    Text("System").tag("System")
                }
            }
        }
    }
}

When UIKit Is Still the Better Choice

Supporting iOS 13 or Earlier

SwiftUI on iOS 13 is missing many essential features, including List reordering improvements, LazyVStack, AsyncImage, and reliable navigation APIs. If your deployment target must include iOS 13, UIKit is often more practical for complex interfaces.

Highly Customized, Pixel-Perfect Layouts

SwiftUI's layout system is powerful but opinionated. For apps requiring fine-grained control over text layout, custom collection view behaviors, or complex gesture recognizers that interact with scroll views, UIKit still offers more predictable control.

Large Existing UIKit Codebases

If you have a mature UIKit app, a full rewrite is rarely justified. Instead, adopt SwiftUI incrementally using UIHostingController to embed SwiftUI views, or UIViewRepresentable to wrap existing UIKit components.

import SwiftUI
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }
        let window = UIWindow(windowScene: windowScene)

        let hostingController = UIHostingController(rootView: ProfileView())
        window.rootViewController = hostingController
        window.makeKeyAndVisible()
        self.window = window
    }
}

struct ProfileView: View {
    var body: some View {
        Text("Embedded SwiftUI in a UIKit window")
            .padding()
    }
}

Advanced Text and Custom Drawing

While SwiftUI has Canvas and TextEditor, apps with rich text editing, complex Core Text layouts, or custom PDF generation often still rely on UITextView, TextKit, and Core Graphics directly through UIKit.

How to Mix SwiftUI and UIKit

Wrapping UIKit Views in SwiftUI

Use UIViewRepresentable to bring UIKit components into SwiftUI. This is useful when you need a specific control that SwiftUI does not yet provide, such as MKMapView or a custom camera preview.

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    @Binding var region: MKCoordinateRegion

    func makeUIView(context: Context) -> MKMapView {
        let mapView = MKMapView()
        mapView.delegate = context.coordinator
        return mapView
    }

    func updateUIView(_ view: MKMapView, context: Context) {
        view.setRegion(region, animated: true)
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, MKMapViewDelegate {
        let parent: MapView
        init(_ parent: MapView) { self.parent = parent }
    }
}

Embedding SwiftUI in UIKit

Conversely, UIHostingController lets you present SwiftUI views from UIKit view controllers, enabling a gradual migration path.

let swiftUIView = FeatureView(viewModel: FeatureViewModel())
let hostingController = UIHostingController(rootView: swiftUIView)
navigationController?.pushViewController(hostingController, animated: true)

Best Practices

Conclusion

SwiftUI and UIKit are not adversaries — they are complementary tools in the Apple developer ecosystem. For new projects targeting modern iOS versions, SwiftUI offers faster development, cleaner code, and cross-platform potential that UIKit cannot match. For apps with legacy constraints, highly customized interfaces, or complex text and drawing needs, UIKit remains a robust and reliable choice. The most pragmatic approach is often hybrid: use SwiftUI where its declarative power shines, and reach for UIKit when you need precise control. By understanding the strengths and limitations of each framework, you can make architectural decisions that keep your codebase maintainable and your development velocity high for years to come.

— Ad —

Google AdSense will appear here after approval

← Back to all articles