How we started with a custom keyboard, hit iPadOS walls, and shipped a privacy-first composer that feels right with Magic Keyboard.
We set out to ship an a Phonetic keyboard for iPad. The on-screen part worked—but iPadOS doesn’t let third-party keyboards reliably remap hardware keyboards system-wide. Rather than ship a half-solution, we pivoted to an iPad app—Composer—that delivers fast phonetic typing (especially with Magic Keyboard), autosave + notes, a key layout guide, and a strict offline, no-tracking stance. It’s now approved for the App Store.
The Problem we are addressing
If you type Armenian, a phonetic layout (QWERTY → Armenian letters) is second nature. iPadOS, however, doesn’t include Armenian Phonetic. Some third-party keyboards exist, but many ship with broad data-collection clauses. We wanted:
• A layout Armenians actually use: phonetic
• Privacy-first: offline, no analytics, no accounts
• works with hardware keyboard (Magic Keyboard)
Phase 1 — The Keyboard Extension
Goal: A system keyboard you can pick in any app.
CSV → keys on screen
We defined the layout in a CSV (layer, row, pos, latin_key, label_shown, output_unicode…). Our first speedbumps:
• BOM/line endings: Our parser choked on a stray BOM and CRLFs; we fixed detection, normalized headers.
• “White keyboard”: The extension booted with zero keys when the CSV failed. We added a visible error label + a minimal fallback layout to avoid blank screens.
• Constraints: Initial height locks fought UIKit; we switched to gentle constraints and rendered after metrics settled (rotation/split view).
Signing, bundles, and install
Real-device testing surfaced the usual:
• Duplicate bundle identifiers (parent/extension must be prefixed correctly).
• Embed & Sign for the extension.
• CFBundleExecutable missing (Info.plist configured incorrectly).
• UIDeviceFamily warnings (set Targeted Device Family = iPad; remove plist override).
All fixable—and the on-screen phonetic keyboard worked.
Then came the hard stop: hardware keyboards
With a Magic Keyboard attached, our number/symbol row still typed digits and punctuation in other apps. We tried intercepting UIKey and HID usages in the extension, but on device the OS routes physical keys through the Hardware Keyboard Layout—before third-party keyboards can remap them. In short: iPadOS does not let a third-party keyboard extension globally remap a hardware keyboard across apps.
We could ship an on-screen keyboard only… but that’s not the experience we wanted.
Phase 2 — The Pivot to Composer
Decision: Build an iPad app where we control the editor, so hardware keys map exactly the way Armenian phonetic writers expect. Then make it painless to copy/share text into any app.
What we built
• Phonetic mapping for letters and punctuation (with your exact bottom row and number-row behavior).
• Smart punctuation: Auto-capitalize after Armenian period (։) and Return.
• Keyboard layout guide: Full-screen, segmented (Primary / Shift / .?123) with centered rows.
• Notes + autosave: Named notes, plus a background draft to protect your work.
• Copy/Share on the same line as Apple’s bar: We attached actions to inputAssistantItem so Apple’s predictive bar stays centered while our buttons sit left/right.
• iPad-only UI: Compact top bar (.?123, Shift, New, Save, Notes, Keyboard). No clutter.
The sticky UI bug that taught us patience
When returning to the app, iPadOS sometimes auto-scrolled the text view to make the caret “visible,” jolting you away from your place. We tried a handful of approaches:
1. Scroll to caret → fought manual scrolling (bad).
2. Anchor to a top character → better, still occasional drift.
3. Freeze viewport briefly on app foreground and block auto-scroll methods (scrollRangeToVisible, scrollRectToVisible, and surprise setContentOffset attempts), then restore the exact offset + selection.
Result: zero motion on return, consistent across devices.
Shipping details you don’t want to learn the hard way
• Privacy manifest (ITMS-91056): It must be a plist, not JSON. Include the required version keys:
• NSPrivacyCollectedDataTypesVersion = 1.0
• NSPrivacyAccessedAPITypesVersion = 2.0
• And leave both arrays empty if you collect nothing / access no required-reason APIs.
• Export compliance prompts: Set
ITSAppUsesNonExemptEncryption = NO
in Info.plist if you don’t implement crypto; it stops repeat questions.
• iPad-only: Use TARGETED_DEVICE_FAMILY = 2 and don’t hardcode UIDeviceFamily in Info.plist (Xcode overwrites it).
• Company/DSA: App Store Connect may block new app creation if Company Name (seller) or DSA trader info is missing at the account level. Fill those first or manually create the app record.
• TestFlight blank page: Usually “no processed build yet,” a pending agreement, role/permissions, or a browser extension blocking resources.
Composer today
• Armenian Phonetic typing (letters + punctuation)
• Magic Keyboard friendly
• Auto-caps after “։”
• Notes + autosave (local only)
• Copy/Share on the assistant bar
• Keyboard layout guide
• iPad-only and privacy-first