キーボードは、ユーザーがアプリで最も長い時間を過ごすインターフェースのひとつです。にもかかわらず、日本のApp Storeで「AIキーボード」として本格的に動作するアプリはまだ少数派です。Rork MaxはSwiftUIとネイティブAPIへの深いアクセスを提供しているため、他のノーコードツールでは不可能な「カスタムキーボード拡張(Keyboard Extension)」の構築が可能です。
Rork MaxでiOS Keyboard Extensionをゼロから構築し、Claude APIによるAI文章補完・多言語翻訳・スマートテンプレート挿入を実装した上で、App Storeで審査を通過させ、サブスクリプションで収益化するまでの全工程を順を追って整理していきます。実際に動作するコードを3つ以上掲載し、よくある落とし穴10選も網羅しました。
なぜ「AIキーボード」が今、個人開発者の最大のチャンスなのか
キーボードアプリの市場は、ここ3年で大きく変化しました。GboardやSwiftKeyのような大手が独占していた時代は終わりつつあり、「特定のユーザー層に特化したAIキーボード」への需要が急増しています。
実際に国内外のApp Storeを調べると、医療従事者向けの医療用語補完キーボード、プログラマー向けコードスニペット挿入キーボード、SNS投稿文を自動生成するキーボードなど、ニッチ特化のAIキーボードが月額課金で着実に収益を上げています。
Rork Maxがこの領域で強力な選択肢になる理由は3つあります。第一に、SwiftUIネイティブコードを生成できるため、UIInputViewControllerを継承したカスタムキーボードのターゲットを追加できます。第二に、Claude APIやOpenAI APIとの連携コードをRork Maxが生成し、AIによる文章補完ロジックを短時間で実装できます。第三に、App GroupsによるContaining AppとExtension間のデータ共有も、適切なプロンプトで構築可能です。
チャンスの大きさを数字で見ると、「gboard 代替」「AIキーボード iPhone」のキーワードは月間数万回の検索があり、課金ユーザーの獲得コストも一般的なアプリより低い傾向にあります。理由は単純で、「一度気に入ったキーボードはなかなか替えない」という強力なスイッチングコストが発生するからです。
Rork Max でキーボード拡張を始める前に知っておくべき iOS の制約
Keyboard Extensionは、iOSで最も制約の多い拡張機能のひとつです。実装前に必ずこれらの制約を理解してください。
ネットワークアクセスの制限 : Keyboard Extensionはデフォルトでネットワークアクセスが禁止されています。Claude APIなどのクラウドAIを呼び出すには、ユーザーが「設定 → 一般 → キーボード → フルアクセスを許可」をオンにする必要があります。これを怠ると、AI機能が一切動作しません。
メモリ制限 : Extensionに割り当てられるメモリは約60MBです。大きなモデルをオンデバイスで動作させることは現実的ではありません。クラウドAPIとの連携が基本設計になります。
App Groupsによるデータ共有 : メインアプリとExtension間でユーザー設定・APIキー・カスタム辞書などを共有するには、App Groupsコンテナを使う必要があります。UserDefaultsの標準コンテナはExtensionから読み取れません。
バックグラウンド処理不可 : Extensionはバックグラウンドでは動作しません。AI補完リクエストは、ユーザーがキーボード上でアクションを取るたびにフォアグラウンドで実行されます。
pasteboard制限 : Full Accessなしの状態ではクリップボードの読み取りができません。
これらの制約はApp Store審査でも確認対象になります。特に「Full Accessが必要な理由」をPrivacy Policy・App Store説明文・オンボーディング画面で明示しないとリジェクトされます。
App Extension ターゲットの追加と基本構成(Rork Max での設定)
Rork MaxでKeyboard Extensionを構築する際の具体的な手順を解説します。
まず、Rork Maxに以下のプロンプトを入力してプロジェクトの骨格を生成します:
iOSカスタムキーボード拡張機能アプリを作ってください。
要件:
- メインアプリ(Containing App)とKeyboard Extension(App Extension)の2ターゲット構成
- App Groupsを使ったUserDefaultsでの設定共有
- UIInputViewControllerを継承したカスタムキーボードView
- SwiftUIのView構造でキーボードUIを構築
- ネットワークリクエストのためのFull Access確認ロジック
Bundle IDプレフィックス: com.yourcompany.aikeyboard
App Group ID: group.com.yourcompany.aikeyboard
生成されたコードの構造を確認してください。正しく構成されている場合、以下のようなファイル構成になっているはずです:
AIKeyboard/
├── AIKeyboard/ ← Containing App
│ ├── ContentView.swift
│ ├── SettingsView.swift
│ └── OnboardingView.swift
└── AIKeyboardExtension/ ← Keyboard Extension
├── KeyboardViewController.swift
├── KeyboardView.swift
└── AIService.swift
KeyboardViewController.swiftの核心部分は次のようになります:
import UIKit
class KeyboardViewController : UIInputViewController {
private var hostingController: UIHostingController<KeyboardView> ?
// App Groupsから設定を読み込む
private let sharedDefaults = UserDefaults (
suiteName : "group.com.yourcompany.aikeyboard"
)
override func viewDidLoad () {
super . viewDidLoad ()
setupKeyboardView ()
}
private func setupKeyboardView () {
let keyboardView = KeyboardView (
hasFullAccess : hasFullAccess,
onTextInsert : { [ weak self ] text in
// テキストを現在のテキストフィールドに挿入
self ? .textDocumentProxy. insertText (text)
},
onTextDelete : { [ weak self ] in
self ? .textDocumentProxy. deleteBackward ()
},
onNextKeyboard : { [ weak self ] in
self ? . advanceToNextInputMode ()
},
onAIRequest : { [ weak self ] prompt, completion in
self ? . requestAICompletion ( prompt : prompt, completion : completion)
}
)
let hosting = UIHostingController ( rootView : keyboardView)
addChild (hosting)
view. addSubview (hosting. view )
hosting. view .translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint. activate ([
hosting. view .topAnchor. constraint ( equalTo : view.topAnchor),
hosting. view .bottomAnchor. constraint ( equalTo : view.bottomAnchor),
hosting. view .leadingAnchor. constraint ( equalTo : view.leadingAnchor),
hosting. view .trailingAnchor. constraint ( equalTo : view.trailingAnchor)
])
hosting. didMove ( toParent : self )
hostingController = hosting
}
// MARK: - AI補完リクエスト
private func requestAICompletion ( prompt : String , completion : @escaping ( String ) -> Void ) {
guard hasFullAccess else {
completion ( "フルアクセスが必要です" )
return
}
guard let apiKey = sharedDefaults ? . string ( forKey : "claude_api_key" ),
\ ! apiKey. isEmpty else {
completion ( "APIキーが設定されていません" )
return
}
Task {
do {
let result = try await AIService. complete ( prompt : prompt, apiKey : apiKey)
await MainActor. run { completion (result) }
} catch {
await MainActor. run { completion ( "エラーが発生しました" ) }
}
}
}
}
ポイントはhasFullAccessプロパティです。このプロパティがfalseの場合、ネットワークリクエストを一切試みてはいけません。App Storeの審査でも「Full Accessなしでネットワークにアクセスしようとするアプリ」はリジェクト対象です。
UIInputViewController を使ったカスタムキーボードUIの実装
次に、SwiftUIで構築するキーボードUIの実装です。キーボードUIは3つのレイヤーで構成します:
AIサジェストバー (上部): AI補完候補を3件表示
通常キー配列 (中央): QWERTY/かなレイアウト
ツールバー (下部): AI機能ON/OFF・キーボード切替・改行
import SwiftUI
struct KeyboardView : View {
let hasFullAccess: Bool
let onTextInsert: ( String ) -> Void
let onTextDelete: () -> Void
let onNextKeyboard: () -> Void
let onAIRequest: ( String , @escaping ( String ) -> Void ) -> Void
@State private var suggestions: [ String ] = []
@State private var currentInput: String = ""
@State private var isLoadingAI: Bool = false
@State private var selectedLayout: KeyboardLayout = .qwerty
var body: some View {
VStack ( spacing : 0 ) {
// AIサジェストバー
SuggestionBar (
suggestions : suggestions,
isLoading : isLoadingAI,
hasFullAccess : hasFullAccess,
onSelect : { suggestion in
onTextInsert (suggestion)
suggestions = []
currentInput = ""
}
)
// キー配列
KeyGrid (
layout : selectedLayout,
onKeyTap : { key in
handleKeyTap (key)
}
)
// ツールバー
KeyboardToolbar (
hasFullAccess : hasFullAccess,
onNextKeyboard : onNextKeyboard,
onDelete : onTextDelete,
onReturn : { onTextInsert ( " \n " ) }
)
}
. background ( Color (UIColor.systemGroupedBackground))
}
private func handleKeyTap ( _ key: KeyType) {
switch key {
case . character ( let char) :
onTextInsert (char)
currentInput += char
// 3文字以上入力されたらAI補完をリクエスト
if currentInput. count >= 3 && hasFullAccess {
fetchAISuggestions ()
}
case .delete :
onTextDelete ()
if \ ! currentInput. isEmpty {
currentInput. removeLast ()
}
case .return :
onTextInsert ( " \n " )
currentInput = ""
suggestions = []
case .space :
onTextInsert ( " " )
currentInput = ""
suggestions = []
}
}
private func fetchAISuggestions () {
guard \ ! isLoadingAI else { return }
isLoadingAI = true
onAIRequest (currentInput) { result in
// 結果を改行で分割して最大3件
let items = result. components ( separatedBy : " \n " )
. map { $0 . trimmingCharacters ( in : .whitespaces) }
. filter { \ ! $0 . isEmpty }
. prefix ( 3 )
suggestions = Array (items)
isLoadingAI = false
}
}
}
SuggestionBarでは、ローディング中のアニメーションと補完候補のタップ操作を実装します。AIの応答に0.5〜2秒かかることを考慮し、スケルトンUI(プレースホルダーアニメーション)を表示してユーザーに「考え中」であることを伝える点が肝心です。
Claude API / GPT-4o との連携:リアルタイムAI補完の実装
AI補完の核心となるAIServiceの実装です。Keyboard ExtensionからのAPIコールには、タイムアウトを短め(10秒以内)に設定する点が肝心です。
import Foundation
struct AIService {
static func complete ( prompt : String , apiKey : String ) async throws -> String {
let endpoint = URL ( string : "https://api.anthropic.com/v1/messages" )\ !
var request = URLRequest ( url : endpoint)
request.httpMethod = "POST"
request.timeoutInterval = 8 // キーボードは応答性が最優先
request. setValue ( "application/json" , forHTTPHeaderField : "Content-Type" )
request. setValue (apiKey, forHTTPHeaderField : "x-api-key" )
request. setValue ( "2023-06-01" , forHTTPHeaderField : "anthropic-version" )
let systemPrompt = """
あなたはiOSキーボードの文章補完AIです。
ユーザーが入力している文章の続きを3つ提案してください。
ルール:
- 各提案は1行で、改行で区切る
- 自然な日本語で補完する
- 元の文章を含めず、続きのみを提案する
- それぞれの提案は20文字以内
"""
let body: [ String : Any ] = [
"model" : "claude-haiku-4-5-20251001" , // 軽量・高速モデルを使用
"max_tokens" : 100 ,
"system" : systemPrompt,
"messages" : [
[ "role" : "user" , "content" : "入力中のテキスト: \( prompt ) " ]
]
]
request.httpBody = try JSONSerialization. data ( withJSONObject : body)
let (data, response) = try await URLSession.shared. data ( for : request)
guard let httpResponse = response as? HTTPURLResponse,
( 200 ... 299 ). contains (httpResponse.statusCode) else {
throw AIError.invalidResponse
}
guard let json = try JSONSerialization. jsonObject ( with : data) as? [ String : Any ],
let content = json[ "content" ] as? [[ String : Any ]],
let firstContent = content. first ,
let text = firstContent[ "text" ] as? String else {
throw AIError.parsingFailed
}
return text
}
enum AIError : Error , LocalizedError {
case invalidResponse
case parsingFailed
case networkUnavailable
var errorDescription: String ? {
switch self {
case .invalidResponse : return "APIからの応答が無効です"
case .parsingFailed : return "レスポンスの解析に失敗しました"
case .networkUnavailable : return "ネットワークに接続できません"
}
}
}
}
モデルの選択が重要なポイントです。claude-haiku-4-5を選ぶ理由は速度です。キーボードアプリでは「1秒以内に候補が表示される」ことがユーザー体験の生命線であり、高性能・低速のモデルはここでは逆効果になります。OpenAI APIを使う場合も同様にgpt-4o-miniを推奨します。
また、リクエストのデバウンス処理も必須です。ユーザーが高速タイピング中に1キーごとにAPIを呼ぶと、コストが爆発します。currentInput.count >= 3の条件に加えて、300〜500msのデバウンスを設けることを強く推奨します。
App Groups でメインアプリと安全にデータを共有する
APIキーやユーザー設定をContaining AppとExtension間で安全に共有するには、App Groupsを使います。Rork MaxはApp Groupsの設定コードも生成できますが、エンタイトルメント(Capabilities)の設定はXcodeで手動で行う必要があります。
// SharedUserDefaults.swift — 両ターゲットで共有するユーティリティ
struct SharedUserDefaults {
static let groupID = "group.com.yourcompany.aikeyboard"
private static var defaults: UserDefaults {
guard let defaults = UserDefaults ( suiteName : groupID) else {
fatalError ( "App Groupsが設定されていません。XcodeのCapabilitiesを確認してください。" )
}
return defaults
}
// MARK: - APIキーの保存・取得
// 注意: 本番環境では Keychain を使用すること
static var claudeAPIKey: String {
get { defaults. string ( forKey : "claude_api_key" ) ?? "" }
set { defaults. set (newValue, forKey : "claude_api_key" ) }
}
// MARK: - ユーザー設定
static var aiEnabled: Bool {
get { defaults. bool ( forKey : "ai_enabled" ) }
set { defaults. set (newValue, forKey : "ai_enabled" ) }
}
static var preferredLanguage: String {
get { defaults. string ( forKey : "preferred_language" ) ?? "ja" }
set { defaults. set (newValue, forKey : "preferred_language" ) }
}
static var customTemplates: [ String ] {
get { defaults. stringArray ( forKey : "custom_templates" ) ?? [] }
set { defaults. set (newValue, forKey : "custom_templates" ) }
}
// MARK: - サブスクリプション状態
static var isPremiumActive: Bool {
get { defaults. bool ( forKey : "is_premium_active" ) }
set { defaults. set (newValue, forKey : "is_premium_active" ) }
}
}
重要な注意点 : APIキーをUserDefaultsに平文で保存することはセキュリティ上のリスクがあります。本番アプリでは、メインアプリ側でAPIキーをKeychain(kSecAttrAccessGroupで共有)に保存し、Extension側からも同じアクセスグループで読み取るパターンを採用してください。ユーザーがAPIキーを持ち込む「BYOK(Bring Your Own Key)」モデルならこの構成が最適です。
独自のバックエンドでAPIキーを管理する場合は、Extensionからバックエンドのプロキシエンドポイントにリクエストを送り、バックエンド側でClaudeにリレーする構成にすることで、APIキーをデバイスに保存しない設計にできます。
よくある落とし穴と解決策:ハマりやすいポイント10選
Keyboard Extension開発で実際に詰まりやすいポイントを、解決策とともにまとめます。
1. Extension が起動しない("Open Keyboard" が表示されない)
→ NSExtensionPrincipalClassがplistに正しく設定されているか確認。Rork Maxが生成したコードにKeyboardViewControllerの完全なモジュール名が入っているか確認してください。
2. hasFullAccess が常に false を返す
→ 実機テスト必須。シミュレーターではhasFullAccessが正確に動作しない場合があります。実機で「設定 → 一般 → キーボード → キーボードを追加」からキーボードを追加し、フルアクセスをオンにしてテストしてください。
3. ネットワークリクエストがタイムアウトする
→ Extension内のURLSessionはデフォルトの設定では動作しないことがあります。URLSession(configuration: .ephemeral)を使い、タイムアウトを明示的に設定してください。
4. App GroupsのUserDefaultsが共有されない
→ XcodeのCapabilitiesで両ターゲット(Containing App + Extension)に同じApp Group IDを追加したか確認。Bundle Identifierがcom.yourcompany.aikeyboard.extensionのように正しくサフィックスされているか確認してください。
5. キーボードの高さが固定されない
→ viewDidLoad()ではなくviewWillLayoutSubviews()でViewの高さを制約に追加してください。推奨高さはポートレート216pt、ランドスケープ162ptです。
6. テキスト入力フィールドによってキーボードが異なる動作をする
→ textDocumentProxy.keyboardTypeを確認し、.numberPadや.emailAddressなど特殊なキーボードタイプに対応したレイアウト切替を実装してください。
7. メモリエラーでExtensionがクラッシュする
→ 画像リソースをExtensionターゲットに含めすぎないようにしてください。重い画像はメインアプリのみに含め、ExtensionはSF Symbolsと最小限のカスタムアセットのみを使用します。
8. insertText が効かないフィールドがある
→ 一部のサードパーティアプリはtextDocumentProxyを実装していません。この場合はUIPasteboardへのコピーにフォールバックするロジックを追加しておくと親切です。
9. 審査でリジェクトされる(ガイドライン2.5.4)
→ フルアクセスが必要な機能と不要な機能を明確に分離し、フルアクセスなしでも基本的なキーボード入力ができるようにしてください。フルアクセスは「AI機能のために必要」と明示します。
10. Keyboard Extensionがデプロイ後に消える
→ TestFlightやApp Storeからインストールした場合、「設定 → キーボード」から手動で追加が必要です。オンボーディング画面でこの手順を動画付きで案内しましょう。
サブスクリプション課金と Full Access の組み合わせ実装
AIキーボードアプリの収益化には「フリーミアム + サブスクリプション」モデルが最も効果的です。設計は次のように分けます:
無料プラン(Full Access不要) : 基本的なカスタムキーボードレイアウト、絵文字クイックアクセス、1日5回までのAI補完
プレミアムプラン(月額・年額) : 無制限AI補完、多言語翻訳、カスタムテンプレート作成・共有、広告なし
StoreKit 2とRevenueCatを組み合わせたサブスクリプション管理の実装をContaining App側で行います:
import RevenueCat
// AppDelegate または App.init() で初期化
func setupRevenueCat () {
Purchases. configure ( withAPIKey : "YOUR_REVENUECAT_API_KEY" )
// サブスクリプション状態をApp Groupsに同期
Purchases.shared. getCustomerInfo { (customerInfo, error) in
if let error = error {
print ( "RevenueCat エラー: \( error. localizedDescription ) " )
return
}
let isPremium = customerInfo ? .entitlements[ "premium" ] ? .isActive == true
SharedUserDefaults.isPremiumActive = isPremium
}
}
Extension側ではRevenueCatを直接使わず、SharedUserDefaults.isPremiumActiveを参照するだけにします。RevenueCat SDKのメモリフットプリントは大きく、Extensionの60MB制限を圧迫するためです。
サブスクリプション状態の更新タイミングも重要です。ユーザーがサブスクリプションを購入した直後、アプリをバックグラウンドに移してキーボードを開いても、Extension側のSharedUserDefaultsはまだ更新されていないことがあります。これを解消するには、Containing AppのRevenueCatリスナーで購入完了を検知し次第、App Groupsへの書き込みをトリガーする設計にします。
App Store 審査通過のための必須チェックリスト
Keyboard Extensionはレビューチームの特別な注意を受けるカテゴリです。以下のチェックリストを全て満たしてから申請してください。
プライバシー関連(最重要) :
プライバシーポリシーURLをApp Store Connect・アプリ内両方に設定していること
キーストロークのログを取っていない旨を明示(または実際に取らない設計にする)
フルアクセスが必要な理由をApp説明文に英語・日本語で明記していること
App Privacy Labelで"Keyboard Input"を正しく申告していること
機能要件 :
フルアクセスなしでも基本的なテキスト入力が動作すること
「次のキーボードに切替」ボタン(地球儀アイコン)を必ず表示していること
ダークモード・ライトモードの両方で視認性が確保されていること
Dynamic Type(文字サイズ変更)への対応(キーのラベルが小さくなりすぎないこと)
技術要件 :
Extensionのバイナリサイズが制限内であること(通常60MB以下)
クラッシュしないこと(当然ですが、Apple Intelligenceキーボードとの衝突に注意)
审査チームがキーボードをシステム設定から有効化できること(README等で手順を記載しておくと親切)
よくあるリジェクト理由への対策 :
Guideline 2.5.4(Extensions must not access non-shared userDefaults): 標準のUserDefaultsではなくApp Groups UserDefaultsを使用
Guideline 5.1.1(Data Collection): キーストロークを第三者に送信しないこと
収益化シミュレーション:月収100万円への道筋
AIキーボードアプリで月収100万円(約6,700ドル)を達成するための試算を示します。
プレミアムプランの価格設定 :
月額: ¥480(App Storeの価格ティア)
年額: ¥3,600(月額の25%オフ相当)
必要な有料ユーザー数 : 月収100万円 ÷ ¥480 = 約2,083人(月額プランのみの場合)
ダウンロード数→課金率の目安 :
キーボードアプリの平均課金転換率: 2〜5%(一般的なアプリより高い傾向)
転換率3%なら: 2,083 ÷ 3% ≒ 69,000ダウンロードが必要
流入戦略の優先度 :
App Store SEO : 「AIキーボード」「文章補完 キーボード」「スマートキーボード iPhone」などのキーワードをタイトル・サブタイトルに最適配置
コミュニティ起点のバイラル : X(旧Twitter)でのAI補完デモ動画がバズりやすい。「使ってみた」系コンテンツがオーガニック拡散を生む
ニッチ特化で競争回避 : 「ライター向けAIキーボード」「医療従事者向け用語補完」など特定職業・用途に特化すると競合の少ないカテゴリで存在感を持てます
LTV最大化の設計 :
キーボードはデイリーアクティブ率が非常に高いカテゴリです。一度習慣化されたキーボードのチャーン率は月2〜3%と低く、年額プランへの誘導で年間LTVを高めやすいのが強みです。
無料→有料への転換を促す最も効果的な導線は「使用上限に達した瞬間」です。1日5回の無料AI補完を使い切ったタイミングでPaywallを表示し、「プレミアムで無制限に」という訴求を行うとコンバージョン率が高まります。
Rork Maxが生成するRevenueCat連携コードと、Keyboard Extensionの組み合わせで、このマネタイズ設計を実装してみてください。3〜6ヶ月の継続的なASO改善と、SNSでの定期的なデモ動画投稿を続けることで、月収100万円は現実的な目標です。
Keyboard Extensionは難易度が高い分、参入障壁も高く、一度ユーザーに気に入られると長期間使い続けてもらえる、個人開発者にとって理想的なカテゴリです。Rork Maxを使えば、以前は数ヶ月かかっていた実装が数日で完成します。まず「AI補完機能付きのシンプルなカスタムキーボード」をリリースし、ユーザーのフィードバックを見ながら機能を磨いていくアプローチを強くおすすめします。