10 KiB
iOS Native Development
Complete guide to iOS development with Swift and SwiftUI (2024-2025).
Swift 6 Overview
Key Features
- Data race safety: Compile-time detection (default in Swift 6)
- Concurrency: async/await, actors, @MainActor
- Macro system: Code generation at compile time
- Move semantics: Ownership optimization
- Enhanced generics: More powerful type system
Modern Swift Patterns
Async/Await:
func fetchUser(id: String) async throws -> User {
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
// Usage
Task {
do {
let user = try await fetchUser(id: "123")
self.user = user
} catch {
self.error = error
}
}
Actors (Thread-safe classes):
actor UserCache {
private var cache: [String: User] = [:]
func get(_ id: String) -> User? {
cache[id]
}
func set(_ id: String, user: User) {
cache[id] = user
}
}
SwiftUI vs UIKit
When to Use SwiftUI
✅ New projects (iOS 13+) ✅ Declarative UI preferred ✅ Fast iteration needed ✅ Cross-platform (macOS, watchOS, tvOS) ✅ 40% less code vs UIKit
When to Use UIKit
✅ Legacy app maintenance ✅ Complex customizations ✅ Fine-grained control needed ✅ Specific UIKit features required ✅ Pre-iOS 13 support
SwiftUI Basics
struct ContentView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("Count: \(count)")
.font(.title)
Button("Increment") {
count += 1
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
Property Wrappers:
@State: View-local state@Binding: Two-way binding@StateObject: Observable object owner@ObservedObject: Observable object reference@EnvironmentObject: Dependency injection@Published: Observable property
Architecture Patterns
MVVM (Most Popular)
// Model
struct User: Identifiable, Codable {
let id: String
let name: String
let email: String
}
// ViewModel
@MainActor
class UserViewModel: ObservableObject {
@Published var users: [User] = []
@Published var isLoading = false
@Published var error: Error?
private let repository: UserRepository
init(repository: UserRepository = UserRepository()) {
self.repository = repository
}
func loadUsers() async {
isLoading = true
defer { isLoading = false }
do {
users = try await repository.fetchUsers()
} catch {
self.error = error
}
}
}
// View
struct UserListView: View {
@StateObject private var viewModel = UserViewModel()
var body: some View {
List(viewModel.users) { user in
Text(user.name)
}
.task {
await viewModel.loadUsers()
}
}
}
TCA (The Composable Architecture)
When to use:
- Complex state management
- Predictable state updates
- Excellent testing
- Enterprise apps
Trade-offs:
- Steeper learning curve
- More boilerplate
- Excellent for large teams
Performance Optimization
Compiler Optimizations
1. Use final classes:
final class FastClass {
// Compiler can optimize (no dynamic dispatch)
}
2. Private methods:
private func optimize() {
// Compiler can inline
}
3. Whole-module optimization:
# Build Settings
SWIFT_WHOLE_MODULE_OPTIMIZATION = YES
Memory Management
ARC (Automatic Reference Counting):
class Parent {
var child: Child?
}
class Child {
weak var parent: Parent? // Weak to avoid retain cycle
}
Common Retain Cycles:
// ❌ Bad: Retain cycle
class ViewController: UIViewController {
var completion: (() -> Void)?
func setup() {
completion = {
self.doSomething() // Strong capture
}
}
}
// ✅ Good: Weak self
class ViewController: UIViewController {
var completion: (() -> Void)?
func setup() {
completion = { [weak self] in
self?.doSomething()
}
}
}
SwiftUI Performance
1. Use const modifiers:
Text("Hello") // Recreated on every render
vs
Text("Hello")
.font(.title) // Modifier creates new view
// Better: Extract static views
let titleText = Text("Hello").font(.title)
2. Avoid expensive computations:
struct ExpensiveView: View {
let data: [Item]
// Computed every render
var sortedData: [Item] {
data.sorted() // ❌ Bad
}
// Better: Cache with @State or pass sorted
}
Testing Strategies
XCTest (Unit Testing)
import XCTest
@testable import MyApp
final class UserViewModelTests: XCTestCase {
var viewModel: UserViewModel!
var mockRepository: MockUserRepository!
override func setUp() {
super.setUp()
mockRepository = MockUserRepository()
viewModel = UserViewModel(repository: mockRepository)
}
func testLoadUsers() async throws {
// Given
let expectedUsers = [User(id: "1", name: "Test", email: "test@example.com")]
mockRepository.usersToReturn = expectedUsers
// When
await viewModel.loadUsers()
// Then
XCTAssertEqual(viewModel.users, expectedUsers)
XCTAssertFalse(viewModel.isLoading)
XCTAssertNil(viewModel.error)
}
}
XCUITest (UI Testing)
import XCTest
final class LoginUITests: XCTestCase {
let app = XCUIApplication()
override func setUp() {
super.setUp()
app.launch()
}
func testLoginFlow() {
let emailField = app.textFields["emailField"]
emailField.tap()
emailField.typeText("test@example.com")
let passwordField = app.secureTextFields["passwordField"]
passwordField.tap()
passwordField.typeText("password123")
app.buttons["loginButton"].tap()
XCTAssertTrue(app.staticTexts["Welcome"].waitForExistence(timeout: 5))
}
}
Target Coverage:
- Unit tests: 70-80%+
- Critical paths: 100%
- UI tests: Key user flows only (slow)
iOS-Specific Features
WidgetKit
import WidgetKit
import SwiftUI
struct SimpleWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "SimpleWidget", provider: Provider()) { entry in
SimpleWidgetView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is my widget")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
Live Activities (iOS 16.1+)
import ActivityKit
struct OrderAttributes: ActivityAttributes {
struct ContentState: Codable, Hashable {
var status: String
var estimatedTime: Date
}
var orderId: String
}
// Start activity
let attributes = OrderAttributes(orderId: "123")
let initialState = OrderAttributes.ContentState(
status: "Preparing",
estimatedTime: Date().addingTimeInterval(1800)
)
let activity = try Activity.request(
attributes: attributes,
contentState: initialState
)
App Clips
Characteristics:
- <10MB size limit
- Fast, lightweight experiences
- No installation required
- Invoked via NFC, QR, Safari, Maps
Human Interface Guidelines (HIG)
Navigation Patterns
Tab Bar:
- 2-5 top-level sections
- Bottom placement
- Always visible
- Immediate navigation
Navigation Bar:
- Hierarchical navigation
- Back button automatic
- Title and actions
- Large/inline title modes
Modal Presentation:
- Interrupting tasks
- Self-contained flow
- Clear dismiss action
- Use sparingly
Design Principles
Clarity:
- Legible text (minimum 11pt)
- Sufficient contrast (WCAG AA)
- Precise icons
Deference:
- Content first, UI second
- Translucent backgrounds
- Minimal UI elements
Depth:
- Layering (sheets, overlays)
- Visual hierarchy
- Motion provides meaning
Colors
System Colors:
Color.primary // Adaptive black/white
Color.secondary // Gray
Color.accentColor // App tint color
Color(uiColor: .systemBlue)
Color(uiColor: .label)
Dark Mode:
// Automatic
Color.primary // Adapts to light/dark
// Custom
Color("CustomColor") // Define in Assets.xcassets
SF Symbols
Image(systemName: "star.fill")
.foregroundColor(.yellow)
.font(.title)
// Rendering modes
Image(systemName: "heart.fill")
.symbolRenderingMode(.multicolor)
App Store Requirements (2024-2025)
SDK Requirements
- Current: Xcode 15+ with iOS 17 SDK (required as of April 2024)
- Upcoming: Xcode 16+ with iOS 18 SDK (recommended for 2025 submissions)
Privacy
- Privacy manifest: Required for third-party SDKs
- Tracking permission: ATT framework for advertising
- Privacy nutrition labels: Accurate data collection info
- Account deletion: In-app deletion required
Capabilities
- Sandbox: All apps sandboxed
- Entitlements: Request only needed capabilities
- Background modes: Justify background usage
- HealthKit: Privacy-sensitive, strict review
Submission Checklist
✅ App icons (all required sizes) ✅ Screenshots (all device sizes) ✅ App description and keywords ✅ Privacy policy URL ✅ Support URL ✅ Age rating questionnaire ✅ Export compliance ✅ Test on real devices ✅ No crashes or major bugs
Common Pitfalls
- Strong reference cycles: Use
[weak self]in closures - Main thread blocking: Use async/await, avoid sync operations
- Large images: Resize before displaying
- Unhandled errors: Always handle async throws
- Ignoring safe areas: Use
.ignoresSafeArea()intentionally - Not testing dark mode: Design for both appearances
- Hardcoded strings: Use localization from start
- Memory leaks: Profile with Instruments regularly
Resources
Official:
- Swift Documentation: https://swift.org/documentation/
- SwiftUI Tutorials: https://developer.apple.com/tutorials/swiftui
- HIG: https://developer.apple.com/design/human-interface-guidelines/
- WWDC Videos: https://developer.apple.com/videos/
Community:
- Hacking with Swift: https://www.hackingwithswift.com/
- Swift by Sundell: https://www.swiftbysundell.com/
- objc.io: https://www.objc.io/
- iOS Dev Weekly: https://iosdevweekly.com/