SwiftUI task modifiers and async lifecycle
Sofia Martinez
Jan 2026
1 tab
import SwiftUI
struct PostsView: View {
@StateObject private var viewModel = PostsViewModel()
var body: some View {
List(viewModel.posts) { post in
PostRowView(post: post)
}
.task {
await viewModel.loadPosts()
}
.refreshable {
await viewModel.refresh()
}
}
}
// Search with task(id:)
struct SearchView: View {
@State private var searchQuery = ""
@State private var results: [SearchResult] = []
@State private var isLoading = false
var body: some View {
VStack {
TextField("Search", text: $searchQuery)
.textFieldStyle(.roundedBorder)
.padding()
if isLoading {
ProgressView()
} else {
List(results) { result in
Text(result.title)
}
}
}
.task(id: searchQuery) {
guard !searchQuery.isEmpty else {
results = []
return
}
isLoading = true
// Debounce
try? await Task.sleep(nanoseconds: 300_000_000)
// Check if task was cancelled
guard !Task.isCancelled else { return }
do {
results = try await searchAPI(query: searchQuery)
} catch {
print("Search failed: \(error)")
}
isLoading = false
}
}
private func searchAPI(query: String) async throws -> [SearchResult] {
// Simulate API call
try await Task.sleep(nanoseconds: 1_000_000_000)
return []
}
}
// Parallel async operations
struct DashboardView: View {
@State private var posts: [Post] = []
@State private var comments: [Comment] = []
@State private var users: [User] = []
@State private var isLoading = false
var body: some View {
VStack {
if isLoading {
ProgressView("Loading...")
} else {
Text("\(posts.count) posts")
Text("\(comments.count) comments")
Text("\(users.count) users")
}
}
.task {
isLoading = true
// Load all data in parallel
async let postsData = fetchPosts()
async let commentsData = fetchComments()
async let usersData = fetchUsers()
do {
posts = try await postsData
comments = try await commentsData
users = try await usersData
} catch {
print("Error loading data: \(error)")
}
isLoading = false
}
}
private func fetchPosts() async throws -> [Post] {
try await Task.sleep(nanoseconds: 1_000_000_000)
return []
}
private func fetchComments() async throws -> [Comment] {
try await Task.sleep(nanoseconds: 1_000_000_000)
return []
}
private func fetchUsers() async throws -> [User] {
try await Task.sleep(nanoseconds: 1_000_000_000)
return []
}
}
// Manual task management
struct VideoPlayerView: View {
@State private var downloadTask: Task<Void, Never>?
@State private var downloadProgress: Double = 0
var body: some View {
VStack {
if let task = downloadTask, !task.isCancelled {
ProgressView(value: downloadProgress)
Button("Cancel Download") {
task.cancel()
downloadTask = nil
}
} else {
Button("Download Video") {
startDownload()
}
}
}
}
private func startDownload() {
downloadTask = Task {
for i in 1...100 {
guard !Task.isCancelled else { return }
try? await Task.sleep(nanoseconds: 50_000_000)
downloadProgress = Double(i) / 100.0
}
}
}
}
1 file · swift
Explain with highlit
SwiftUI's .task modifier handles async operations tied to view lifecycle. The task starts when the view appears and cancels automatically when it disappears, preventing memory leaks. I use .task for data fetching, subscriptions, and long-running operations. For manual control, I track task handles with @State var task: Task<Void, Never>? and cancel explicitly. The .task(id:) variant re-runs when dependencies change, perfect for search queries. Structured concurrency with async let enables parallel operations. Task priorities guide scheduling—.userInitiated for UI-critical work, .background for non-urgent tasks. .refreshable creates pull-to-refresh with async support.