Another Dude
Another Dude

Reputation: 1452

Swift: task {} before iOS 15?

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

Answers (5)

Zeeshan Ahmad II
Zeeshan Ahmad II

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

thecoolwinter
thecoolwinter

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

alejandromp
alejandromp

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

luizParreira
luizParreira

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

lorem ipsum
lorem ipsum

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

Related Questions