iOS App Development in Swift and SwiftUI
iPhone and iPad apps built by people who spend every week inside Xcode, App Store Connect and TestFlight. We know which review rules bite, which background work iOS will refuse to run, and which parts of Apple’s design language are worth arguing with.
- Apple account in your name
- TestFlight from sprint two
- Review handled by us

SwiftUI by Default, UIKit on Purpose
Every new iOS project we start is Swift, and almost every screen in it is SwiftUI. That is not fashion. SwiftUI gives you Dynamic Type, dark mode, right-to-left layout, VoiceOver labels and sensible behaviour on every screen size largely for free, and retrofitting those things onto a UIKit app later costs several weeks you did not plan for.
But SwiftUI is not the answer to everything, and any studio that tells you otherwise has not shipped enough of it. There are places where it is still the wrong tool, and knowing them in advance is the difference between a clean build and a screen that gets rewritten twice.
The two frameworks live in the same app. A SwiftUI screen can host a UIKit view controller and the other way round, so this is a per-screen decision rather than a project-wide one.
SwiftUI is the right call for
- Lists, forms, settings, onboarding and anything that is mostly data on a screen
- Apps that need to look right on the smallest supported iPhone and a Pro Max without a second layout
- Accessibility and Dynamic Type as a default rather than a retrofit
- Widgets and Live Activities, which cannot be written in UIKit at all
- Teams that will hand the code to another developer in two years and want it recognisable
We drop to UIKit for
- Custom camera capture with manual exposure, focus or frame-level processing
- Very large or infinitely scrolling collections where cell reuse behaviour has to be controlled precisely
- Interactive, gesture-driven transitions that need frame-by-frame control
- Text editing beyond the basics, where the underlying text system still has to be reached directly
- Controls that need behaviour SwiftUI has not exposed yet on your minimum supported version
Concurrency is the other decision that ages badly. We write asynchronous work with Swift’s structured concurrency rather than nested completion handlers, because the compiler can then tell you about a data race before a tester finds it as an intermittent crash.
Follow Apple Until Following Apple Costs You the Product
The Human Interface Guidelines are not law, and they are not decoration either. They describe what an iPhone user has already learned. Breaking them is sometimes right, but it is never free, and it should always be a decision somebody made out loud.
Follow, almost always
The Grammar of the Platform
- The edge swipe that goes back. Break it and people feel trapped.
- Safe areas, the home indicator and the notch or island region
- Touch targets big enough for a thumb on a moving train
- Dynamic Type, so text that is set large in Settings is actually large
- Standard share, context menu and pull-to-refresh behaviour
- System alerts and sheets, rather than a homemade modal that traps focus
Depart, deliberately
Where the Brand Has to Win
- Colour, type and shape. A stock grey app is not a well-behaved app.
- A custom tab bar when five destinations genuinely are not enough
- Bespoke primary actions where a floating control beats a toolbar button
- Illustration and motion that carry meaning rather than filling space
- Onboarding that teaches rather than a carousel nobody reads
The rule we work to: deviate on how it looks, rarely on how it behaves. A user forgives an unfamiliar colour instantly and never forgives a back gesture that does nothing. More on this in app UI and UX design.
On iOS, Privacy Is Now Part of the Build, Not the Paperwork
What your app collects used to be a claim you made on a form. It is now something Apple inspects in the binary, which changes who has to care and when.
Privacy manifests
A declaration shipped inside the bundle listing collected data types, tracking domains and the reasons for certain system APIs. Your app has one; every third-party SDK is expected to bring its own signed version. We build yours from the dependency graph, not from a questionnaire.
Required-reason APIs
A short list of ordinary-looking calls, such as reading file timestamps, free disk space, system boot time and user defaults, that can be chained together to fingerprint a device. Using them is fine; using them without declaring an approved reason is not.
App Tracking Transparency
Required before you link a person or device to advertising across other companies’ apps and sites. Most people decline. We treat it as a business decision with a measurable cost rather than a checkbox an SDK quietly turns on.
Privacy nutrition labels
The public summary on your store listing: what is collected, whether it is linked to identity, whether it is used to track. Reviewers compare it with the binary. A mismatch is one of the least defensible rejections there is.
Permission strings that mean something
Every capability needs a usage description written for the person holding the phone, not for the reviewer. Vague strings get flagged, and even when they pass, people deny a prompt they do not understand, which quietly breaks the feature anyway.
Collecting less on purpose
The cheapest way through all of this is to want less data. Anonymous identifiers instead of device ones, aggregated events instead of user-level rows, and a deliberate decision about whether an analytics SDK is worth the declaration it forces you into.
We go further into this on the app security and privacy page, including what happens to all of it when the app has a backend.
Nothing Goes to Review Before It Has Been on Real Phones
TestFlight is not a formality at the end. It is how a build stops being a demo and starts being software, and the two tester tiers behave very differently.
Internal testers
- People with a role on your App Store Connect account, which means you and us
- Builds appear within minutes of processing finishing, with no review step at all
- Used for the daily sprint build, so broken things are caught before anyone outside sees them
- The right tier for anything half-finished or behind a feature flag
External testers
- Anyone you invite by email, or anyone at all through a public TestFlight link
- The first build of a version goes through beta review, which is lighter than App Review but is a real gate
- Beta review uses the same pipeline, so signing, entitlement and manifest problems surface here instead of on launch week
- Builds expire after a set period, so a beta group needs a regular release rhythm rather than one drop
What Beta Review Actually Checks
Signing and entitlements
Whether the build is signed correctly and whether it claims capabilities it is actually allowed to have. This is the single most common reason a first external build stalls.
Obvious policy problems
Not the full guideline sweep, but enough that a build which will clearly fail App Review often fails here first, which is a gift rather than a setback.
That it runs at all
A build that crashes on launch on the reviewer device does not get distributed. Discovering that a week before submission is the entire point of the exercise.
Testers send feedback with a screenshot attached straight from the TestFlight app, and crash reports come back to us automatically. Our wider approach to device coverage is on app testing and QA.
The Rejections That Actually Catch People
Review is usually quick once a build is clean. What costs time is a rejection that could have been designed out months earlier. These are the ones we see, and the ones we scope around before any code gets written.
Something the reviewer could not reach
- No demo account, or one that stopped working since you created it
- A feature gated behind a code, a physical device, a paid plan or a partner integration with no way to demonstrate it
- A region lock that means the app does nothing useful from where the reviewer sits
- The fix is a reviewer notes field that reads like instructions for a new colleague, plus a demo account we test the morning we submit
Money going around Apple
- Digital goods or a subscription sold through anything other than in-app purchase
- A link, a button or a hint steering people to pay on a website
- Unlocking paid content that was bought elsewhere, without the corresponding in-app option
- Physical goods and real-world services are the exception, and getting that boundary right is a scoping conversation, not a submission-week one
Accounts without an exit
- If people can create an account in the app, they must be able to delete it in the app
- Pointing them at an email address or a web form does not satisfy it
- It is a backend endpoint, a confirmation flow and a decision about what deletion means for data you are legally required to keep
- Routinely forgotten, and always discovered at the worst possible moment
User-generated content with no controls
- Any app where people post, comment, message or upload needs moderation built in
- That means filtering, a way to report content, a way to block another user, and a stated method of acting on reports
- Terms that users agree to, published somewhere reachable
- This is a whole feature set, not a checkbox, and it belongs in the first estimate
Thin, duplicate or placeholder
- An app that is a website in a frame, with nothing that needed a phone
- Near-identical apps from one account, or a template filled in differently
- Lorem text, dead links, empty states that look like bugs, a beta label in the listing
- Screenshots showing features the build does not have
Declarations that do not match the binary
- A privacy label saying no data is collected while an SDK ships a device identifier
- Tracking declared but the ATT prompt never shown
- Required-reason APIs used with no declared reason, often from a dependency rather than your own code
- Calling private or undocumented APIs, which static analysis finds and which usually means rewriting the feature
A rejection is a conversation
It arrives as a message in the resolution centre with a guideline reference. Most are answered the same day with an explanation, a screen recording or a small change. You never need to learn the numbering.
Appeals exist
When we believe a rejection is a misreading rather than a real breach, there is a formal route to argue it. Sometimes that is faster than redesigning a feature, and sometimes it is not. We tell you which we think it is.
Expedited review is a favour
It can be requested for genuine emergencies such as a crash affecting everyone. Spend it on an ordinary release and it stops working when you actually need it.
The full submission sequence, including listing assets, phased release and what happens on update day, lives on app launch and submission.
Subscriptions Are a Product, Not a Payment Screen
Most of the work in a subscription app is not taking the first payment. It is everything that happens to that subscription afterwards, and almost all of it is invisible until it goes wrong.
What the store handles for you
- Payment, tax, currency and refunds in every territory you sell in
- Free trials and introductory offers, with eligibility worked out for you
- Billing retry and a grace period when a card fails, instead of an instant cut-off
- Upgrades, downgrades and the proration between tiers in the same group
- Family Sharing, if you choose to enable it on a product
- Offer codes and win-back offers you can run without shipping a build
What you have to build
- A purchase screen stating price, duration and renewal terms before the tap, with links to terms and privacy
- A Restore Purchases control, because people change phones
- Verification of every transaction, so entitlements cannot be forged by a jailbroken device
- A server listening for store notifications, so a cancellation, refund or failed renewal reaches your backend without the app opening
- A defined state for lapsed and refunded users that is not simply a locked screen
- A route to manage or cancel the subscription, rather than hiding it and collecting one-star reviews
Commission rates, the programmes that reduce them and the rules on linking out all change from year to year, so we settle the specifics with you at scoping rather than quoting a number here that will be wrong by next spring. Monetisation strategy sits on app analytics and monetisation.
Widgets, Live Activities, App Clips and the Limits of Background Work
The most valuable iOS features are often the ones a person never opens the app to use. They also come with the platform’s tightest constraints, which is exactly why they get scoped badly.
Widgets
- A widget does not run continuously. It hands the system a timeline of prepared states and the system decides when to show them.
- Refreshes come out of a daily budget that depends on how often the person actually uses your app
- So a widget showing a number that changes every minute is the wrong widget. One showing today’s figure, the next appointment or a streak is the right one.
- Interactive elements can trigger work without opening the app, within strict limits
- Home Screen, Lock Screen and StandBy all want different densities of the same information
Live Activities
- For something with a beginning and an end that a person wants to watch: a delivery, a journey, a match, a timer, a job in progress
- Updated by push from your server, so the backend work is real
- They expire, so anything long-running needs a graceful ending rather than a stuck card
- Badly used, they are the fastest way to have notifications turned off entirely
App Clips
- A slice of the app launched by a code, an NFC tag, a link or a place in Maps, with no visit to the store
- A hard size budget that forces the slice down to one job and almost no dependencies
- No background work, limited data access, and a deliberately short life before it is cleaned up
- Worth building when somebody is standing in front of a physical thing. Otherwise it is a demo, not a feature.
Background execution
- App refresh tasks are opportunistic. The system weighs usage, battery, charging state and low power mode, then decides.
- Processing tasks handle longer jobs and typically wait for overnight charging
- Background download and upload sessions keep transferring after the app is suspended, and hand the result back on relaunch
- Silent push can wake the app, but it is throttled and never guaranteed
- Continuous location, audio and a handful of other modes are declared capabilities that Apple looks at closely
- Design rule: nothing critical may depend on background work having already happened
Two Questions That Move the Estimate More Than Any Feature
Both get decided in the first week, because both are cheap to answer early and expensive to revisit.
Is iPad in or out?
An iPhone-only app already runs on an iPad, in a scaled window. Whether that is acceptable is a commercial judgement rather than a technical one, and pretending otherwise is how iPad support ends up half done.
Doing it properly means designing for a second shape entirely: a wide layout with a sidebar or split view rather than a stretched phone screen, keyboard shortcuts and a pointer that changes what a hover means, text selection and drag and drop between apps, and the fact that on an iPad your app may be one of several visible at once and can be resized while it runs. Multiple windows of your own app is a further step again.
Every one of those has a design cost and a test cost. We price iPad as an explicit line rather than quietly assuming it either way, and for a lot of consumer apps the right answer is to leave it out of version one and say so.
How old an iPhone are you supporting?
iOS users move to new versions quickly, so the tail is thinner than people expect. One version back from current suits most consumer apps. Two is defensible when you are selling into organisations that manage their own device refresh cycles, or where your audience skews towards older hardware.
What it costs you is not abstract: every newer API needs an availability check and a fallback path, some SwiftUI behaviour differs between versions in ways you only find on a real device, and the test matrix grows by a whole column. That is real hours, and it shows up in the estimate.
The asymmetry matters. Raising the minimum later is a one-line change. Lowering it later means auditing every screen you have already built. So we pick it deliberately at the start and write it down.
When Native iOS Is the Wrong Choice
We build native and cross-platform, so there is nothing in it for us either way. This is roughly how the conversation goes.
Look at cross-platform instead
If Any of These Are True
- You need Android at the same time and one budget covers both
- The app is mostly screens, lists, forms and API calls
- You are testing whether the idea works before investing properly
- One small team will be maintaining it for years afterwards
Native iOS earns its cost
If Any of These Are True
- Your audience is overwhelmingly on iPhone and Android can wait
- Widgets, Live Activities, App Clips or Watch are core rather than nice to have
- Camera, sensors, on-device processing or heavy animation carry the product
- You want new platform features in the weeks after they ship, not months
A simplification of a longer conversation. We give the real recommendation on the first call, including the times it is the cheaper option.
Android App Development
Kotlin, Compose, Play Console rollouts and the fragmentation problem that has no iOS equivalent.
Learn moreCross-Platform Apps
What you actually give up and actually gain by sharing one codebase across both stores.
Learn moreApple Watch & Apple TV
When a companion app on the wrist is worth building, and when it is a vanity addition.
Learn moreiOS Questions We Get Asked
What is a privacy manifest and does our app need one?
A privacy manifest is a file inside the app bundle that declares what data the app collects, which tracking domains it contacts, and the reason it calls certain system APIs that Apple treats as a fingerprinting risk. Common examples of those APIs are reading file timestamps, free disk space, system boot time and user defaults. Widely used third-party SDKs are expected to ship their own signed manifest, and your app has to account for the ones that do not. If the manifest disagrees with what the binary actually does, the upload gets flagged or the submission comes back. We generate it from the dependency list and the code rather than from good intentions, and we check it again every time a library is upgraded.
How far back should the app support older iOS versions?
Usually one version back from current, sometimes two. iOS users update quickly compared with Android, so the tail of very old devices is thin, and every extra version you support costs design work, testing time and code paths that guard newer APIs. The decision is a business one: look at who your users are, whether they are consumers or a managed fleet inside a company, and what the oldest hardware in that group can still run. We agree a deployment target during scoping and write it into the estimate, because raising it later is trivial and lowering it later is not.
Will asking for tracking permission hurt us, and can we avoid it?
App Tracking Transparency applies when you link data about a person or a device to advertising or data brokerage across apps and websites owned by other companies. Plenty of apps never need it. First-party analytics that stay inside your own app, crash reporting and basic product measurement generally do not require the prompt, while an advertising SDK that reads the advertising identifier does. Most people who see the prompt decline it, so the honest advice is to design measurement that works without it wherever you can, and to ask only where advertising attribution genuinely pays for itself. Declaring tracking on the privacy label and then never showing the prompt is a rejection.
Can we push a fix to users without waiting for App Review?
Not for anything that changes the compiled app. Any change to the binary goes through review, which is usually quick but is never guaranteed on a particular day. What you can change without a submission is everything the app fetches at runtime: server-side content, feature flags, remote configuration and the data behind a screen. So we build the flags in from the start, which means a broken feature can be switched off in minutes instead of waiting on a build. If a submission is genuinely urgent there is an expedited review request, which is meant for real emergencies and quietly loses its effect if you spend it on ordinary releases.
Why does our background refresh not run when we expect it to?
Because on iOS you do not schedule background work, you request it. Background app refresh tasks are opportunistic: the system decides when to run them from how often the person opens your app, the battery level, whether the device is charging, network conditions and low power mode. An app somebody uses daily gets far more background time than one they opened once. Longer jobs need a processing task that typically runs overnight while charging, large transfers should use a background download or upload session that the system finishes on your behalf, and anything that truly has to happen now needs a push notification to trigger it. If a feature only works when the refresh runs on schedule, that feature is designed wrong for iOS.
Is an App Clip worth building alongside the app?
Only when there is a real moment of first contact away from the store. An App Clip is a small slice of the app that launches from a code, an NFC tag, a link or a place in Maps, does one job and then goes away. It suits ordering at a table, paying for parking, renting something on the spot or checking in at a venue. It is a poor fit for anything that needs an account, ongoing state or background work, and it has a strict size budget that forces you to strip the slice down to almost nothing. If nobody is going to be standing in front of a physical thing with a phone in their hand, the App Clip is decoration and the budget is better spent on the main app.
Get an iPhone App Scoped Properly
Tell us what the app has to do. You get a written scope, a deployment target, an iPad decision and a cost range back within two business days.