·3 min read

Exploring SwiftUI: Executing a Task Only Once

I was working on a code where I wanted to run the task only once, as it was unnecessary to do a call on navigating back to the view again. I have already seen the onFirstAppear modifier by Matt Young, and I wanted to extend it for async/await syntax—syntactic sugar to avoid wrapping in a Task {}.

I have to ensure that a task is executed only once by using the task modifier in combination with a ViewModifier.

FirstTask ViewModifier

First, I define a FirstTask structure that conforms to the ViewModifier protocol. This struct will contain an action property that will hold the asynchronous closure and a @State property called hasAppeared that will be used to track whether the closure has already been executed.

struct FirstTask: ViewModifier {
  let action: @Sendable () async -> ()
 
  // Use this to ensure the block is only executed once
  @State private var hasAppeared = false
 
  func body(content: Content) -> some View {
    content.task {
 
      // Prevent the action from being executed more than once
      guard !hasAppeared else { return }
      hasAppeared = true
      await action()
    }
  }
}

In the body property of the FirstTask struct, I use the task modifier to execute the asynchronous closure. Before executing the closure, I check the hasAppeared property to ensure that the closure has not already been executed. If it has, it simply returns from the closure without executing it. If it has not, I set the hasAppeared property to true and then execute the closure.

onFirstTask Modifier

Then, I define an extension on the View protocol that takes an asynchronous closure as a parameter. This closure will contain the code that I want to execute only once.

extension View {
  func onFirstTask(_ action: @escaping @Sendable () async -> ()) -> some View {
    modifier(FirstTask(action: action))
  }
}

Example Usage

To use this code, I call the onFirstTask method on any view and pass in the asynchronous method.

struct AppleMusicSongListView: View {
  @StateObject private var viewModel = AppleMusicSongViewModel()
 
  var body: some View {
    VStack {
      VStack(alignment: .leading, spacing: 12) {
        ///
    }
    .onFirstTask {
      await viewModel.fetchLibrarySongs()
    }
  }
}

Complex Views

Suppose there are cases with complex view hierarchies where views might be reconstructed. In that case, I can leverage @StateObject to hold the isFirstAppear state and ensure that this state is retained across the view's appearances.

extension View {
  func onFirstTask(_ action: @escaping @Sendable () async -> ()) -> some View {
    modifier(FirstTask(action: action))
  }
}
 
private struct FirstTask: ViewModifier {
  let action: @Sendable () async -> ()
 
  // Use this to ensure the block is only executed once
  @StateObject private var hasAppeared = FirstAppearState()
 
  func body(content: Content) -> some View {
    content.task {
      // Prevent the action from being executed more than once
      guard !hasAppeared.value else { return }
      hasAppeared.value = true
      await action()
    }
  }
}
 
// Create a separate class to hold the isFirstAppear state
private class FirstAppearState: ObservableObject {
    @Published var value: Bool = true
}

Conclusion

This is a simple way to ensure an asynchronous code block is executed only once in SwiftUI. Try it in your code, and let me know how it works for you!

Post Topics

Explore more in these categories:

Related Articles

Exploring Stream's Video SDK: Creating a WWDC Watch Party App

Build a WWDC 2024 Watch Party App using Stream's Video SDK. Implement video playback, calling features, and synchronize playback for seamless group viewing. Dive into Stream's powerful tools to create interactive experiences for Apple developers and elevate your WWDC experience.

Exploring AI: Cosine Similarity for RAG using Accelerate and Swift

Learn how to implement cosine similarity using Accelerate framework for iOS and macOS apps. Build Retrieval-Augmented Generation (RAG) systems breaking down complex mathematics into simple explanations and practical Swift code examples. Optimize document search with vector similarity calculations.