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.

Design Decision: UIKit was chosen to reliably meet the non-functional requirement of Time-to-Text under 500ms. Real device measurements consistently achieve 200-300ms.

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.

Measurement Results: Target 500ms or less → Actual device measurement: stable at 200-300ms. From the moment a user taps the app icon to text input readiness takes only 0.2-about 1 second.

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

Q. Can't SwiftUI achieve sub-500ms launch?
It's not impossible, but UIKit is reliably faster by milliseconds. There's overhead from @StateObject initialization and body re-evaluation. For maximum speed, UIKit's deterministic lifecycle has the advantage.
Q. What are the downsides of not using Storyboard?
UI visualization becomes code-based. You lose Interface Builder's drag-and-drop design capability. However, for a memo app with a simple UI, code-based UI construction presents no issues.
Q. What are the real device measurements?
Against the target of under 500ms, real device measurements are stable in the 200-300ms range. Measured at nanosecond precision with os_signpost and analyzed in detail with Instruments.
Q. Plans to migrate to SwiftUI?
The critical path (launch → input → send) will remain UIKit. Non-critical screens like settings may be considered for SwiftUI adoption.