Native iOS Development in Swift
An app crashes on cold start — EXC_BAD_ACCESS during initialization of a singleton that accesses another singleton not yet initialized. Or: a ViewController leaks in memory because a closure captures self without [weak self], and this ViewController hangs in memory two screens after the user left it. These aren't hypothetical scenarios — these are the two most common problem classes on iOS projects that come to us after another team.
Native iOS development in Swift is direct platform access. No layer, no performance compromises, complete control over every frame.
SwiftUI vs UIKit: Not a Flame War, an Engineering Solution
SwiftUI appeared in 2019. By 2023–2024, it covers most production tasks. But UIKit isn't obsolete — Apple doesn't deprecate it and continues adding APIs.
Real picture on large projects: hybrid approach. SwiftUI for most screens, UIKit where SwiftUI hits limits.
Where SwiftUI Wins Unconditionally
SwiftUI's declarative syntax reduces UI code 3–5x vs UIKit. A settings screen with List, Toggle, Picker — 40 lines of SwiftUI vs 200 lines of UIKit with UITableViewDataSource delegates.
@State, @Binding, @ObservableObject (and @Observable macro from iOS 17) create reactive binding between data and UI without manual reloadData(). Changing @State variable automatically redraws affected parts. This works correctly if you understand SwiftUI's diff calculation — through Equatable and id in ForEach.
AsyncImage, NavigationStack with type-safe routing through NavigationPath, searchable, refreshable — ready patterns that UIKit requires implementing manually.
Where UIKit Remains Necessary
UICollectionView with compositional layout and diffable data source — complex grids with different cell types, horizontal sections within vertical scroll, dynamic cell sizes. SwiftUI LazyVGrid / LazyHGrid don't provide this control.
Custom screen transitions. UIViewControllerAnimatedTransitioning and UIViewControllerInteractiveTransitioning — interactive pop gesture with partial progress, custom hero transition with precise frame control. SwiftUI matchedGeometryEffect covers some cases, not all.
UITextView with TextKit 2. Rich text editor, custom attributes, custom rendering — TextKit 2 (iOS 16+) moved to async layout, solving long-document performance issues. SwiftUI TextEditor — UITextView wrapper without TextKit access.
UIScrollView with custom behavior. scrollViewDidScroll, parallax effects, sticky headers with custom logic, pull-to-refresh with custom indicator. SwiftUI ScrollView with scrollPosition and onScrollGeometryChange (iOS 17) cover some cases, not all.
Integration: UIHostingController and UIViewRepresentable
UIHostingController wraps SwiftUI view and makes it UIViewController. Embed SwiftUI screen in UIKit navigation stack — no problems.
UIViewRepresentable does the opposite: wraps UIView for use within SwiftUI. MapKit's MKMapView, WKWebView, custom UIKit components — all available in SwiftUI through this protocol.
One pattern we use on projects: UIKit Coordinator manages navigation at flow level, screens themselves implemented in SwiftUI. Coordinator creates UIHostingController, passes ViewModel through initializer or @EnvironmentObject, manages transitions. This gives clean separation: SwiftUI handles UI, Coordinator — navigation.
Combine and async/await: Both Live in One Project
Before Swift 5.5, async code on iOS used Combine or callback chains. With async/await and Actor, concurrency model became part of the language.
async/await + Actor: Modern Approach
// Correct — MainActor guarantees UI updates on main thread
@MainActor
class UserViewModel: ObservableObject {
@Published var user: User?
@Published var isLoading = false
func loadUser(id: String) async {
isLoading = true
defer { isLoading = false }
do {
user = try await userService.fetch(id: id)
} catch {
// handle error
}
}
}
@MainActor — actor isolation guaranteeing main thread execution. Without it, @Published properties can update from background thread, leading to Xcode warnings and potential crashes.
Task { } runs async task from sync context. TaskGroup — parallel tasks with result aggregation. withCheckedThrowingContinuation — bridge between callback APIs and async/await.
Where Combine Remains Useful
Combine isn't replaced by async/await — they complement each other. Combine effective for:
-
Input debouncing.
searchTextField.textPublisher.debounce(for: .milliseconds(300), scheduler: RunLoop.main).sink { ... }— classic search pattern. -
Combining multiple data sources.
Publishers.CombineLatestorPublishers.Zipfor merging two Publishers. -
Transformation operators.
map,flatMap,filter,removeDuplicates— functional stream processing.
On new projects, we use async/await as the primary tool for network calls and business logic, Combine — for UI state reactive binding where @Published + sink is more convenient than explicit task management.
iOS App Architecture
MVVM — baseline pattern. ViewModel contains logic and @Published state, SwiftUI View subscribes through @ObservedObject or @StateObject. One rule: View doesn't know about URLSession, CoreData, UserDefaults.
Clean Architecture adds Repository and UseCase layers. UserRepository abstracts data source (network vs cache). FetchUserUseCase contains business rule. UserViewModel invokes UseCase and manages UI state. Each layer tested independently through protocol dependencies and mock substitution.
TCA (The Composable Architecture) — stricter pattern from Point-Free. State, Action, Reducer, Effect — all explicit, all testable, composable through Scope. Works well in big teams where predictability and isolated reducer testing matter.
Essential Tools
Xcode Instruments. Time Profiler shows where CPU spends time. Allocations — memory leaks and excessive allocations. Leaks — objects that should be freed. Before every release — mandatory run.
Firebase Crashlytics. Crash-free rate, stack trace grouping, breadcrumb events before crashes. Setup in 30 minutes, gives picture across entire device base.
Fastlane match. Certificate and provisioning profile management through encrypted git repository. Eliminates "builds locally but not on CI" once and for all.
XCTest + XCUITest. Unit tests for ViewModel and UseCase, UI tests for critical flows (onboarding, payment, auth).
Process and Timelines
iOS app development: requirements analysis → architectural design → UI/UX (parallel) → development → testing (TestFlight) → App Store submission → support.
App Store Review takes 24–48 hours for standard apps, up to 7 days for first submission or after rejection.
| Complexity | Estimated Timeline |
|---|---|
| MVP (5–8 screens, basic API) | 6–10 weeks |
| Medium app (15–25 screens) | 3–5 months |
| Complex (payments, AR, CoreML, custom UI) | 5–9 months |
Cost — individually after requirements and design analysis.







