前回まで、下書きの数や送信の速さといった、画面の「上」で起きることばかり測ってきた。だが、その画面そのものを何で描くかは、Day1より前に一度だけ決めて、以来ほとんど書き直していない。選んだのはUIKitだ。2026年のいま、新規iOSアプリの既定は誰がどう見てもSwiftUIで、「UIKit SwiftUI どっち」と検索すれば「新規ならSwiftUIで始めろ」という答えがまず出てくる。それでもこのアプリの画面は、いまだにUIKitが描いている。へそ曲がりだからではない。理由を、2008年までさかのぼって確かめる。
UIKitとSwiftUIは、機能の優劣で並ぶライバルではない。「画面とは何か」という問いへの、世代の違う2つの答えだ。だから新しいほうが常に正しいとは限らない。順番に見ていく。
2008年、UIKitは「画面は命令で組み立てるもの」として始まった
2008年7月、iPhone OS 2.0と一緒に最初のiOS向けSDKが公開された。UIKitはここで世に出ている。当時の、そしていまも変わらないUIKitの世界観は、ひとことで言えば命令的だ。UIViewを生成し、addSubviewで親に貼り、参照を手元に持っておき、必要になったらその参照を掴んでbackgroundColorやtextを書き換える。画面は、開発者が1つずつ手で積み上げ、あとから手で書き換える「もの」だった。
// UIKitの世界観:参照を持ち、命令で組み立て、命令で書き換える。
let label = UILabel()
label.text = "未送信:0件"
view.addSubview(label)
// あとで状態が変わったら、自分でこの参照を掴んで書き換える。
label.text = "未送信:\(outbox.count)件"
この「参照を持って、あとから書き換える」が、UIKitのすべての土台にある。UIViewControllerがライフサイクルを抱え、delegateとtarget-actionでイベントを受け、Interface Builderの.xibで見た目を組む。2008年に決まったこの骨格は、18年近くたったいまもほとんど変わっていない。古いことは、ここでは欠点ではない。骨格が動かないからこそ、その上の知識は積み上がり、枯れていく。
2011〜2014年、命令的UIの上に道具だけが積まれていった
UIKitの骨格はそのままに、その上へ道具が足されていった時期がある。2011年のiOS 5でStoryboardが入り、画面遷移を線で結べるようになった。2012年のiOS 6で、iOSにもAuto Layoutが来て、座標の直書きから制約ベースのレイアウトへ移った。2014年には言語そのものが入れ替わり、Objective-CからSwiftへの長い移行が始まった。
ここで大事なのは、これらがどれも「命令的UIの上に乗った便利機能」であって、画面の作り方そのものは変えなかったことだ。Storyboardは巨大化すると差分が読めなくなり、Auto Layoutの制約は数が増えると衝突して「制約地獄」と呼ばれた。
もう少し言えば、この時期の痛みは「画面が複雑になるほど増える」種類のものだった。表示もロジックも1つのUIViewControllerに詰め込む肥大化が進み、数千行のビューコントローラは珍しくなかった。Auto Layoutの制約も、要素が10も20も増えれば、どれか1本が矛盾して画面全体が崩れる。皮肉な話だが、UIKitのつらさは、ほぼすべて「画面が多い・複雑だ」という前提の上で牙をむく。画面が1つで要素も数個のアプリは、この弱点をそもそも踏まない。後で効いてくるので、覚えておく。
Swiftは言語としては美しくなったが、UIKitの使い方はviewDidLoadで参照を組み立てる、あのままだった。つまり2014年の時点で、iOS開発は「新しい言語で、古い世界観のUIを書く」という捻れた状態にあった。この捻れが、次の答えを準備したと言っていい。
2019年、SwiftUIは「画面は状態の関数だ」と言い切った
2019年6月のWWDCで、SwiftUIが発表された。同時にCombineも来て、iOS 13から使えるようになった。SwiftUIの主張は、UIKitとは正反対と言っていい。画面は手で組み立てる「もの」ではなく、状態を渡すと姿が決まる「関数」だ、という立場をとった。状態が変われば、フレームワークが差分を計算して画面を描き直す。開発者は参照を持って書き換えない。状態だけを書き換える。
// SwiftUIの世界観:状態を宣言し、画面はその状態の写像として導かれる。
struct OutboxBadge: View {
@State private var count = 0
var body: some View {
Text("未送信:\(count)件") // countが変われば、ここは自動で描き直される
}
}
同じ「未送信の件数を出す」でも、発想がまるで違う。UIKitはlabelという参照を覚えておき、自分で書き換える。SwiftUIはcountという状態だけを持ち、表示はそこから導かれる。どちらが正しいという話ではない。ただ、片方は2008年の、片方は2019年の「画面とは何か」だ。世代が11年離れている。
ただし2019年のSwiftUIは、思想は鋭くても実用はまだ粗かった。iOS 13と14の頃は、込み入ったリストやナビゲーションで素直に動かない場面が多く、難しい画面はUIKitに逃がすのが定石だった。思想が先に来て、実装が後から追いつく。新しいフレームワークの常で、SwiftUIもこの数年を通ってきた。
具体的には、iOS 13のSwiftUIはListの区切り線を思うように消せず、NavigationViewは深い遷移で状態を見失うことがあった。@Stateと@Bindingで「状態を誰が持つか」を決める考え方も、UIKitに慣れた手には最初なじまない。だから当時の本気のアプリは、SwiftUIで外枠を作り、難所だけUIViewControllerRepresentableでUIKitに包んで逃がす、という折衷が多かった。新しい答えは、古い答えを必要としながら育った。ここも伏線として置いておく。
なぜ2023年まで「SwiftUIで全部書く」に踏み切れなかったのか
SwiftUIが「思想は良いが、まだ本番では怖い」を抜けたのは、自分の見立てでは2023年だ。この年のWWDCで、iOS 17と一緒にObservationフレームワークとSwiftDataが来た。
それまでのSwiftUIは、状態の変化をObservableObjectと@Publishedで伝えていた。だがこれは、オブジェクトのどれか1つが変わると、それを見ている画面が必要以上に描き直される作りで、規模が大きくなるほど無駄が増えた。Observationフレームワークは、@Observableマクロで「実際に読まれたプロパティが変わったときだけ描き直す」へ作り替えた。SwiftDataは、永続化をSwiftの型からそのまま宣言できるようにした。この2つで、SwiftUIはやっと「大きな本番アプリを最初から最後までこれで書ける」水準に届いた。「SwiftUI 本番」で検索して安心できる答えが増えたのも、だいたいこの時期からだ。
自分のアプリで言えば、ここで来たSwiftDataには手を出していない。送信キューの永続化は、Day3で書いたOutboxの素朴なファイルとCoreDataで既に足りていて、新しい永続化層を足す理由がなかったからだ。ただ、Observationが押し出した「読まれたものだけ描き直す」という考え方そのものは、状態を手で配るUIKit側の設計にも、規律として効いた。誰がどの状態を読んでいるかをはっきりさせる——この一点は、フレームワークが違っても変わらない。
だから時系列で見ると、SwiftUIが「既定」になったのは、2019年の登場時点ではない。実用の重心が移ったのは2023年だ。登場から既定まで、4年かかっている。新しい答えが古い答えを置き換えるには、思想の鋭さだけでは足りず、これだけの時間が要るということでもある。
2026年、既定はSwiftUIになった。ではなぜこのアプリはUIKitのままなのか
2026年のいま、状況ははっきりしている。新規アプリの既定はSwiftUIだ。スクロールやアニメーションといった普段づかいの性能はUIKitと並んだと言われ、複数のApple OSへ同じコードで展開できる強みもある。新しいAPIはSwiftUIから先に来る。これは認める。「UIKit SwiftUI どっち」の一般論で言えば、答えはもうSwiftUIで固まっている。もし自分がいま、画面の多いアプリや、iPhoneとMacの両方に出すアプリを新しく始めるなら、迷わずSwiftUIで書き始める。
それでもこのアプリの画面をUIKitで描いているのは、このアプリが「ほぼ1画面で、起動の速さがそのまま価値になる」という、かなり特殊な形をしているからだ。Day4で起動0.3秒を詰めた話を書いたが、キャプチャ専用アプリにとって、最初の0.3秒は機能の一部ではなく、機能そのものに近い。ここでUIKitには、地味だが効く性質がある。冷えた状態からの起動が、ベンチマークによってはSwiftUIより5〜10%ほど速いと報告されることだ。理由を乱暴にまとめれば、SwiftUIは起動時に状態とビューの対応関係を組み立てる土台を一度用意するが、UIKitは最初の1画面を出すだけならその土台が要らない。
// このアプリの起動経路。SceneDelegateで、最初の1画面だけを最短で出す。
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: windowScene)
window.rootViewController = ComposerViewController() // 入力欄ひとつだけの画面
window.makeKeyAndVisible()
// 状態グラフの構築も、ビューツリーの差分エンジンも、ここでは動かさない。
}
差は数十ミリ秒の世界で、ほとんどのアプリでは誤差だ。だが、価値の全部が最初の0.3秒に乗っているアプリでは、この数十ミリ秒は誤差ではなく中身になる。Day6で外部依存をゼロにした話とも噛み合う。UIKitは2008年から積み上がった、いちばん枯れたApple純正の土台で、「初回だけなぜか重い」のような、新しい層が増えるほど起きやすい不具合の余地が少ない。Day7でHistoryの反映をSwiftで直したときも、UIKitとCoreDataの枯れた挙動の上だったから、原因を底まで追えた。
加えて、UIKitだけで組んだ最初の画面は、出るまでの経路が短く、毎回ほぼ同じ時間で立ち上がる。速いだけでなく、ばらつきが小さい。中央値だけでなく、いちばん遅い回までが揃うことは、1日に何十回も開く道具では地味に効いてくる。たまに引っかかる起動は、平均が同じでも、速いという印象を確実に削るからだ。
SwiftUIが遅いのではない、と言われたらどう答えるか
ここは公平に書く。いまの主張に対する、いちばん正しい反論はこうだ。「SwiftUIが起動を遅くするのではない。遅いのはアーキテクチャだ」。これは正しい。「SwiftUI 起動 遅い」で検索して出てくる答えの多くも、フレームワークではなく設計の問題を指している。実際、起動5.2秒のSwiftUIアプリを、設計を見直すだけで1.1秒へ縮めた、という報告もある。8割の短縮で、フレームワークのせいにできる差ではない。UIKitとSwiftUIを混ぜるときのUIHostingControllerの上乗せも、1%未満とされていて、無視していい。普段づかいの95%の場面で、SwiftUIは十分に速い。
もう1つ正直に付け加える。ユーザーが感じる速さは、計測上の起動時間とぴったりは一致しない。起動直後に空の入力欄へカーソルが乗り、すぐ打てる状態になっていれば、多少の差は体感に出ない。だからSwiftUIでも、設計が良ければ「十分速い」に届く。それでもUIKitを選ぶのは、体感を設計で稼ぐ前に、素の経路がもう速いほうが、守るべき0.3秒に余白が残るからだ。速さを設計で取り返す勝負を、そもそも始めなくて済む。
だから、自分はこう答える。UIKitを選んだのは、SwiftUIが遅いからではない。このアプリには「開発すべき画面」がほとんどないからだ。SwiftUIの最大の見返りは、UI開発の時間が3〜5割ほど縮むことにある。画面が多いアプリほど、この見返りは大きい。ところがこのアプリは画面が1つしかない。縮むはずの開発時間が、そもそも縮む対象を持っていない。一方で、失いたくない起動の数十ミリ秒と、層を増やさない安心は、画面が1つしかないからこそ相対的に重くなる。見返りが最小で、コストが最大になる側に、たまたまこのアプリは立っている。
これはDay15で書いた結論の出し方と、同じ筋だ。機能の良し悪しは単体では決まらず、いつも「何の道具か」とセットで決まる。SwiftUIが良いか悪いかではない。1画面のキャプチャ道具という用途に対して、いまはUIKitのほうが釣り合う、というだけの話だ。用途が変われば、答えも変わる。
UIKitを選んで、何を諦めたのか
UIKitを選ぶことは、ただではない。公平のために、諦めたものを正直に並べておく。まず、たった1画面でも、入力欄やボタンの配置は自分で制約を書いて組む必要がある。SwiftUIなら十数行で済む見た目に、UIKitでは何倍かのコードがかかる。次に、コードを書いてから画面で確かめるまでの往復が遅い。SwiftUIのライブプレビューのように、書いた端から見た目が出る速さは手に入らない。そして、同じコードでMacやApple Watchへ広げる道も、最初から捨てている。
これらは、画面の多いアプリなら確かに効いてくる損失だ。だが、ここでも同じ話に戻る。このアプリは画面が1つしかない。書くべきUIが少ないから、ボイラープレートの総量も、プレビューを我慢する回数も、たかが知れている。多プラットフォーム展開も、キャプチャ専用という用途には最初から要らない。UIKitの弱点はどれも「画面が多いほど痛い」もので、画面が1枚のこのアプリは、その痛みがいちばん小さい場所に立っている。損失を正直に数え上げても、なお釣り合っている——それが、この選択の現在地だ。
年表で見ると、答えはこう動いてきた
ここまでを1枚に畳む。
| 年 | 出来事 | 「画面とは何か」への影響 |
|---|---|---|
| 2008 | iPhone OS 2.0、UIKit公開 | 画面は命令で組み立てる「もの」 |
| 2011 | iOS 5、Storyboard | 命令的UIの上に画面遷移の道具が乗る |
| 2012 | iOS 6、Auto Layout | 座標の直書きから制約ベースへ |
| 2014 | Swift発表 | 言語は新しく、UIの世界観は据え置き |
| 2019 | SwiftUI/Combine(iOS 13) | 画面は状態の「関数」だと宣言 |
| 2021 | Swift 5.5、async/await | 非同期が言語そのものに内蔵される |
| 2023 | Observation/SwiftData(iOS 17) | SwiftUIが本番水準に到達し、重心が移る |
| 2025 | 描画パイプラインの刷新(とされる) | UIKitとの性能差がさらに縮んだと報告 |
| 2026 | 新規アプリの既定はSwiftUI | UIKitは下の層へ回り始める |
この表で自分が読み取るのは、勝ち負けではない。2008年の「もの」としての画面と、2019年の「関数」としての画面が、片方に一本化されるのではなく、層になって積み重なっているという事実だ。2026年のいま、SwiftUIの画面の下には、ほとんどの場合UIKitがいる。UIHostingControllerは、SwiftUIをUIKitの上に載せるための部品だ。新しい答えは、古い答えを消したのではなく、その上に乗った。
言い換えると、2026年の問いはもう「UIKitかSwiftUIか」の二択ではない。下にUIKit、上にSwiftUI、という層の重なりが既定になりつつある。問いは「どちらを選ぶか」から「どの層をどちらに任せるか」へ移った。このアプリは、たまたまその層が1枚しかないので、いちばん下のUIKitだけで足りている。層が増えた日には、上からSwiftUIが入ってくる。
歴史の続きは、どこへ向かうのか
予想を書いておく。この先、SwiftUIは吸収を続け、UIKitは「SwiftUIの下で画面を実際に出す層」へ静かに退いていく。アプリ開発者がUIKitを直接書く場面は減り続ける。だがゼロにはならない。少なくとも、起動の最初の数十ミリ秒を奪い合うような、いちばん下の層では残る。
このアプリ自身の続きも、たぶんそうなる。もし将来、設定や履歴といった「1画面では収まらない」部分が育ったら、そこはUIHostingControllerでSwiftUIを載せていく。新しい層の見返りが、その画面でだけコストを上回るからだ。だが、いちばん最初に出るキャプチャ画面——起動0.3秒の核——は、最後まで、あるいは永遠にUIKitのままかもしれない。歴史が「関数」へ進んでも、最下層で最初の0.3秒を守る仕事は、いちばん枯れた答えに任せたい。
最後に、自分が答えを持っていない問いを1つ置く。あなたがいま、ほぼ1画面の個人アプリを新しく始めるとして、最初の1行はUIKitとSwiftUI、どちらで書き始めるだろうか。そしてその選択を決めるのは、「最初の起動の速さ」だろうか、それとも「これから何年も面倒を見る前提での書きやすさ」だろうか。自分はこのアプリでは前者を取ったが、それは1画面という特殊な形だからだ。あなたの形では、たぶん答えが違う。その理由を、コメントで一言もらえると、次にどこかでSwiftUIへ寄せるときの、いい目印になる。
Obsidian連携シンプルメモ
外部ライブラリ依存ゼロ。UIKitとApple純正フレームワークだけで描いた、起動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