SwiftUI has matured into the definitive framework for Apple platform development. With iOS 18 and beyond, SwiftUI offers comprehensive APIs for building sophisticated apps across iPhone, iPad, Mac, Watch, and Vision Pro. This guide covers modern SwiftUI patterns for production development.
Modern Architecture with Observation
// Modern SwiftUI with @Observable (iOS 17+)
import SwiftUI
import Observation
@Observable
class UserViewModel {
var user: User?
var isLoading = false
var error: Error?
private let userService: UserService
init(userService: UserService = .shared) {
self.userService = userService
}
func loadUser(id: String) async {
isLoading = true
defer { isLoading = false }
do {
user = try await userService.fetchUser(id: id)
} catch {
self.error = error
}
}
func updateProfile(name: String, email: String) async throws {
guard var user = user else { return }
user.name = name
user.email = email
self.user = try await userService.updateUser(user)
}
}
struct ProfileView: View {
@State private var viewModel = UserViewModel()
let userId: String
var body: some View {
Group {
if viewModel.isLoading {
ProgressView()
} else if let user = viewModel.user {
ProfileContent(user: user, viewModel: viewModel)
} else if let error = viewModel.error {
ErrorView(error: error) {
Task { await viewModel.loadUser(id: userId) }
}
}
}
.task {
await viewModel.loadUser(id: userId)
}
}
}
struct ProfileContent: View {
let user: User
@Bindable var viewModel: UserViewModel
@State private var isEditing = false
var body: some View {
List {
Section {
ProfileHeader(user: user)
}
Section("Details") {
LabeledContent("Email", value: user.email)
LabeledContent("Member since", value: user.createdAt.formatted())
}
Section {
Button("Edit Profile") {
isEditing = true
}
}
}
.sheet(isPresented: $isEditing) {
EditProfileView(user: user, viewModel: viewModel)
}
}
}Navigation with NavigationStack
// Type-safe navigation with NavigationStack
import SwiftUI
// Define navigation destinations
enum AppRoute: Hashable {
case home
case profile(userId: String)
case settings
case productDetail(productId: String)
case orderHistory
case order(orderId: String)
}
// Navigation coordinator
@Observable
class NavigationCoordinator {
var path = NavigationPath()
func navigate(to route: AppRoute) {
path.append(route)
}
func pop() {
if !path.isEmpty {
path.removeLast()
}
}
func popToRoot() {
path = NavigationPath()
}
}
// Main app structure
struct ContentView: View {
@State private var coordinator = NavigationCoordinator()
var body: some View {
NavigationStack(path: $coordinator.path) {
HomeView()
.navigationDestination(for: AppRoute.self) { route in
switch route {
case .home:
HomeView()
case .profile(let userId):
ProfileView(userId: userId)
case .settings:
SettingsView()
case .productDetail(let productId):
ProductDetailView(productId: productId)
case .orderHistory:
OrderHistoryView()
case .order(let orderId):
OrderDetailView(orderId: orderId)
}
}
}
.environment(coordinator)
}
}
// Using navigation in child views
struct HomeView: View {
@Environment(NavigationCoordinator.self) private var coordinator
var body: some View {
List {
Button("View Profile") {
coordinator.navigate(to: .profile(userId: "123"))
}
Button("Settings") {
coordinator.navigate(to: .settings)
}
NavigationLink(value: AppRoute.orderHistory) {
Label("Order History", systemImage: "list.bullet")
}
}
.navigationTitle("Home")
}
}Modern Data Flow
// SwiftData integration (iOS 17+)
import SwiftUI
import SwiftData
@Model
class Task {
var title: String
var isCompleted: Bool
var dueDate: Date?
var priority: Priority
@Relationship(deleteRule: .cascade)
var subtasks: [Subtask] = []
enum Priority: String, Codable, CaseIterable {
case low, medium, high
}
init(title: String, priority: Priority = .medium) {
self.title = title
self.isCompleted = false
self.priority = priority
}
}
@Model
class Subtask {
var title: String
var isCompleted: Bool
var task: Task?
init(title: String) {
self.title = title
self.isCompleted = false
}
}
struct TaskListView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \Task.dueDate) private var tasks: [Task]
@State private var showingAddTask = false
var body: some View {
List {
ForEach(tasks) { task in
TaskRow(task: task)
}
.onDelete(perform: deleteTasks)
}
.navigationTitle("Tasks")
.toolbar {
Button(action: { showingAddTask = true }) {
Label("Add Task", systemImage: "plus")
}
}
.sheet(isPresented: $showingAddTask) {
AddTaskView()
}
}
private func deleteTasks(at offsets: IndexSet) {
for index in offsets {
modelContext.delete(tasks[index])
}
}
}
struct TaskRow: View {
@Bindable var task: Task
var body: some View {
HStack {
Button {
task.isCompleted.toggle()
} label: {
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundStyle(task.isCompleted ? .green : .gray)
}
.buttonStyle(.plain)
VStack(alignment: .leading) {
Text(task.title)
.strikethrough(task.isCompleted)
if let dueDate = task.dueDate {
Text(dueDate, style: .date)
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer()
PriorityBadge(priority: task.priority)
}
}
}Custom Components
// Reusable component patterns
import SwiftUI
// Custom button style
struct PrimaryButtonStyle: ButtonStyle {
@Environment(\.isEnabled) private var isEnabled
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.headline)
.foregroundStyle(.white)
.padding(.horizontal, 24)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(isEnabled ? Color.accentColor : Color.gray)
)
.scaleEffect(configuration.isPressed ? 0.95 : 1)
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
}
}
// Async image with placeholder
struct AsyncImageView: View {
let url: URL?
var placeholder: Image = Image(systemName: "photo")
var body: some View {
AsyncImage(url: url) { phase in
switch phase {
case .empty:
ProgressView()
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
case .failure:
placeholder
.foregroundStyle(.secondary)
@unknown default:
placeholder
}
}
}
}
// Card component
struct Card<Content: View>: View {
let content: Content
var padding: CGFloat = 16
var cornerRadius: CGFloat = 12
init(
padding: CGFloat = 16,
cornerRadius: CGFloat = 12,
@ViewBuilder content: () -> Content
) {
self.padding = padding
self.cornerRadius = cornerRadius
self.content = content()
}
var body: some View {
content
.padding(padding)
.background(
RoundedRectangle(cornerRadius: cornerRadius)
.fill(.background)
.shadow(color: .black.opacity(0.1), radius: 8, y: 4)
)
}
}
// Usage
struct ExampleView: View {
var body: some View {
VStack(spacing: 20) {
Card {
VStack(alignment: .leading, spacing: 8) {
Text("Welcome")
.font(.headline)
Text("This is a card component")
.foregroundStyle(.secondary)
}
}
Button("Primary Action") {
// Action
}
.buttonStyle(PrimaryButtonStyle())
}
.padding()
}
}Best Practices
SwiftUI Best Practices 2026
Use @Observable instead of ObservableObject for better performance
Prefer NavigationStack over NavigationView
Use SwiftData for persistence when possible
Extract reusable components with proper view composition
Use .task modifier for async work tied to view lifecycle
Implement proper error handling with Result types
Use environment values for dependency injection
Conclusion
SwiftUI in 2026 provides everything needed for production iOS development. The combination of @Observable, NavigationStack, and SwiftData creates a powerful, type-safe development experience. Start with these patterns and adapt them to your specific needs.
Need help with iOS development? Contact Jishu Labs for expert mobile development consulting.
About Riken Patel
Riken Patel is the Mobile Lead at Jishu Labs with extensive experience in iOS and cross-platform mobile development.