1. Why UIKit in 2026

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 meet the non-functional requirement of Time-to-Text under 500ms.

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: The target is 500ms or less. In a screen-recording measurement (August 11, 2026, iPhone 16e, warm launch), the text field had focus 0.17 seconds after the tap and the keyboard was fully up at 0.4 seconds.

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 SwiftUI adds work for @StateObject initialization and body re-evaluation, so this app, which aims for the fastest launch, chose UIKit for its tightly controllable lifecycle (this is not a same-conditions measurement of the two).
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?
The target is under 500ms. The recorded measurement is a screen recording from the icon tap (August 11, 2026, iPhone 16e, warm launch): the text field had focus at 0.17 seconds and the keyboard was fully up at 0.4 seconds. During development we record intervals with os_signpost and analyze them in 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.