← Back to Blog
iOS Dev SwiftUI Claude API Railway

Building an AI-Powered iOS App from Scratch: The Complete Guide

Everything you need: SwiftUI project setup, connecting the Claude API via a secure backend, Git workflow, TestFlight, and shipping to the App Store.

Daniel Ehrlich · May 2026 · 15 min read

I built and shipped an AI-powered iOS app with no prior iOS experience. This guide is everything I learned — the tools, the decisions, the gotchas, and the workflow that got an app from idea to the App Store.

The app is RDR2 Companion — an AI assistant for Red Dead Redemption 2 players. It uses Claude as its AI backbone, Railway as a secure API proxy, and SwiftUI for the iOS frontend. The same architecture works for any AI-powered iOS app.

Who this is for: Developers with some coding background who want to build an AI iOS app but haven't done iOS development before. I had 25 years of engineering experience but zero Swift — the stack is genuinely learnable.

The Full Stack

Before we dive in, here's everything in the stack and why each piece is there:

🍎
Xcode + SwiftUI
Apple's IDE and modern UI framework for iOS. SwiftUI is declarative — you describe what you want, not how to draw it. Far easier to pick up than UIKit for new iOS developers.
developer.apple.com/xcode · Free
🤖
Anthropic Claude API
The AI engine. Claude is excellent at following complex system prompts — which means you can build a deep expert persona for your app's domain. The API charges per token (input + output).
console.anthropic.com · Pay-per-use
🚂
Railway
Backend hosting for the API proxy. Your iOS app never touches the Claude API directly — it goes through your Railway server, which holds the API key. Railway has a generous free tier and deploys from GitHub in seconds.
railway.app · Free tier + usage-based
🐙
GitHub
Version control and deployment trigger. Both your iOS app code and your Railway backend live in GitHub repos. Railway auto-deploys when you push to main.
github.com · Free for public/private repos
🤖
Claude Code (AI Coding Assistant)
This is the meta-tool — Claude Code is a CLI coding assistant you run in your terminal. It reads your codebase, writes Swift, debugs errors, and explains what it's doing. Probably the highest-leverage tool in the entire stack.
claude.ai/code · Subscription
✈️
TestFlight
Apple's beta testing platform. You upload a build from Xcode, share a link, and testers install it on their real device. Required step before App Store submission.
Part of Apple Developer Program · $99/yr

1

Set Up Your iOS Project in Xcode

Open Xcode → New Project → iOS → App. Give it a name, set the interface to SwiftUI, and language to Swift. Everything else can stay default.

Your project structure out of the box looks like this:

// Key files in a new SwiftUI project
YourApp/
  YourAppApp.swift     // App entry point (@main)
  ContentView.swift    // First screen
  Assets.xcassets/     // Images, colors, app icon

I recommend creating these folders early so the project doesn't become a flat mess:

Views/    // SwiftUI screens
Models/   // Data structures
Services/ // API calls, business logic

SwiftUI vs UIKit — make this decision once

If you're starting in 2025+, choose SwiftUI. Here's why:

  • Declarative syntax — much closer to React/modern web frameworks than UIKit's imperative approach
  • Live Previews — see your UI change as you type, no need to launch the simulator for every change
  • Apple is investing here — new APIs land in SwiftUI first
  • Less boilerplate — a button in SwiftUI is 2 lines; UIKit requires a subclass and delegate

The main downside: some complex UI patterns (very custom animations, certain table/collection view behaviors) are still easier in UIKit. But for an AI chat interface + informational screens, SwiftUI is perfect.

Key SwiftUI patterns to know upfront

// @State — local view state
@State private var message = ""

// @StateObject — view-owned ViewModel
@StateObject private var viewModel = ChatViewModel()

// @ObservedObject — ViewModel passed in from parent
@ObservedObject var viewModel: ChatViewModel

// NavigationStack (use this, not NavigationView)
NavigationStack {
  ContentView()
    .navigationTitle("Chat")
}
Tip: Set up Claude Code in your terminal before writing any Swift. When you're stuck on a build error or need a complex SwiftUI component, Claude Code reads your entire codebase and writes the exact code you need. It was the single biggest accelerant in this project.

2

Set Up GitHub and Version Control

Do this before writing significant code. Git saves you constantly.

# In Terminal, navigate to your Xcode project folder
git init
git add .
git commit -m "feat: initial Xcode project setup"
git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO.git
git push -u origin main

Create a .gitignore file in your project root — Xcode generates files you never want to commit:

# Xcode
*.xcuserstate
xcuserdata/
.DS_Store
DerivedData/

# Secrets — NEVER commit these
Secrets.swift
.env

Branch strategy that works for solo developers

  • main — stable, releasable code only. TestFlight builds come from here.
  • dev — active development. Merge into main when a feature is complete.
  • feature/name — for bigger features, branch off dev.

3

Build the Railway Backend (API Proxy)

This is the most important architecture decision in the entire project. Never put your Anthropic API key directly in your iOS app. The app binary is publicly downloadable — anyone who downloads it can extract the key and bill charges to your account.

The solution is a simple backend proxy. Your iOS app sends messages to your Railway server, the server adds the API key and forwards the request to Anthropic, then sends the response back. The key never touches the device.

// Architecture
iPhone → POST /api/chat → Railway server → Anthropic API → response → iPhone

Set up Railway

  1. Go to railway.app and create a free account
  2. Create a new project → Deploy from GitHub repo
  3. In Railway's Variables tab, add: ANTHROPIC_API_KEY = sk-ant-...
  4. Railway gives you a public URL like your-app.up.railway.app

The proxy server (Node.js / Express)

// server.js — minimal Claude API proxy
const express = require('express');
const app = express();
app.use(express.json());

app.post('/api/chat', async (req, res) => {
  // Verify the app secret (lightweight auth)
  if (req.headers['x-app-key'] !== process.env.APP_SECRET_KEY) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  const response = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.ANTHROPIC_API_KEY,
      'anthropic-version': '2023-06-01',
      'content-type': 'application/json'
    },
    body: JSON.stringify(req.body)
  });

  const data = await response.json();
  res.json(data);
});

app.listen(process.env.PORT || 3000);
The APP_SECRET_KEY trick: Set a second environment variable APP_SECRET_KEY in Railway, and hardcode the same value in your Swift Constants.swift. Your iOS app sends it in every request header. The server rejects requests without it. This prevents random people from hitting your Railway endpoint and running up your Anthropic bill — without requiring user accounts or complex auth.

4

Build the AI Chat Interface in SwiftUI

The core of the app is a chat UI — a scrolling message list and a text input at the bottom. Here's the architecture that works well:

// ChatViewModel.swift — state and API calls
class ChatViewModel: ObservableObject {
  @Published var messages: [ChatMessage] = []
  @Published var isLoading = false

  func send(text: String) async {
    isLoading = true
    messages.append(ChatMessage(role: .user, content: text))

    let reply = await ClaudeService.shared.sendMessage(text)
    messages.append(ChatMessage(role: .assistant, content: reply))
    isLoading = false
  }
}
// ClaudeService.swift — calls your Railway proxy
func sendMessage(_ text: String) async -> String {
  var request = URLRequest(url: URL(string: Constants.backendURL)!)
  request.httpMethod = "POST"
  request.setValue("application/json", forHTTPHeaderField: "Content-Type")
  request.setValue(Constants.appSecretKey, forHTTPHeaderField: "x-app-key")

  let body: [String: Any] = [
    "model": "claude-haiku-4-5-20251001",
    "max_tokens": 1024,
    "system": systemPrompt,
    "messages": [["role": "user", "content": text]]
  ]
  request.httpBody = try? JSONSerialization.data(withJSONObject: body)

  let (data, _) = try! await URLSession.shared.data(for: request)
  // Parse Claude's response...
}

Model selection: Haiku vs Sonnet

For a chat interface where responses should feel fast, use claude-haiku-4-5. It's 5–10x cheaper than Sonnet and returns responses in 1–3 seconds. Use Sonnet for tasks that require deeper reasoning — not conversational Q&A.

Writing the system prompt

The system prompt is what turns Claude from a general AI into a domain expert. Be specific — the more context you give about the domain, tone, and format, the better the responses.

// Example: RDR2 expert system prompt (condensed)
You are an expert guide for Red Dead Redemption 2. You have
encyclopedic knowledge of missions, characters, locations,
collectibles, honor mechanics, hunting, fishing, and secrets.

Tone: knowledgeable but friendly. Like a friend who has
completed RDR2 100% and loves helping others.

Format: short paragraphs, no markdown headers unless listing
multiple steps. Always be specific — no vague answers.

5

Test on Simulator and Real Devices

Xcode has an excellent iOS Simulator built in. Use it constantly — you don't need a physical device for most testing.

# Build and run in terminal (or just hit ▶ in Xcode)
xcodebuild -scheme YourApp \
  -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
  build

# Take a screenshot from the simulator
xcrun simctl io booted screenshot ~/Desktop/screen.png

Things to test on a real device that the simulator can't replicate:

  • Network latency (simulator is always fast; real devices vary)
  • Keyboard behavior (especially avoiding keyboard overlap on chat inputs)
  • Face ID / Touch ID flows
  • How the app feels to actually use

6

Ship via TestFlight

TestFlight is Apple's beta testing platform — a required step before App Store submission. You upload a build, share a link, and testers install it on their device.

  1. In Xcode → Product → Archive. This creates a release build.
  2. In the Organizer that opens, click Distribute App → TestFlight & App Store
  3. Upload. Wait 5–15 minutes for processing.
  4. In App Store Connect, go to your app → TestFlight → Share the install link
You need an Apple Developer Program membership ($99/yr) to distribute via TestFlight or the App Store. There's no way around this.

7

Submit to the App Store

The App Store submission is mostly done in App Store Connect. You'll need:

  • App name and description — choose carefully; this is your App Store listing
  • Screenshots — required for iPhone (6.7" is most important) and iPad if you support it. Size: 1290×2796 px for iPhone 16 Pro Max
  • App icon — 1024×1024 px PNG, no transparency, no rounded corners (Apple applies them)
  • Privacy label — declare what data your app collects. If you use the proxy pattern above, your app collects nothing — select "Data Not Collected"
  • Age rating — answer the questionnaire honestly

Once everything is filled out, submit for review. The first review typically takes 1–3 days. Subsequent updates are often approved same-day.


The Tools Summary

Here's the complete toolkit with honest assessments:

Tool Cost Verdict
Xcode + SwiftUI Free Essential. No alternative for iOS.
Claude API ~$0.001/msg (Haiku) Excellent for expert personas. Start with Haiku.
Railway $5/mo or less Best DX for solo devs. Deploy from GitHub in 60s.
GitHub Free Non-negotiable. Use it from day one.
Claude Code Subscription Worth every dollar. Best AI coding assistant available.
Apple Developer Program $99/yr Required to ship. Pay it, no alternative.
Vercel (for website) Free tier Best static hosting. GitHub push = instant deploy.

Lessons Learned

  • Read the CLAUDE.md pattern. Create a CLAUDE.md file in your project root with project context — AI coding assistants load this automatically and dramatically improve their output.
  • Build the proxy first. Don't touch the iOS UI until your backend is working and your Railway URL is responding. Test it with curl before writing a single line of Swift networking code.
  • Screenshot everything. After any UI change, take a simulator screenshot and compare. The Xcode Live Preview is helpful but the simulator is the ground truth.
  • Ship earlier than feels comfortable. The RDR2 app went from zero to App Store submission in weeks. Version 1.0 doesn't have to be perfect — it has to be good enough to learn from real users.
  • The App Store review is not as scary as it sounds. Follow the guidelines, set your privacy labels honestly, and don't do anything obviously against the rules. Most apps are approved.

Next: Securing Your API Key → App Store Submission Guide →

Building something similar? Questions about the stack?

Book a free 30-minute discovery call →