Building an iOS App With AI
Sat 07 February 2026

Lately I’ve been reading a lot of stories about how people are using LLMs for developing software with notable success. I’ve been experimenting with these models myself, but mostly adding simple features to an existing app or fixing bugs. I wanted to try creating a complete program from scratch leveraging AI tools as much as I could to see how well the process works with various tools and approaches.

I picked a problem that I thought would be fun but was probably something I wouldn’t get around to building if I had to code it all myself. This is one of the promises of LLMs - they lower the effort significantly such that you can create applications and tools that you otherwise wouldn’t consider. That’s attractive for a lot of personal, “hobby” level programs.

Getting Started

The application is an iOS app to track weight workouts. I started with this prompt using Codex.

Create an Xcode iOS app project for the iPhone using SwiftUI that will allow me to track my weightlifting workouts. The app should allow me to create Workouts which are made up of Exercises. Each exercise has a number of Reps and a Weight in lbs. Each day I workout, I can record a completed workout and make any adjustments to the exercises/reps/weights that I actually performed. I can also enter notes for that completed workout. This will build a history of past workouts.

The app should be easy to navigate and to see the elements of each exercise. The app will exist only on the iPhone - it does not need to work on an iPad or the Mac. It does not need to synchronize data across devices, but I would like to export the data to a file (e.g. JSON or CSV) and also import historical data that I’ve already been tracking.

The prompt covers the basic features, but leaves a lot for the model to decide on its own. From this, Codex created an Xcode project with a number of model classes backed by a JSON file store. It created all the required SwiftUI views and a tabbed interface for the app. But when I tried to build the project in Xcode, it didn’t compile, primarily due to Xcode/Swift/SwiftUI version incompatibilities.

This is something you get used to with these tools - LLMs are usually trained on data no more recent than 6 months ago, but coding tools, APIs, and libraries are changing all the time. AI tools often produce code that is slightly outdated. But LLMs are good at solving errors given an error message, so I was able to work through this pretty easily. Here are some examples of the kinds of errors I worked through to get the app to build and run.

I get one build error in ContentView: Call to main actor-isolated initializer ‘init(fileName:)’ in a synchronous nonisolated context

I added @MainActor but am still getting the same error

I no longer get the error in ContentView, but now I get two errors in WorkoutArchiveDocument:
Line 18: Call to main actor-isolated static method ‘decode(from:)’ in a synchronous nonisolated context
Line 22: Call to main actor-isolated static method ‘encode’ in a synchronous nonisolated context

Note: I built this app using Xcode 26.0, which introduced Coding Assistant - limited LLM capabilities similar to early versions of GitHub Copilot. Xcode 26.0 did not include support for programming agents like Claude Code or OpenAI Codex, but this was recently added in 26.3 after I built this project; thus, most of my use of LLMs for this project involved using OpenAI Codex from the command line.

Using Codex in the terminal with an Xcode project works fine. Codex knows how to modify an Xcode project to add new files and change project settings, and Xcode recognizes when files have changed and reloads the project and source files.

Xcode’s Coding Assistant is fine for small tasks like fixing bugs, so I used Codex for the larger tasks like adding a new feature or creating a new view. I used Xcode’s Coding Assistant for smaller tasks such as fixing a warning or tweaking a SwiftUI view. I also used ChatGPT a little bit using its “Work with Apps” feature. That works, but it only can see one source file at a time, so it’s a bit limited.

Pair Programming

The initial prompt got me started with an app I could play around with, which helped me refine my ideas. Once the basic functionality was working, my interaction with Codex was more like working with a fellow (albeit junior) programmer. I would report issues, ask for new features or changes in behavior, describe improvements to the UI, and then Codex would make changes to the code base.

OK. It builds now, and I was able to run it on my phone. I tested the app and found a few issues: 1) an Exercise needs a number of Sets (integer); 2) I should be able to type a number in for Weight - it’s too tedious to use a spinner to increase it to 100 lbs for example; 3) I’d like to add an Equipment field to Exercise which uses a picker for “Barbell”, “Dumbbell”, or “Other”; 4) rename Plans to Routines; 5) there is a bug when I add more than two exercises where all of the additional ones (2 through n) mirror each other when I edit any property. So if I change the name of Exercise 3, then the names of Exercise 2 and 4 also change.

These smaller iterations on the app were easier to manage since the context of a working app helped me create more specific prompts, which in turn helped the LLM’s accuracy in terms of hitting the mark that I expected. This interaction felt reminiscent of sitting next to a co-worker, watching what they did, and providing guidance and feedback. I also found myself in more of a directorial role than that of a pure developer. I would make higher-level decisions like UI design changes and specific names to use for labels and classes, and then let Codex work through the implementation details.

I’ve added comments to each Equipment enum value with the name of an SF Symbol. I’d like to use those in the picker for the exercise. Can you modify the enum to include the SF symbol names as an associated value and then use that to create Labels for the picker with the symbol and text value?

Sometimes Codex would make decisions that I didn’t agree with, and I would tell it so. Again, that’s common when working with junior or peer developers.

I actually prefer the standard picker. Can you revert that last change?

But I also would ask for large-scale changes to the app and let Codex grind away on a solution. I learned to be more specific and to have Codex ask me questions if I wasn’t clear enough.

Add a new feature where the app will call out time reminders as I’m resting between sets and exercises. The way it should work is:
- once I start a workout, the app should begin listening for audio commands
- if it hears the word “set” it should start a 45-second countdown as follows: to begin it says “45” then alternating tick/tock sounds every second until it gets to thirty when it calls out “thirty” and then the tick/tock sounds until it calls out “15” and then tick/tock sounds until the last 5 seconds in which case it uses a beep sound until the countdown is done and it finally ends with “done”
- if it hears the word “rest” it does the same thing but it begins the countdown from 60 instead of 45.
- once the workout is saved or canceled, it stops listening for audio commands

Since this is a bit of a complex feature, please ask me questions if anything isn’t clear. Also, explain to me how you are going to implement this feature before you begin so I can confirm your approach (e.g. which libraries will you use for speech recognition and audio sounds, and how will you track the background timer activity).

This is the kind of feature that’s fun to add, but if you’ve ever worked with Apple’s Audio or Speech APIs, you know there is a sizeable learning curve to use them properly. Without the LLM’s help, I wouldn’t have taken the time to bother with this.

The AI/Human Split

To track the code I wrote vs. the code an LLM wrote, I tagged git commits with an “(AI)” prefix. Then I had ChatGPT create a short Python script using git ls-files and git blame to count up the lines of code attributed with each commit comment. While this approach isn’t perfect, as I would sometimes make small changes on top of Codex’s changes before committing, it’s pretty close, and the results align with my expectation.

% git log --reverse --oneline
830c646 (AI) initial checkin after fixing compilation errors
d3f76f3 Added icon. Added sets, equipment, and notes to Exercise.
143310b Renamed "routine" back to "workout". Added symbols to Equipment enum but segmented Picker only supports text OR>
395ab0f (AI) Add "work" calculation to Exercises and Workout. Display it in History.
b94f95b (AI) Added StatsView for a chart of history.
d28d8e5 (AI) Added voice commands and countdown timer.
...

The results show that I was responsible for about 12% of the lines of code, and “AI” was responsible for the other 88%. That’s just one data point, but I’d say it feels right for this and other similar projects that I’ve completed in the last few months. Remember, this is the kind of app that I probably wouldn’t have bothered to tackle without the use of an LLM, so I expect it to do the large majority of the work; otherwise, what’s the point? If these tools can do 80-90% of the coding and I can just add my tweaks and direction as needed, then it’s a big success.

AI Hits and Misses

I took away a number of observations from this experience, some positive and some negative.

Very short time to “personal MVP” - Getting a basic working version of the app up and running quickly creates momentum. Going from an idea, to an initial prompt or spec, and then a basic working app in 15-30 minutes is amazing and gets the ball rolling where it feels easy to add to it.

Automates the drudgery - Researching error messages and trying out fixes is not work I enjoy. The LLMs are very good at proposing solutions to errors. Sometimes it takes a couple of iterations, and you may need to “prune” the model’s solution path to nudge it in the right direction, but overall this is an area where LLMs shine.

IDE integration is not required - While LLM coding assistants first appeared in IDEs like GitHub’s Copilot in VS Code, or Cursor’s VS Code fork, the best coding assistants today were designed to work in the command line. For this app, I mostly used Xcode to compile the app, play around with SwiftUI previews, and use the iOS Simulator.

Prompt history documents the development process - The history of the prompts and responses provides a story of how the app was built and is helpful in re-familiarizing yourself with the app. I found this quite useful when I came back to the app after a week or so and couldn’t remember what I’d last done. This is especially true when you are no longer writing most of the code. I used an open source tool codex-viewer to help here. This tool is based off the similar claude-code-viewer tool for Claude Code.

LLMs stuck in the recent past - Because the training data is always a bit out of date for an LLM, they often use slightly old versions of APIs or libraries or even language syntax. I ran into this right away because I was using a new version of Xcode (with new Swift features and new SwiftUI libraries) that was released after Codex was trained. At one point, Codex insisted I was asking it to use a version of the iOS SDK that didn’t exist.

I want to use iOS 26. What changes do I need to make to get that to work?

There isn’t an iOS 26 SDK—Xcode only supports version numbers Apple has actually released (currently iOS 17.x, with 18 in preview). You can’t set a deployment target higher than what ships with the SDK, so the project can’t be built for iOS 26. If you meant a real version (say iOS 16 or 17), let me know which, and I’ll walk through the exact project-setting changes.

It pays to be specific - The LLM version of “garbage in, garbage out” is probably “ambiguity in, confident nonsense out.” Taking the time to be more specific with your prompts really helps. If you’ve ever outsourced development work to a new developer that isn’t familiar with your product or business, this won’t be surprising. But there’s something about the casualness of typing in a prompt box where you don’t feel the need to be explicit and exact in your instructions. And you don’t need to overdo it, but being somewhat explicit, offering examples, and asking for clarifying questions go a long way in getting the output you expect.

Claude Code even has a built-in “plan mode” focusing on just that aspect of the process. I’ve had very good success on other projects using this feature to create a detailed spec, iterating on it a few times to get it right, then asking Claude to execute the implementation plan in the spec. (In hindsight, I probably should have done that for this experiment.)

LLMs are not great at reuse and refactoring - They don’t recognize that they’ve done something similar elsewhere in the project where they could extract a class or function, generalize it a bit, and reuse it. You can ask them specifically to do this, but you need to do the work of identifying those opportunities and requesting the changes.

LLMs can be good debugging assistants - I ran into some thorny problems with audio, speech recognition, and asynchronous routines. This was compounded by the fact that these aspects of iOS perform differently in the iOS simulator than they do on the actual device. But by giving Codex a detailed description of the behavior, it was able to hypothesize as to what the problems and potential solutions might be, and that helped me eventually solve them.

Considerable Net Positive

I’m impressed by what these tools can do and how quickly they’ve evolved to this point (this project was developed in early 2026). I find using LLMs analogous to other powerful development tools and languages in the past (debuggers, toolkits, IDEs), but their capabilities are much broader, and their limits seem to keep expanding.

I recently read an article that talked about two kinds of developers: builders and coders. The main idea was that builders enjoy the process of creating something new and seeing it come to life, while coders care more about the process of writing the actual code. The builders see AI coding tools as a huge benefit to their work as it allows them to build more rapidly and try out more ideas with less effort. But the coders are more reluctant to use these tools because it takes away the part of the process that they most enjoy. While I recognize aspects of each of those roles in myself, I’ve always leaned more heavily to the builder side. I guess that’s why I find LLM tools so interesting and exciting, even if they are changing the way I now write code.

Throughout my life of developing code and working with software teams, I’ve found the continual changes to the profession and the industry the most exciting part of it. This is what first attracted me to a career in technology - that I’d never be bored. LLMs are the latest example of how things never stay the same, and for me, it’s what continues to make all of this so much fun.