Building an Asynchronous Button in SwiftUI
Sometimes, we need to perform asynchronous tasks when a button is tapped in a SwiftUI application. For example, fetching data from a network request, saving data to a database, or performing any long-running operation. In this tutorial, we'll explore how to create an asynchronous button in SwiftUI that performs an asynchronous task when tapped.
Why Use Asynchronous Buttons?
Asynchronous tasks are essential for operations that take time to complete, such as network requests, file I/O, or any long-running computations. By using asynchronous buttons, we can keep the UI responsive and provide feedback to users while the task is being performed.
Creating an Async Button
In this code snippet, we'll create an AsyncButton
view that performs an asynchronous task when tapped. The button will display a loading indicator while the task is in progress and disable user interaction to prevent multiple taps.
This is a simple example of how to create an asynchronous button in SwiftUI:
import SwiftUI
/// A button that performs an asynchronous task when tapped.
struct AsyncButton<Label: View>: View {
/// The asynchronous action to perform when the button is tapped.
var action: () async -> Void
/// The label of the button.
@ViewBuilder
var label: () -> Label
/// Whether the task is currently being performed.
@State
private var isPerformingTask = false
var body: some View {
Button(action: {
// When the button is tapped, we are performing a task
isPerformingTask = true
// Perform the task asynchronously
Task {
// Perform the asynchronous task
await action()
// After the task is completed, we are no longer performing a task
isPerformingTask = false
}
}) {
HStack {
// Show a loading indicator while the task is in progress
if isPerformingTask {
ProgressView()
.controlSize(.mini)
}
// Show the label of the button
label()
}
}
// Disable the button while the task is in progress
.disabled(isPerformingTask)
}
}
/// Example usage:
struct ContentView: View {
var body: some View {
AsyncButton(action: fetchData) {
Text("Fetch Data")
}
}
func fetchData() async { // <2>
// Simulate a network request
try? await Task.sleep(nanoseconds: 2 * 1_000_000_000)
print("Data fetched")
}
}
Improving the Async Button
The AsyncButton
view can be further customized to handle errors and provide feedback to the user. For example, we can display an error message if the task fails or show a success message when the task completes successfully. We can also add animations to enhance the user experience.
Here's an improved version of the AsyncButton
view that handles errors and provides feedback to the user:
import SwiftUI
/// A button that performs an asynchronous task when tapped.
struct AsyncButton<Label: View>: View {
/// The asynchronous action to perform when the button is tapped.
var action: () async throws -> Void
/// The label of the button.
@ViewBuilder
var label: () -> Label
/// Whether the task is currently being performed.
@State
private var isPerformingTask = false
/// The error message to display if the task fails.
@State
private var errorMessage: String?
/// Whether to show the alert.
@State
private var showAlert = false
var body: some View {
Button(action: {
// When the button is tapped, we are performing a task
isPerformingTask = true
// Perform the task asynchronously
Task {
do {
// Perform the asynchronous task
try await action()
// If the task completes successfully, clear the error message (if any)
errorMessage = nil
} catch {
// If the task fails, display the error message
errorMessage = error.localizedDescription
}
// After the task is completed, we are no longer performing a task
isPerformingTask = false
// Show the alert if there is an error
showAlert = errorMessage != nil
}
}) {
HStack {
// Show a loading indicator while the task is in progress
if isPerformingTask {
// Use a mini progress view
ProgressView()
.controlSize(.mini)
}
// Show the button label
label()
}
}
// Disable the button while the task is in progress
.disabled(isPerformingTask)
// Show an alert with the error message if the task fails
.alert(isPresented: $showAlert) {
// Display an alert with the error message
Alert(
title: Text("Error"),
message: Text(errorMessage ?? ""),
dismissButton: .default(Text("OK"))
)
}
}
}
Conclusion
Creating an asynchronous button in SwiftUI is straightforward and enhances the user experience by keeping the UI responsive. By leveraging Swift's concurrency model, we can perform tasks asynchronously and handle errors gracefully. This approach ensures that our applications remain smooth and user-friendly, even when performing complex operations.
Resources
- https://developer.apple.com/documentation/swiftui/view/progressview
- https://developer.apple.com/documentation/swift/async
- https://developer.apple.com/documentation/swift/async/await
- https://developer.apple.com/documentation/swift/async/task
- https://developer.apple.com/documentation/swift/async/throws
Read more
- SwiftUI Development with DynamicUI • 4 minutes reading time.
- SwiftLeeds 2024, Day 2 • 7 minutes reading time.
- Safely unwrap optional values in SwiftUI bindings • 4 minutes reading time.
Share
Share Mastodon Twitter LinkedIn Facebook