本記事について
本記事はこのセクション以降 AI に書いてもらっています。
ふとclaudeのiOSアプリがあることに気付き、スマホからどこまで出来るのか気になり、さくっと試した内容になります。アプリ作成、記事作成、記事投稿も全てスマホから行いました。
かなり簡易的なwebアプリですが、「10分で逆にこれだけ出来るんだ」ぐらいでご覧ください。
ClaudeのiOSアプリを利用しており、無料版なのでモデルがSonnet 4.6でしたが、Opus 4.6にするともっとクォリティ上がりそうだなと思いました。
はじめに
「Webアプリ作ってみたいな」と思いながら、PCを開くのが億劫で後回しにしていました。
ある日、iOSの Claudeアプリ を開いて、軽い気持ちで話しかけてみたのがきっかけです。
「ToDoアプリ作って」
たったこれだけで、動くWebアプリが完成しました。コードはほぼ書いていません。
この記事では、その体験を丸ごとシェアします。
使ったもの
| 項目 | 内容 |
|---|---|
| デバイス | iPhone |
| ツール | Claude(iOSアプリ) |
| 開発環境 | なし |
| 事前知識 | 特に不要 |
実際の流れ
1. iOSアプリからClaudeに話しかける
App StoreからClaudeをインストールして、チャット欄にこう入力しました。
ToDoアプリ作って。シンプルでいいよ
するとClaudeが即座にコードを生成してくれました。
2. スマホからアクセスできるように変換してもらう
最初はReact(JSX)形式でコードが生成されましたが、iOSのSafariではそのままでは開けません。そこで追加でお願いしました。
HTMLファイルとして書き直して。iOSのSafariで直接開きたい
これだけで、単体で動く .html ファイルに変換してくれました。あとはファイルアプリ経由でSafariで開くだけです。
3. 実際に動かした様子
タスクを入力している画面です。キーボードを出しながらでも快適に操作できます。
タスク「a」「b」が追加済みで、「c」を入力中の様子
タスクにチェックを入れると打ち消し線が入り、完了済みとして管理できます。右下の「CLEAR DONE」ボタンで完了済みをまとめて削除できます。
「a」「b」を完了済みにした状態。CLEAR DONEボタンが表示されている
フィルター機能でタスクを絞り込めます。「ACTIVE」タブでは未完了のタスクだけが表示されます。
ACTIVEタブで未完了タスク「c」だけを表示した状態
「DONE」タブでは完了済みタスクだけを確認できます。
DONEタブで完了済みタスク「a」「b」を表示した状態
できあがったアプリの機能
- ✅ タスクの追加
- ✅ 完了チェック / 削除
- ✅ ALL / ACTIVE / DONE でフィルタリング
- ✅ 完了済みタスクをまとめて削除(CLEAR DONE)
- ✅ ダークテーマのUI
- ✅ iOSのSafariでそのまま動作
実際のコード
Claudeが生成したHTMLファイルの全体です。これをそのまま .html として保存してSafariで開けば動きます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Things to do</title>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
min-height: 100vh;
background: #0f0f0f;
display: flex;
align-items: center;
justify-content: center;
font-family: 'DM Mono', monospace;
padding: 24px;
}
.card {
background: #1a1a1a;
border: 1px solid #2a2a2a;
border-radius: 2px;
width: 100%;
max-width: 480px;
overflow: hidden;
}
.header {
padding: 36px 32px 24px;
border-bottom: 1px solid #2a2a2a;
}
.title {
font-family: 'Playfair Display', serif;
font-size: 28px;
color: #f0ead6;
letter-spacing: -0.5px;
}
.subtitle {
font-size: 11px;
color: #555;
margin-top: 6px;
letter-spacing: 2px;
text-transform: uppercase;
}
.input-row { display: flex; border-bottom: 1px solid #2a2a2a; }
.input-row input {
flex: 1;
background: transparent;
border: none;
outline: none;
padding: 18px 24px;
font-family: 'DM Mono', monospace;
font-size: 13px;
color: #ccc;
}
.input-row input::placeholder { color: #3a3a3a; }
.input-row button {
background: #f0ead6;
color: #0f0f0f;
border: none;
padding: 0 24px;
font-size: 22px;
cursor: pointer;
}
.filters { display: flex; padding: 0 8px; border-bottom: 1px solid #2a2a2a; }
.filters button {
background: none;
border: none;
padding: 12px 16px;
font-family: 'DM Mono', monospace;
font-size: 11px;
color: #444;
cursor: pointer;
letter-spacing: 1.5px;
text-transform: uppercase;
border-bottom: 2px solid transparent;
}
.filters button.active { color: #f0ead6; border-bottom-color: #f0ead6; }
.list { min-height: 80px; }
.item {
display: flex;
align-items: center;
padding: 16px 24px;
border-bottom: 1px solid #1f1f1f;
gap: 14px;
}
.check {
width: 20px; height: 20px;
border: 1px solid #333;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.check.done { border-color: #f0ead6; background: #f0ead6; }
.check.done::after {
content: '';
display: block;
width: 5px; height: 9px;
border: 2px solid #0f0f0f;
border-top: none; border-left: none;
transform: rotate(45deg) translate(-1px, -1px);
}
.text { flex: 1; font-size: 13px; color: #bbb; }
.text.done { color: #333; text-decoration: line-through; }
.del {
background: none; border: none;
color: #333; font-size: 20px;
cursor: pointer; padding: 0 4px; line-height: 1;
}
.empty {
padding: 40px 24px; text-align: center;
font-size: 11px; color: #2a2a2a;
letter-spacing: 2px; text-transform: uppercase;
}
.footer {
padding: 14px 24px;
display: flex;
justify-content: space-between;
align-items: center;
}
.count { font-size: 11px; color: #444; }
.clear {
background: none; border: none;
font-family: 'DM Mono', monospace;
font-size: 11px; color: #333;
cursor: pointer; letter-spacing: 1px; text-transform: uppercase;
}
</style>
</head>
<body>
<div class="card">
<div class="header">
<div class="title">Things to do</div>
<div class="subtitle" id="subtitle">0 remaining</div>
</div>
<div class="input-row">
<input id="input" type="text" placeholder="add a task..." />
<button onclick="addTodo()">+</button>
</div>
<div class="filters">
<button class="active" onclick="setFilter('all', this)">all</button>
<button onclick="setFilter('active', this)">active</button>
<button onclick="setFilter('done', this)">done</button>
</div>
<div class="list" id="list"></div>
<div class="footer">
<span class="count" id="count">0 total</span>
<button class="clear" id="clearBtn" onclick="clearDone()" style="display:none">clear done</button>
</div>
</div>
<script>
let todos = [];
let filter = 'all';
const input = document.getElementById('input');
input.addEventListener('keydown', e => { if (e.key === 'Enter') addTodo(); });
function addTodo() {
const text = input.value.trim();
if (!text) return;
todos.push({ id: Date.now(), text, done: false });
input.value = '';
render();
}
function toggleTodo(id) {
todos = todos.map(t => t.id === id ? { ...t, done: !t.done } : t);
render();
}
function removeTodo(id) {
todos = todos.filter(t => t.id !== id);
render();
}
function setFilter(f, btn) {
filter = f;
document.querySelectorAll('.filters button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
render();
}
function clearDone() {
todos = todos.filter(t => !t.done);
render();
}
function render() {
const remaining = todos.filter(t => !t.done).length;
document.getElementById('subtitle').textContent = `${remaining} remaining`;
document.getElementById('count').textContent = `${todos.length} total`;
document.getElementById('clearBtn').style.display = todos.some(t => t.done) ? 'block' : 'none';
const filtered = todos.filter(t =>
filter === 'all' ? true : filter === 'active' ? !t.done : t.done
);
const list = document.getElementById('list');
if (filtered.length === 0) {
list.innerHTML = '<div class="empty">— no tasks —</div>';
return;
}
list.innerHTML = filtered.map(t => `
<div class="item">
<div class="check ${t.done ? 'done' : ''}" onclick="toggleTodo(${t.id})"></div>
<span class="text ${t.done ? 'done' : ''}">${escapeHtml(t.text)}</span>
<button class="del" onclick="removeTodo(${t.id})">×</button>
</div>
`).join('');
}
function escapeHtml(str) {
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
}
render();
</script>
</body>
</html>
所要時間
| ステップ | 時間 |
|---|---|
| アプリインストール・アカウント作成 | 約3分 |
| アプリ生成の依頼 | 約1分 |
| HTML形式への変換依頼 | 約1分 |
| iOSで動作確認・微調整 | 約5分 |
| 合計 | 約10分 |
やってみて感じたこと
よかった点
- iOSアプリから完結する ── 通勤中やソファでゴロゴロしながらでも開発できる
- 日本語で話しかけるだけ ── コードを書く必要がない
- 形式の変換も一瞬 ── 「iOSのSafariで開きたい」と伝えるだけでHTMLに変換してくれた
- 修正が爆速 ── 「ここ直して」と言うだけで即対応してくれる
注意点
- 生成されるコードの形式によってはそのまま使えない場合がある(変換をお願いすればOK)
- 外部公開するにはVercelなどへのデプロイが別途必要
- 複雑な機能(DB連携、認証など)は追加の作業が必要になる
- 無料プランはメッセージ数に上限あり
まとめ
「Webアプリ作るのってハードル高そう」と思っていましたが、iOSのClaudeアプリを使えばスマホ+自然な日本語だけで動くアプリが作れます。
しかも「iOSで開きたい」と伝えるだけでSafari対応のHTMLに変換までしてくれるので、本当にノーコードで完結しました。
プログラミング経験がなくても、アイデアさえあれば形にできる時代になったんだなと実感しました。
次は外部公開(デプロイ)にも挑戦してみようと思います!



