UIKit vs SwiftUI: Why We Chose UIKit for Launch Speed
1. Why UIKit in 2026
In 2026, the majority of new iOS apps are built with SwiftUI. Apple emphasizes SwiftUI's evolution at every WWDC, and the convenience of declarative UI is beyond question.
However, this app has a non-negotiable requirement: Time-to-Text (the time from launch to text input readiness) must be under 500ms.
SwiftUI introduces overhead inherent to declarative UI frameworks: @StateObject initialization, body re-evaluation, and .onAppear timing control. While negligible for most apps, these become significant when optimizing at the millisecond level.
In contrast, UIKit's viewDidAppear to becomeFirstResponder() path is deterministically faster. The ViewController lifecycle is explicit, giving precise control over when the keyboard appears.
2. Why We Completely Eliminated Storyboard
Choosing UIKit alone wasn't enough. Storyboards also introduce overhead that impacts launch performance.
- XML Parsing Cost: Storyboards are XML files internally, requiring parsing at launch time
- Segue Resolution Cost: Resolving segue configurations for screen transitions takes time
- Unnecessary Complexity: A simple memo app doesn't need Storyboard's visual design tools
Instead, we instantiate ViewControllers directly in SceneDelegate.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
PerformanceLogger.shared.signpostBegin(name: "SceneConnect")
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let composeVC = ComposeViewController()
let navController = UINavigationController(rootViewController: composeVC)
window.rootViewController = navController
window.makeKeyAndVisible()
PerformanceLogger.shared.signpostEnd(name: "SceneConnect")
}
This approach achieves zero Storyboard parse time and zero segue resolution. By instantiating ComposeViewController directly from SceneDelegate, all unnecessary processing is eliminated from the launch path.
3. One Line in viewDidAppear Completes Everything
UIKit's greatest strength is its explicitly defined ViewController lifecycle. When viewDidAppear(_:) is called, the view is fully rendered on screen. Calling becomeFirstResponder() at this precise timing minimizes the delay to keyboard display.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
activateTextInput()
}
private func activateTextInput() {
textView.becomeFirstResponder()
PerformanceLogger.shared.endTimeToText()
}
Measurement is performed at nanosecond precision using os_signpost. PerformanceLogger.shared.endTimeToText() records the signpost end event, enabling detailed analysis in Instruments.
4. Performance Measurement Infrastructure
Design decisions are based on quantitative data, not subjective impressions of "feeling fast."
- PerformanceLogger: A custom logger wrapping
os_signpost, measuring each phase of launch, send, and UI updates - Instruments Integration: Nanosecond-precision tracking via Xcode's os_signpost Instrument
- Key Metrics: Time-to-Text (launch to input ready), Send-to-Reset (send button to UI reset)
- Continuous Monitoring: Performance measurement on every development build for immediate regression detection
Performance is a feature. Users don't explicitly demand "speed," but they unconsciously stop using slow apps. That's why continuously managing performance through numbers matters.
5. Are There Cases for Using SwiftUI?
It's not UIKit-or-nothing. There are areas within the app where SwiftUI could be appropriate.
- Settings Screens: Non-critical screens that don't affect launch speed could benefit from SwiftUI's declarative productivity
- Critical Path Definition: The core flow of launch → compose → send must remain UIKit
- Hybrid Complexity: Mixing UIKit and SwiftUI requires careful management of UIHostingController and data flow consistency
- Current Decision: The memo app's UI is simple enough that a pure UIKit approach maintains codebase consistency
If SwiftUI's performance improves further in the future, there's room for re-evaluation. But UIKit on the critical path is the policy for the foreseeable future.
Frequently Asked Questions
@StateObject initialization and body re-evaluation. For maximum speed, UIKit's deterministic lifecycle has the advantage.os_signpost and analyzed in detail with Instruments.References
- Apple Developer — UIKit Documentation — Official reference for the framework chosen to build Simple Memo - for Obsidian's UI
- Apple Developer — SwiftUI Documentation — Declarative UI framework evaluated as an alternative and measured for performance
- Apple Developer — os_signpost / OSSignposter — Performance measurement API integrated with Instruments, used for Time-to-Text measurement
- WWDC 2023 — Analyze hangs with Instruments — WWDC session referenced for UI responsiveness optimization