前回の最後で、共有シート(Share Sheet)を使わなかった理由を数字で書くと予告した。約束の数字を先に置く。「思いついてから、最初の1文字が置けるまで」の時間を、3つの入口で計測した結果だ。
| 入口 | 思いつき→1文字目 | タップ数 | 前提条件 |
|---|---|---|---|
| ホーム画面アイコン直叩き | 約0.3秒 | 1 | なし |
| アクションボタン割当 | 約0.3秒 | 1(物理) | iPhone 15 Pro以降 |
| 共有シート経由 | 約1.5秒 | 3〜4 | 既に別アプリを開いていること |
差は1.2秒。たった1.2秒、と思うかもしれない。だが、メモアプリの設計でこの1.2秒は致命的だ。思いつきは、口に出すより速く消える。会議中にふと浮かんだ案、歩いている時に繋がった点と点、それを掴むまでに動線が1.2秒もたつくと、文字を置く前に思考そのものが揮発する。1.2秒は「待てる時間」ではなく「忘れるのに十分な時間」だ。キャプチャ専用アプリにとって速度は機能の一つではなく、機能の前提条件にあたる。だからこの1.2秒は、削れる贅肉ではなく、譲れない一線として扱った。今日はその理由を、計測値と一緒に分解する。Captio式の起動0.3秒のメモアプリで、なぜ最も「iOSらしい」共有シートをあえて捨てたのか。順に書く。
仮説:共有シートは「キャプチャ」の入口にならない
着手前の仮説はこうだった。「共有シートは、既にある何かを送る機構であって、頭の中の思いつきを捕まえる機構ではない」。
共有シートは、必ず「シェア元のコンテンツ」を前提にする。Safariのページ、写真、選択したテキスト。何かのアプリの中で、何かを選んでから呼び出すものだ。ところが、ふと浮かんだ思いつきには「シェア元」がない。頭の中にしかない。それを共有シートで捕まえようとすると、まず適当なアプリを開き、適当なものを選び、それから共有シートを出す、という回り道が要る。仮説が正しければ、共有シート経由はキャプチャ用途で必ず遅くなる。検証は、その遅さが数字で出るかどうかで判定する。
この仮説は、アプリの根本思想と直結している。狙っているのは「思いついた瞬間に、最短の動線で文字を置く」一点だ。動線の起点に「まず別のアプリを開く」が挟まった時点で、その思想は崩れる。
計測の方法も先に書いておく。「思いつき」は内心の出来事なので、そのまま秒で測れない。代わりに、入口を起動する操作(アイコンへの指の接触、アクションボタンの押下、共有ボタンのタップ)を起点に置き、入力欄に最初のキャレットが点滅するまでを終点にした。各入口で20回ずつ、暖機済みと冷間の両方で測り、中央値を採った。平均ではなく中央値にしたのは、共有シート経由で時々起きる「アイコンを見失って2秒超」の外れ値に引っ張られないためだ。外れ値こそ体験を壊すので別途記録したが、代表値は中央値で揃えた。測り方を先に固定しておかないと、後から都合よく数字を選べてしまう。
検証:1.5秒の内訳を分解する
共有シート経由の1.5秒を、ストップウォッチと内部ログで分解した。内訳はこうなった。
| フェーズ | 所要(目安) | 何が起きているか |
|---|---|---|
| 別アプリを開く/前にいる | 別計上 | そもそもの前提 |
| 共有ボタンを探してタップ | 約0.4秒 | アプリごとに位置が違う |
| 共有シートの展開 | 約0.3秒 | アイコン一覧の描画 |
| 目的のアイコンを探す | 約0.3秒 | 並び順が学習で変動する |
| 拡張機能のコールド起動 | 約0.5秒 | 別プロセスの立ち上げ |
合計でおよそ1.5秒。しかもこの表には、表に書けない前提が一行ある。「別アプリを開く/前にいる」だ。共有シートは、あなたが既に何かのアプリの中にいることを暗黙に要求する。ホーム画面でぼんやりしている時にふと思いついたら、まず何かアプリを開かないと共有シートにすら辿り着けない。直接起動の入口は、この「別計上」の行がそもそも存在しない。ゼロ秒で始められる。1.5秒という数字は、実は最も有利な「既にアプリを開いている」状態で測った値であって、本当の思いつきの瞬間からの実測はもっと開く。表の一番上の行が空欄であることが、この入口の不利を物語っている。
注目したもう一つは、最後の「拡張機能のコールド起動 約0.5秒」だ。iOSの共有拡張(Share Extension)は、ホストアプリとは別プロセスで動く。メインアプリが暖まっていても、拡張は拡張で別に立ち上がる。しかも歴史的にメモリ上限が本体より厳しく、軽量に保たないと途中で落ちる。本体側で削り込んだ起動0.3秒の努力が、拡張という別の土俵では効かない。これが効いて、暖機しても0.5秒を切れなかった。
もうひとつ、数字に出ない問題があった。共有シートのアイコンの並び順は、ユーザーの利用履歴で動的に変わる。昨日3番目だったアイコンが、今日は5番目にいる。「探す」時間の約0.3秒は平均で、実際には毎回ブレる。動線の中に「目で探すステップ」が混ざると、所要時間の分散が一気に増える。速さは平均だけでなく、ブレの小ささでも測るべきだ、というのが「iOS 共有シート メモ 遅い」を実測して得た学びだった。
拡張機能のコールド起動について、もう少し踏み込む。共有拡張は本体アプリと別バイナリ・別プロセスで、システムが必要になった瞬間に初めて起動する。本体を10秒前に使っていても、拡張のプロセスは生きていないことが多く、毎回ほぼゼロからの立ち上げになる。さらに、拡張はメモリ上限が本体より厳しい環境で動く。重い初期化を持ち込むと、システムにいきなり殺される。だから拡張側では、本体で使っている初期化処理の大半を持ち込めない。結果として「本体は0.3秒で起動できるのに、拡張は同じ初期化を積めず0.5秒かかる」という、努力が移植できない壁にぶつかる。速度の数字以前に、最適化の在庫が共有できないことが、拡張を主入口にしない決定打になった。
なぜ標準機構をあえて使わないのか
iOSの標準機構を使わない、と言うと「車輪の再発明では」と返ってくる。AIに聞いても同じ趣旨の反論が出る。ここにきちんと答えておく。
標準機構が常に最善なのは、その機構が「想定している用途」に乗っているときだけだ。共有シートが想定するのは「既存コンテンツの転送」。一方こちらの用途は「ゼロからの発生」。土俵が違う以上、標準だから速い、は成り立たない。実際、計測では標準機構のほうが1.2秒遅かった。標準を捨てたのではない。用途に合う標準を選び直した、というのが正確だ。
具体例を一つ挙げる。共有シート経由でメモアプリを呼ぶと、システムは「あなたは今いるアプリの何かを共有しようとしている」と解釈する。だから渡ってくるデータには、シェア元のURLやテキストの断片が紛れ込む。純粋に「空のメモを開きたいだけ」なのに、シェア元の文脈が付いてくる。これを毎回そぎ落とす処理が要る。ゼロからの発生に、いらない出自情報が付随する——この時点で、共有シートはキャプチャの器として歪んでいる。「iOS アクションボタン メモ 直接起動」を選んだのは速度のためだけでなく、渡ってくるデータがクリーンだからでもある。出自のない思いつきには、出自を持たない入口がふさわしい。
そして標準を選び直すという観点では、アクションボタンやコントロールセンターも立派なiOSの標準機構だ。これらもApp Intents経由でシステムに統合される、れっきとした純正の作法だ。「標準を捨てた」のではなく「同じ標準の棚から、用途に合う引き出しを選んだ」。共有シートという一番目立つ引き出しが、たまたまこの用途に合わなかっただけだ。
代わりに採用した入口は、どれも「別アプリを経由しない直接起動」で揃えた。ホーム画面アイコン、アクションボタン、コントロールセンター、ウィジェット。共通点は、どれも「シェア元」を要求しないことだ。アクションボタンの割当はApp Intentsで実装し、押した瞬間に入力欄へカーソルが立つところまでを一続きにした。
// アクションボタン → メモ入力に直行する App Intent
struct CaptureIntent: AppIntent {
static let title: LocalizedStringResource = "新規メモ"
static let openAppWhenRun = true // 起動して即フォーカスまで一続き
func perform() async throws -> some IntentResult {
await CaptureRouter.shared.openComposerFocused()
return .result()
}
}
openAppWhenRun = true で本体を起動し、そのままコンポーザを開いてカーソルを立てる。共有拡張のような別プロセスを挟まないので、Day4で詰めた起動0.3秒の最適化がそのまま生きる。同じ「速く書き始める」でも、土台のプロセスが本体か拡張かで、使える最適化の在庫がまるで違う。
直接起動には、直接起動なりの落とし穴もあった。アクションボタンとアイコンを連続で叩くと、Intentと通常起動が二重に走り、コンポーザが2枚開くことがある。入口を増やした副作用だ。ルーター側で「起動要求を一本化し、既にコンポーザが立っていれば新規に開かず既存にフォーカスを返す」処理を挟んで潰した。
@MainActor
final class CaptureRouter {
static let shared = CaptureRouter()
private var isComposerLive = false
func openComposerFocused() async {
guard !isComposerLive else { // 二重起動を弾く
await focusExistingComposer()
return
}
isComposerLive = true
await presentComposerAndFocus()
}
}
入口を増やすほど、入口同士の競合という新しい問題が生まれる。共有シート1本に絞れば起きなかった事故だ。それでも複数の直接入口を選んだのは、競合は実装で潰せるが、共有シートの1.2秒は実装では潰せないからだ。潰せる問題と潰せない問題があるなら、潰せるほうを引き受ける。
入力欄に着地した瞬間に、キーボードが出ていること
直接起動を選んでも、最後の数百ミリ秒で気を抜くと台無しになる。狙いは「アプリが見えた瞬間にはもうキーボードが出ていて、カーソルが待っている」状態だ。起動アニメーションが終わってからフォーカスを取りにいくと、そこで一拍遅れる。
// シーン生成の最序盤で先にフォーカスを予約する
func sceneWillConnect(_ scene: UIScene) {
composer.prepareForImmediateInput() // 入力欄を生成だけ済ませる
}
func sceneDidBecomeActive(_ scene: UIScene) {
composer.textView.becomeFirstResponder() // 表示確定の最速点で起こす
}
becomeFirstResponder() を遅延させず、シーンがアクティブになった最速点で呼ぶ。これで、画面が見えた時にはキーボードが既に立ち上がっている。共有シート経由だと、この「先回りフォーカス」が拡張のライフサイクルに阻まれてうまく決まらない。直接起動に寄せたのは、速度の数字だけでなく、この「着地の制御を自分の手元に置ける」ことも理由だった。
この「先回り」が効くのは、入力欄を最初の一画面に固定しているからでもある。起動直後に一覧を見せて、そこからメモ作成をタップさせる構造だと、フォーカスを立てる先がまだ存在しない。アプリを開いた瞬間の第一画面が、もう入力欄である——この設計判断が、先回りフォーカスを成立させる前提になっている。速さは単一の最適化で出るものではなく、「第一画面を入力欄にする」「フォーカスを先回りする」「別プロセスを挟まない」という複数の判断が重なって、初めて0.3秒に収束する。どれか一つでも崩すと、体感はずるずる遅くなる。共有シートは、この積み木の土台ごと別物にしてしまう。
4つの直接入口を、性質で並べ直す
「別アプリを経由しない」と一括りにしたが、4つの直接入口にも性質差がある。実測と使い勝手で並べ直すと、こうなった。
| 入口 | 速さ | 事前設定 | 向いている場面 |
|---|---|---|---|
| ホーム画面アイコン | 約0.3秒 | 不要 | 画面を見ている時 |
| アクションボタン | 約0.3秒 | 要(1回) | 画面を見ずに、歩きながら |
| コントロールセンター | 約0.4秒 | 要(1回) | 別アプリの最中に降ろす |
| ウィジェット | 約0.4秒 | 要(配置) | ホームで他用と並べて |
速さだけ見ればアイコンとアクションボタンが横一線だが、本質的な差は「画面を見ているか」にある。アクションボタンとコントロールセンターは、画面を注視していなくても押せる。歩いている時、会議中に手元を見ずに、という場面では物理ボタンが効く。逆にアイコンは、ホームに戻る一手が前提だ。同じ0.3秒でも、その0.3秒に入るまでの文脈が違う。共有シートは、この表のどの行にも入れない。なぜなら、どれも「シェア元」を必要としないが、共有シートだけは必要とするからだ。1枚の表に並べた瞬間、共有シートだけ前提条件の欄が埋まらない。それが、捨てた理由を一番端的に示している。
声で書く入口も、この「シェア元を要求しない」系譜に乗る。v3.0で入れた音声入力は、入力欄のマイクから話すとAppleのオンデバイス音声認識(iOS 26)がそのままカーソル位置に文字を起こす。手がふさがっていても、別アプリを開かずに思いつきを置ける。共有シートが「他アプリ前提」だったのと対照的に、音声も直接起動と同じく前提ゼロで始まる。挙動は音声入力の仕様にまとめてある。
共有シートを完全に捨てたわけではない
公平に書いておく。共有シートを全否定したのではない。「キャプチャの主入口にはしない」だけだ。Safariで読んだ一節を後で見返したい、という「既存コンテンツの転送」用途には、共有シートが正しい。だから受け口としての共有拡張は残してある。主入口(ゼロからの発生)と副入口(既存の転送)を、用途で分けた。
この線引きを誤ると、両方が中途半端になる。共有シートを主入口に据えると、思いつきのキャプチャが1.2秒遅くなる。逆に共有拡張を消すと、ウェブの引用を保存する人が困る。1つの機構に2つの用途を兼ねさせず、それぞれに最適な入口を別々に用意する。これはDay8で書いた「Inbox一本でも、入口は複数あってよい」の続きでもある。受け皿は1つ、入口は用途ごと、という設計だ。
もう一段、反対側からも検討した。「主入口を共有シートに寄せれば、開発もユーザー教育も楽では?」という案だ。共有シートは説明不要でiOSユーザーなら全員知っている。直接起動の入口を4つ用意するより、共有シート1本に絞れば、こちらの実装も減るし、使い方の説明もいらない。費用だけ見れば筋は通る。それでも採らなかったのは、計測が示した1.2秒が、習慣の成立そのものを左右するからだ。キャプチャは、速いから使うのではない。速くて確実だから「考える前に手が動く」習慣になる。1.2秒の遅延と、毎回ブレる動線は、その無意識化を妨げる。教育コストを節約して習慣化を失うのは、メモアプリにとって本末転倒だった。安いほうではなく、習慣が根づくほうを選んだ。
次に測る数字:アクションボタンの定着率と、入口別の使用比率
今回の計測で一番効いたのは、速度そのものより「平均ではなくブレで評価する」視点だった。共有シート経由は平均1.5秒でも、アイコン探しの分散が大きく、最悪値は2秒を超えた。直接起動は平均0.3秒で、しかもブレがほとんどない。速いだけでなく、毎回同じ速さである——この再現性が、キャプチャ用途では速度そのものより効く。思いつきが消える前に、確実に同じ動線で置ける、という安心がキャプチャ習慣を支える。
この「平均よりブレ」の視点は、Day4で起動速度を詰めた時には持てていなかった。あの時は平均0.3秒を出すことに集中していて、最悪値の分散までは見ていなかった。入口を共有シートと比べて初めて、「速いか」だけでなく「毎回同じか」が体験を決めると気づいた。同じアプリを長く触り続けると、最初は見えなかった評価軸が後から立ち上がる。数字を測る習慣の本当の利点は、こうして評価軸そのものが更新されていくところにあるのかもしれない。
次に測りたい数字は2つ。ひとつは、アクションボタンに割り当てたユーザーが、何日後も使い続けているかの定着率。物理ボタンの割当は最初の設定が要るので、その一手間を越えた人がどれだけ残るかを見たい。割当を越えた人は、おそらく「画面を見ずに書く」体験を一度知ってしまった人で、その層の継続率が高ければ、設定の一手間をオンボーディングでもっと押すべきだとわかる。ふたつは、4つの直接入口(アイコン/アクションボタン/コントロールセンター/ウィジェット)の使用比率。どれが実際のキャプチャを担っているかで、次に磨く入口が決まる。仮にウィジェットがほとんど使われていなければ、4つに分散させた労力をアイコンとアクションボタンの2本に寄せ直す判断もできる。入口は多ければ良いのではなく、使われている入口に集中投資するのが正しい。それを決めるのも、また数字だ。
最後に問いを置いておく。あなたが一番よく使うメモアプリは、思いついてから何タップで1文字目に着くだろうか。一度、ストップウォッチで測ってみてほしい。そして「別のアプリを開く」が動線の最初に挟まっていないか、確かめてほしい。挟まっているなら、たぶん共有シートに頼っている。次回のDay14は、その「1文字目に着いた後」——入力中にアプリが落ちても下書きが消えない仕組みを、復元の成功率という数字で書く。あなたのメモアプリは、入力途中で落ちて何文字失ったことがあるか、コメントで教えてくれると比較の材料になる。
Obsidian連携シンプルメモ
外部ライブラリ依存ゼロ。Swift と Apple純正フレームワークだけで組んだ、思いつきを最短1タップで捕まえる起動0.3秒のメモアプリ。
App Store:https://apps.apple.com/jp/app/captio%E5%BC%8F%E3%82%B7%E3%83%B3%E3%83%97%E3%83%AB%E3%83%A1%E3%83%A2/id6749649498