commit ffae96dd58cf720164c4468a8c98225675cb782c
parent 9710be2af08ea4fe161a4d71be872922e118b76c
Author: Michael Camilleri <[email protected]>
Date: Wed, 22 Apr 2026 00:40:43 +0900
Add keyboard priming
The iOS version occasionally exhibits animation hitches in the initial
pull-to-create gesture. Prior measurements suggest that this is due to
the keyboard start-up time. This commit attempts to prime the keyboard
during launch so that these costs are not visible to the user.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Diffstat:
3 files changed, 48 insertions(+), 0 deletions(-)
diff --git a/Listless.xcodeproj/project.pbxproj b/Listless.xcodeproj/project.pbxproj
@@ -92,6 +92,7 @@
BA6953E0EFE6F8255F05A3FD /* AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DD7EDA74DAAFA27C84CA08 /* AccentColor.swift */; };
BB16A28C4DA1695B722B45B2 /* ItemEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138DCA35ED82A745E4745175 /* ItemEntity.swift */; };
BDA8D53342F745B27B72B242 /* ItemRowDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A80C91DBDA44C879958098 /* ItemRowDragGesture.swift */; };
+ C58C877396361901A3E4489E /* KeyboardWarmup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86029FF31D90CFBA78BC8F60 /* KeyboardWarmup.swift */; };
C7C69D883B45F1B4CE979AF7 /* ItemListViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31EDB2C34C8B255000A2525 /* ItemListViewProtocol.swift */; };
C89D4C17F91AF91F18B6EF4E /* ItemStoreCompletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114A89FD89C8EFB5771B7242 /* ItemStoreCompletionTests.swift */; };
CA439FD953EA59A9664E0D74 /* CloudKitErrorClassifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C3B9FF63645B35E09CF1B1 /* CloudKitErrorClassifierTests.swift */; };
@@ -234,6 +235,7 @@
7C73E9D4C42CCABBF0F33543 /* Listless.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Listless.entitlements; sourceTree = "<group>"; };
82F2C024B6C1F2F1FD7A25B0 /* ItemListView+Logic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+Logic.swift"; sourceTree = "<group>"; };
848DBC251E2D2EB7BD089768 /* ItemListTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListTypes.swift; sourceTree = "<group>"; };
+ 86029FF31D90CFBA78BC8F60 /* KeyboardWarmup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardWarmup.swift; sourceTree = "<group>"; };
869254E16B52F36616416DB4 /* ItemListView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ItemListView+Toolbar.swift"; sourceTree = "<group>"; };
88C0D6F2667BD14F29CB84E5 /* ListlessiOSUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListlessiOSUITests.swift; sourceTree = "<group>"; };
9262207DAC21619BD9EDEE15 /* Listless.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Listless.entitlements; sourceTree = "<group>"; };
@@ -296,6 +298,7 @@
05AB46824DCDD04903EA4C82 /* ItemRowDragGesture.swift */,
E71FBC3E19D32F527B5FE9E6 /* ItemRowMetrics.swift */,
DCCE85438FE23E43095B2C25 /* ItemRowSwipeGesture.swift */,
+ 86029FF31D90CFBA78BC8F60 /* KeyboardWarmup.swift */,
E8B5E429B253183F887C5FD6 /* KeyCommandBridge.swift */,
361665491B2313B7A06A94B9 /* PerfSampler.swift */,
FA5D58EC1FBAA96E83A79445 /* PlatformScrollIndicatorsModifier.swift */,
@@ -917,6 +920,7 @@
12E43D0CD9124037022E3C38 /* KeyCommandBridge.swift in Sources */,
19699EC4FF57EF0D636B65E3 /* KeyValueSyncBridge.swift in Sources */,
F6587B84ECC6BFE92A5FB493 /* KeyboardNavigationModifier.swift in Sources */,
+ C58C877396361901A3E4489E /* KeyboardWarmup.swift in Sources */,
5B60B409CE4BA668DB30A65D /* Listless.xcdatamodeld in Sources */,
F0B2B806BD84A4F2FDF8E038 /* ListlessiOSApp.swift in Sources */,
7770D06CBDE3E87B7FDE7C21 /* PerfDebugView.swift in Sources */,
diff --git a/ListlessiOS/Helpers/KeyboardWarmup.swift b/ListlessiOS/Helpers/KeyboardWarmup.swift
@@ -0,0 +1,41 @@
+import UIKit
+
+/// Pre-pays the ~400ms `RemoteTextInput` / autocorrect subsystem spin-up that
+/// otherwise happens on the first real text field focus after a cold launch.
+/// Creates an offscreen `UITextField`, makes it first responder, then resigns
+/// immediately — enough to trigger the keyboard process to initialize without
+/// visibly animating anything.
+@MainActor
+enum KeyboardWarmup {
+ private static var didWarm = false
+
+ static func prime() {
+ guard !didWarm else { return }
+ guard let window = keyWindow() else { return }
+ didWarm = true
+
+ PerfSampler.shared.measure("Keyboard.warmup") {
+ let field = UITextField(frame: CGRect(x: -100, y: -100, width: 1, height: 1))
+ field.autocorrectionType = .default
+ field.autocapitalizationType = .sentences
+ window.addSubview(field)
+ field.becomeFirstResponder()
+ field.resignFirstResponder()
+ field.removeFromSuperview()
+ }
+ }
+
+ private static func keyWindow() -> UIWindow? {
+ for case let scene as UIWindowScene in UIApplication.shared.connectedScenes {
+ if let key = scene.windows.first(where: \.isKeyWindow) {
+ return key
+ }
+ }
+ for case let scene as UIWindowScene in UIApplication.shared.connectedScenes {
+ if let first = scene.windows.first {
+ return first
+ }
+ }
+ return nil
+ }
+}
diff --git a/ListlessiOS/ListlessiOSApp.swift b/ListlessiOS/ListlessiOSApp.swift
@@ -152,6 +152,9 @@ struct ListlessiOSApp: App {
.onChange(of: appearanceMode, initial: true) { _, newValue in
applyAppearanceMode(newValue)
}
+ .task {
+ KeyboardWarmup.prime()
+ }
.overlay(alignment: .top) {
Color.outerBackground
.opacity(0.9)