Chapter 13: iOS-Specific Work
Make the app look and feel like it belongs on iOS. Handle the notch, the keyboard, and all the little details.
Your app runs on iOS, but it doesn't quite feel native yet. The status bar overlaps the header. The keyboard covers input fields. There's no app icon. This chapter fixes every one of those issues. These are the details that separate "an app that runs on iOS" from "an iOS app."
What You'll Do
- Handle safe area insets (the notch, home indicator, status bar)
- Fix keyboard behavior so inputs aren't covered
- Set up the app icon for all required sizes
- Configure the status bar style
- Ensure touch targets meet Apple's minimum size guidelines
- Test on the Simulator and a physical device
What You'll Learn
- CSS
env(safe-area-inset-*)variables - How the iOS keyboard interacts with web views
- Apple's app icon requirements (1024×1024 source → many sizes)
- Xcode's asset catalog system
- The
@capacitor/status-barplugin - Apple's Human Interface Guidelines for touch targets
Step 1: The Safe Area Problem
Modern iPhones have a notch (or Dynamic Island) at the top and a home indicator bar at the bottom. If your app ignores these, content will be hidden:
Without safe areas: With safe areas:
┌──────────────────┐ ┌──────────────────┐
│▓▓▓▓ NOTCH ▓▓▓▓▓│ │▓▓▓▓ NOTCH ▓▓▓▓▓│
│er (hidden!) │ │ │
│ │ │ Header │
│ Content │ │ │
│ │ │ Content │
│ │ │ │
│ Bottom Nav │ │ Bottom Nav │
│▓▓ HOME BAR ▓▓▓▓│ │ │
└──────────────────┘ │▓▓ HOME BAR ▓▓▓▓│
└──────────────────┘
The CSS solution
iOS exposes safe area dimensions through CSS environment variables:
env(safe-area-inset-top)— Space below the notch/Dynamic Islandenv(safe-area-inset-bottom)— Space above the home indicatorenv(safe-area-inset-left)— Space on the left (landscape)env(safe-area-inset-right)— Space on the right (landscape)
These values are 0 on devices without a notch (or in the browser) and ~47px on notched iPhones. This means our safe area CSS works everywhere without platform detection.
Step 2: Apply Safe Area Insets
Send this prompt to Claude Code to apply safe area insets to your fixed elements:
$ claude "Update the fixed-position elements in our app to respect iOS safe area insets. T..."Why inline styles instead of Tailwind?
Tailwind doesn't support env() functions in its utility classes. The style prop is the correct way to use CSS environment variables in React. These values are computed by the browser at render time — they're not static.
Step 3: Configure the Status Bar
Install the Status Bar plugin:
npm install @capacitor/status-bar
npx cap sync
Send this prompt to Claude Code to configure the status bar:
$ claude "Configure the iOS status bar in main.tsx. We have a light amber theme, so set th..."Style.Light means dark text — it refers to the background being light, not the text color. This trips up many developers.
Status bar options
| Style | Effect |
|---|---|
Style.Light | Dark text on light background (for light app themes) |
Style.Dark | White text on dark background (for dark app themes) |
Our app uses a light amber background, so dark status bar text (Style.Light) is correct.
Step 4: Fix Keyboard Behavior
When a text input is focused on iOS, the keyboard slides up. In a web view, this can push the entire page or cover the input. Send this prompt to Claude Code to fix keyboard behavior:
$ claude "Fix iOS keyboard behavior in our Capacitor app. Update the Capacitor config so t..."iOS Safari automatically zooms the page when an input with a font size smaller than 16px is focused. This is meant to help readability but creates a terrible UX — the page zooms in and doesn't zoom back. Setting font-size: 16px on all inputs prevents this.
Step 5: Touch Target Sizes
Apple's Human Interface Guidelines require minimum touch targets of 44×44 points. Some of our buttons (especially icon-only ones) might be too small. Send this prompt to Claude Code to fix touch targets:
$ claude "Audit and fix touch target sizes across our app to meet Apple's minimum guidelin..."Check these components for touch target compliance:
- EntryCard — edit and delete buttons
- BottomNav — tab buttons (already good with
py-2+ icon + label) - Header — shake button
- ShakeReveal — dismiss button
Avoid setting min-height: 44px and min-width: 44px on all button and a elements globally — this can break layouts with inline links, compact toggles, or small icon buttons. Instead, apply the .touch-target class selectively to interactive elements that need it, or use adequate padding (e.g., p-2.5 with a w-6 h-6 icon = 44px total).
Step 6: Set Up the App Icon
Apple requires a 1024×1024 pixel source image for your app icon. From this, Xcode generates all required sizes.
Create your icon
You can use any image editor, or ask Claude Code for help:
$ claude "I need a 1024x1024 app icon for a journal app called GratitudeTree. The app uses..."Add the icon to Xcode
- Open the Xcode project:
npx cap open ios - In the left sidebar, navigate to App → Assets.xcassets → AppIcon
- Drag your 1024×1024 image into the App Store slot (1024pt)
- Xcode will automatically generate all required sizes from this source
If you're using Xcode 15+, you only need the single 1024×1024 image — Xcode generates the rest automatically.
Verify the icon
After adding the icon:
- Build and run in the Simulator (
Cmd + R) - Press
Cmd + Shift + Hto go to the home screen - You should see your custom app icon with "GratitudeTree" underneath
Step 7: Deploy to a Physical Device
Testing on a real device is the only way to verify:
- Safe area insets with the actual notch
- Haptic feedback
- Camera access
- Shake detection
- Performance on real hardware
Requirements
- An iPhone connected via USB (or on the same Wi-Fi for wireless debugging)
- A free Apple Developer account (or paid for App Store submission)
- Xcode's automatic signing set up
Steps
- Connect your iPhone to your Mac via USB
- In Xcode, select your iPhone from the device dropdown (instead of a Simulator)
- Go to Signing & Capabilities → select your Apple ID team
- Click Play (
Cmd + R) - On your iPhone, go to Settings → General → VPN & Device Management → trust your developer certificate
- The app installs and launches on your phone
Using the dev server on a physical device
Update capacitor.config.ts to use your Mac's local IP instead of localhost. Find your Mac's IP: System Settings → Network → Wi-Fi → Details → IP Address
Your phone and Mac must be on the same Wi-Fi network.
If you run into issues deploying to a physical device, paste the full Xcode error output directly into Claude Code. It can diagnose signing issues, provisioning profile problems, and device trust errors quickly.
Step 8: Final Polish Checklist
Before moving to Part 6 (TestFlight), walk through this checklist:
| Check | Status |
|---|---|
| Header text doesn't overlap the notch/Dynamic Island | |
| Bottom nav doesn't overlap the home indicator | |
| Status bar text is readable (dark text on light bg) | |
| Keyboard doesn't cover text inputs | |
| Input fields don't trigger page zoom (font-size ≥ 16px) | |
| All buttons are at least 44×44 touch targets | |
| App icon appears on the home screen | |
| Haptics work on the physical device | |
| Camera opens the actual viewfinder (not just file picker) | |
| Shake detection works with a real shake |
Step 9: Production Build
Before moving to TestFlight, let's make a production build. Send this prompt to Claude Code to prepare the production config:
$ claude "Update capacitor.config.ts for production by removing the development server blo..."Then build and sync:
npm run build
npx cap sync
Now the iOS app uses the bundled files from dist/ instead of connecting to a dev server. This is what you'll submit to TestFlight.
Forgetting to remove the server block is the most common TestFlight mistake — the app tries to connect to localhost and shows a blank screen for your testers.
Step 10: Commit Your Progress
git add .
git commit -m "Add iOS polish: safe areas, status bar, keyboard handling, app icon, touch targets"
git push
Checkpoint
Your app should now:
- Safe area insets are applied — content doesn't overlap the notch or home indicator
- Status bar has the correct style (dark text on light background)
- Keyboard doesn't cover input fields when focused
- All interactive elements meet the 44×44 minimum touch target
- The app icon appears on the home screen
- The app runs correctly on a physical iOS device
- A production build works without the dev server
Part 5 Complete!
Your web app is now a fully functional iOS app. Let's recap what Part 5 added:
| Feature | Chapter |
|---|---|
| Capacitor setup and Xcode project | Chapter 10 |
| Native camera and haptic feedback | Chapter 11 |
| Shake-to-discover with motion detection | Chapter 12 |
| Safe areas, icons, keyboard, and polish | Chapter 13 |
In Part 6, we'll take this app to the finish line — setting up an Apple Developer account, configuring App Store Connect, and shipping to TestFlight so other people can install and test your app on their iPhones.
Follow @parvsondhi for build threads, tips, and updates on this tutorial.