Reputation: 1452
I am following tutorials to understand SwiftUI, and specifically how to call an API when a view appears.
I saw this:
List(results, id: \.trackId) { item in
ListRow(item)
}
.task {
// perform API here
}
But as my app targets iOS 14, I get this error:
'task(priority:_:)' is only available in iOS 15.0 or newer
So what could I do instead? Thank you for your help
Upvotes: 5
Views: 3198
Reputation: 1193
import SwiftUI
struct TaskEntry: Identifiable {
let id = UUID()
let title: String
// Add other properties if needed
}
struct ContentView: View {
@State var results = [TaskEntry]()
@State private var dataTask: Task<Void, Error>?
var body: some View {
List(results, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.title)
}
}
.onAppear {
dataTask = Task {
do {
results = try await loadData()
} catch {
print("Error loading data: \(error.localizedDescription)")
// Handle error accordingly
}
}
}
.onDisappear {
dataTask?.cancel()
}
}
func loadData() async throws -> [TaskEntry] {
// Simulated asynchronous data fetching
// Replace this with your actual data fetching logic
// For now, returning an empty array as a placeholder
return []
}
}
Edit the answer.
Upvotes: 0
Reputation: 1009
None of the accepted answer OP's question well. To create a .task
modifier that propagates back to <iOS15, you need to create a task and keep it around until the .onDisappear
and cancel it. Here's the full code that will let you use .task
on any iOS/macOS version (the top answer here doesn't handle canceling the task).
struct TaskModifier: ViewModifier {
let priority: TaskPriority
let action: @Sendable () async -> Void
@State var task: Task<Void, Never>? = nil
func body(content: Content) -> some View {
content
.onAppear {
if task != nil {
task?.cancel()
}
task = Task(priority: priority, operation: action)
}
.onDisappear {
task?.cancel()
}
}
}
extension View {
@available(iOS, deprecated: 15.0)
func task(priority: TaskPriority = .userInitiated, _ action: @escaping @Sendable () async -> Void) -> some View {
self.modifier(TaskModifier(priority: priority, action: action))
}
}
Then to use, use like the built-in .task
:
struct Example: View {
var body: some View {
Text("Hello World")
.task {
await someAsyncFunc()
}
}
}
Upvotes: 1
Reputation: 939
Just switching to .onAppear
is not correct since it's missing the point of Structured Concurrency. Every time you create a Task
yourself you should be suspicious, you are doing something out of the ordinary.
Granted, in this case we don't have available a "structured concurrency aware" lifecycle modifier, so we need to make our own with Task
init, but that means you need to be responsible of respecting structured concurrency!
This means that getting a proper backwards compatible solution to work is a bit more code, since you want to handle cancellation properly. For that you need to use also .onDisappear
and cancel the task that you started on .onAppear
.
If you want to have it reusable you can make a custom .task modifier.
Upvotes: 6
Reputation: 1107
You can write a version of task { }
that works for iOS 13, iOS 14 and uses apple's version for iOS 15:
extension View {
@available(iOS, deprecated: 15.0, message: "This extension is no longer necessary. Use API built into SDK")
func task(priority: TaskPriority = .userInitiated, _ action: @escaping @Sendable () async -> Void) -> some View {
self.onAppear {
Task(priority: priority) {
await action()
}
}
}
}
Upvotes: 9
Reputation: 29614
async await
is available for iOS 13+.
https://developer.apple.com/documentation/swift/task
if you need to use an async
call is wrap the call in Task
.onAppear(){
Task{
//Your async code here
// await yourFuncHere()
}
}
.onAppear
is a bit un reliable so I might opt for an init
of an ObservableObject
as an alternative.
Upvotes: 5