Rork Max で SwiftUI アプリを生成すると、最初の画面が出るまでが本当に速いですよね。数分で動く UI が出てきて「こんなに早くできるのか」と驚くのですが、そのあとに壁があります。
「なんか UI が安っぽい」「ボタンを押したときの反応が物足りない」「ローディングの見せ方が寂しい」——動いてはいるけど、プロっぽくない状態です。
Rork Max の AI は骨格を作るのが得意で、それは本当に素晴らしい能力です。ただ、細部の UX を詰めるのは今のところ人間が担う部分があります。私がアプリを公開してきた経験から、生成コードを「プロ品質」に仕上げるために毎回使う 4 つのパターンを順番にご紹介します。
AI 生成コードの「惜しい」ポイントを理解する
Rork Max の SwiftUI 生成は、機能的なコードとしては非常に完成度が高いです。データの取得、UI のレイアウト、ナビゲーション——これらは概ね正確に生成されます。
問題は「感触」の部分です。具体的には次のようなポイントが残りがちです。
- ローディング中の見せ方:
ProgressView()一つで済ませているケースが多く、コンテンツの形に合ったプレースホルダーがない - 画面遷移のアニメーション: デフォルトのスライドのみで、コンテンツの特性に合った演出がない
- エラー時の UI: "Error occurred" のテキストのみで、ユーザーがどう対処すべきかわからない
- タッチフィードバック: ボタンを押しても反応が薄く、操作が届いた感覚がない
これらは機能ではなく「感触」の問題なので、プロンプトだけで完璧に解決するのは難しい部分でもあります。では、1 つずつ見ていきましょう。
パターン 1:ローディングをスケルトン UI で仕上げる
最も効果が大きいのがローディングの改善です。
Rork Max が生成するローディングは多くの場合、中央にスピナーが出るパターンです。これ自体は間違いではないのですが、コンテンツが多いアプリでは「空白の時間」が長く感じられます。
スケルトン UI(コンテンツの形に合ったプレースホルダー)を追加すると、体感速度が大幅に改善します。
// Rork Max が生成する典型的なパターン
struct ArticleListView: View {
@State private var articles: [Article] = []
@State private var isLoading = true
var body: some View {
if isLoading {
ProgressView() // ← これをスケルトンに置き換える
} else {
List(articles) { article in
ArticleRow(article: article)
}
}
}
}
// スケルトン UI 版
struct ArticleListView: View {
@State private var articles: [Article] = []
@State private var isLoading = true
var body: some View {
List {
if isLoading {
// コンテンツの形に合ったスケルトンを 4 行表示
ForEach(0..<4, id: \.self) { _ in
ArticleSkeletonRow()
}
} else {
ForEach(articles) { article in
ArticleRow(article: article)
}
}
}
.task { await fetchArticles() }
}
}
// スケルトン行コンポーネント
struct ArticleSkeletonRow: View {
@State private var isAnimating = false
var body: some View {
VStack(alignment: .leading, spacing: 8) {
// タイトル行のプレースホルダー
RoundedRectangle(cornerRadius: 4)
.fill(Color.gray.opacity(0.2))
.frame(width: 200, height: 16)
// 説明文のプレースホルダー
RoundedRectangle(cornerRadius: 4)
.fill(Color.gray.opacity(0.15))
.frame(maxWidth: .infinity)
.frame(height: 12)
}
.padding(.vertical, 8)
.opacity(isAnimating ? 0.5 : 1.0)
.animation(
.easeInOut(duration: 0.9).repeatForever(autoreverses: true),
value: isAnimating
)
.onAppear { isAnimating = true }
}
}Rork Max に「スケルトン UI を追加して」と伝えると汎用的なものになりがちです。「このリストのセル形状に合ったスケルトン行コンポーネントを作って」と具体的に指示すると、実際のレイアウトに合ったものが生成されます。
パターン 2:画面遷移に「意味のある動き」を加える
SwiftUI のデフォルト遷移はスライドです。悪くはないのですが、全画面が同じ遷移では単調になります。
iOS 18 以降で使える .navigationTransition(.zoom) は、タップしたセルから詳細画面が「広がる」ような演出で、ユーザーの空間認識と一致します。
// iOS 18+ のズームトランジション
struct ArticleListView: View {
@Namespace private var namespace
var body: some View {
NavigationStack {
List(articles) { article in
NavigationLink(value: article) {
ArticleRow(article: article)
}
.matchedTransitionSource(id: article.id, in: namespace)
}
.navigationDestination(for: Article.self) { article in
ArticleDetailView(article: article)
.navigationTransition(.zoom(sourceID: article.id, in: namespace))
}
}
}
}
// モーダル操作(設定画面など):下からスライドアップ
.sheet(isPresented: $showSettings) {
SettingsView()
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
}Rork Max に「iOS 18 の zoom ナビゲーショントランジションを実装して」と伝えると対応したコードを生成してくれます。ただし、@Namespace の宣言位置を確認することをお勧めします。ビューの階層が深い場合に位置がずれることがあります。
パターン 3:エラー状態の UX を人間的に設計する
これは見落とされがちですが、ユーザー満足度に大きく影響します。
Rork Max が生成するエラー処理は機能的には正しいのですが、表示が最低限になりがちです。エラーが出た時に「何が起きたか」「どうすればいいか」が伝わる設計にします。
// エラー種別を意味別に定義
enum AppError: Error {
case networkUnavailable
case serverError(Int)
case timeout
var title: String {
switch self {
case .networkUnavailable: return "接続できませんでした"
case .serverError: return "サービスが一時的に利用できません"
case .timeout: return "時間がかかっています"
}
}
var message: String {
switch self {
case .networkUnavailable:
return "Wi-Fi またはモバイルデータ通信を確認してください"
case .serverError(let code):
return "しばらく経ってから再度お試しください(エラーコード: \(code))"
case .timeout:
return "通信速度が低下している可能性があります"
}
}
var sfSymbol: String {
switch self {
case .networkUnavailable: return "wifi.slash"
case .serverError: return "exclamationmark.triangle"
case .timeout: return "clock.badge.exclamationmark"
}
}
}
// 再利用可能なエラー表示コンポーネント
struct ErrorStateView: View {
let error: AppError
let retryAction: () -> Void
var body: some View {
VStack(spacing: 20) {
Image(systemName: error.sfSymbol)
.font(.system(size: 48))
.foregroundColor(.secondary)
VStack(spacing: 8) {
Text(error.title)
.font(.headline)
Text(error.message)
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
}
Button("もう一度試す", action: retryAction)
.buttonStyle(.borderedProminent)
}
.padding(32)
}
}技術的なエラーメッセージ("URLSession error domain -1009" など)をそのまま表示するのは、ユーザーに何も伝えていないのと同じです。Rork Max にこのパターンを渡して「アプリのエラー処理をこのパターンに合わせて」と指示すると、統一感のあるエラー UI に整えてくれます。
パターン 4:ハプティクスでボタンの「確かさ」を伝える
最もシンプルで、最も即効性がある改善です。
iOS のハプティクス(触覚フィードバック)は、ユーザーが「ちゃんと操作できた」という確信を与えます。特に完了アクション、削除操作、トグル操作での差が大きいです。
// アプリ全体で使いやすくするハプティクスラッパー
struct HapticManager {
/// 軽い確認(チェックボックス ON/OFF など)
static func light() {
UIImpactFeedbackGenerator(style: .light).impactOccurred()
}
/// 中程度(通常のボタンタップ)
static func medium() {
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
}
/// 成功通知(タスク完了、保存成功)
static func success() {
UINotificationFeedbackGenerator().notificationOccurred(.success)
}
/// エラー通知(削除確認、操作失敗)
static func error() {
UINotificationFeedbackGenerator().notificationOccurred(.error)
}
}
// 使用例:タスク完了ボタン
Button("完了にする") {
HapticManager.success() // 先にハプティクス → 処理
viewModel.completeTask()
}
// 使用例:スワイプ削除
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
HapticManager.error()
viewModel.delete(item)
} label: {
Label("削除", systemImage: "trash")
}
}
// 使用例:トグルスイッチ
Toggle("通知をオン", isOn: $notificationsEnabled)
.onChange(of: notificationsEnabled) {
HapticManager.light()
}ハプティクスは呼び出す前(アクションの直前)に発火するのがポイントです。処理後に呼ぶと「反応が遅い」という印象になります。
Rork Max との反復改善ワークフロー
これら 4 つのパターンを Rork Max に適用する際のコツは、「一気に全部やってもらおうとしない」ことです。
私のやり方はこうです。まず Rork Max で機能を完成させて、Rork Companion を使って実機で確認しながら「詰めが必要な箇所」をメモします。そのあと 1 箇所ずつ Rork Max に追加指示します——「このローディングをスケルトンにして」「このボタンに medium ハプティクスを追加して」——という具合に。
特に SwiftUI のアニメーション系は、一つ変えると他に影響が出ることがあります。まとめて指示すると生成が複雑になりやすいので、1 回の指示で 1 箇所ずつ直していく方が確実です。
プロンプトのコツについては Rork Max プロンプト活用ガイド に詳しくまとめています。
まずはハプティクスだけでも追加してみてください。「たったこれだけの変更で、アプリの印象がガラッと変わった」と実感できるはずです。次のビルドで試してみることをお勧めします。