Hallo bomen en bloemen, Leo hier. Today we will talk about a little something widespread in UIKit but we don’t have a native way to do it in SwiftUI that is running an action just once per view load.

In UIKit lifecycle we had the viewDidLoad callback that we could use and that would guarantee that as long as the view was alive in the stack we would only run the code inside that closure once.

Before diving into that let’s discuss the joy of researching. This week I had a fantastic training about Experimentation and how things can get very complicated quickly when you talk about AB Tests.

I learned a lot about scientific biases such as selection bias, p-hacking, and confirmation bias. Let’s talk a little bit about p-hacking.

It involves checking your results multiple times as data comes in and then deciding to stop collecting data or make decisions based on intermediate results rather than pre-planned analyses. This approach can lead to false positives and incorrect conclusions. Imagine you want to prove that 50% of the city’s population loves bananas. So you go out in the streets and ask 4 people if they like bananas and then 3 out 4 said they don’t like bananas at all.

But then you have a great idea, how about continuing to ask people? Then you continue and ask 6 more people, so your population now is 10, and 4 of them love bananas. You now have 5 persons that love bananas and 5 persons that hate bananas. 

You give a little tap on your back and say: My work here is done, my sample clearly shows that 50% of the people in this city love bananas. I HAVE DATA TO PROVE!

Yes, my friend, you have data however you are a victim of a very common bias called p-hacking where we stop collecting data when we the results are in favor of our hypothesis. Bad scientist!

Another common mistake of p-hacking is pretty much the opposite. Imagine that you state that for some population of X, you should have at least 3 persons that love pineapples. Then you create or experiment, you collect your X amount of data, and… BAM only two people love pineapples. What would you do? The right thing to do is rethink your hypothesis and create a new test.

What will happen if you continue to collect data? Eventually, you will reach your threshold of pineapple-loving people, then you will think: I knew it, my sample was biased If I could just have Y more people I could get my results. But that my friends, is a form of p-hacking too. Keep collecting evidence until you find your proof doesn’t prove your hypothesis, and barely proves anything, to be honest.

Well, that was one of the things that I learned this week about statistics. It was a really interesting course and I think I’ll bring more about statistics content for the blog in the future.

Last week we explored the new Preview Macro and how you can easily use it for SwiftUI and UIKit. Check by yourself how was the evolution of Canvas in SwiftUI in that article!

Something that never goes out of fashion is functional reactive programming and I always have a feeling that I should learn more about its intricacies. We explored a nice concept in the previous weeks which is the scan operator in Combine Framework. With it, you can read values and produce intermediate results for any Combine Framework stream. Cool, isn’t it?

No more talking, let’s code! But first…

 

Painting of The Day

Today I chose a painting from 1898 called “The Eiffel Tower” by Henry Russeau.

Henri Rousseau, often referred to as Le Douanier (the customs officer) due to his occupation as a toll collector, was a self-taught French painter who became a post-impressionist artist of note. Born in 1844, he is best remembered for his bold, dreamlike jungle scenes, even though he never left France or saw a jungle. His work, initially ridiculed by the art establishment, has since been celebrated for its childlike innocence and its unique representation of the world.

I chose this picture because of the first impression I had of the Eiffel tower, it is majestic and incredible. Such an amazing art piece. And as we are talking about first times, I think that painting was really appropriate.  

 

The Problem – Triggering an Action Only First Time a View Appears in SwiftUI

You want to run some logic or network call only once throughout all the view appearances.

This is something that we who worked with UIKit were used to do. Because of the different lifecycles SwiftUI doesn’t provide a native way to run a piece of code only once for all view appearances so we have to build our own.

Using UIKit we have an easy moment that we can use to run that type of code. The view lifecycle in UIKit had a step where it would load a view for the first time, and that step happened just one time until the view was completely deallocated. And you had two callbacks that you could use to do that: loadView and viewDidLoad.

Now let’s recap all the current SwiftUI view lifecycles. And they are: onAppear, onDisappear and task.

 

Every Time The View Appears – onAppear

onAppear marks the point in a view’s lifecycle when it has been added to the view hierarchy and is now visible to the user. This lifecycle method provides an opportunity for developers to perform tasks such as initiating network requests, starting animations, or updating the state of the view. It’s important to note that onAppear can be called multiple times during a view’s lifecycle, such as when it becomes visible after being temporarily obscured by another view.

 

Every Time The View Disappears – onDisappear

onDisappear is called when a view is removed from the view hierarchy, making it no longer visible on the screen. Developers can use this lifecycle method to perform cleanup tasks, stop animations, or release resources that were allocated when the view appeared. Like onAppear, this method can be triggered multiple times during the lifespan of a view, especially in complex view hierarchies or when views are reused.

 

Every Time The View Appears and You Have an Async Task – task

We have two main tasks APIs, with and without ID parameters. And the ID is used to have more control over how many times you will trigger the task itself.

The task(id:priority:_:) modifier in SwiftUI provides a way for developers to initiate asynchronous operations in conjunction with a view’s lifecycle. By attaching this modifier to a view, tasks can be started when the view appears and automatically canceled when the view disappears, simplifying resource management and ensuring a tight coupling of a view’s lifecycle to its associated operations.

If you use the API with id parameter you have even more control over the task that will run. The id parameter allows for the identification of the task, ensuring that a new task won’t be started unless the identifier changes. Meanwhile, the priority parameter can be used to influence the order of task execution when multiple tasks compete for limited system resources.

This integration of asynchronous tasks directly within the SwiftUI view lifecycle streamlines the process of fetching data, running computations, and other background tasks that need to be closely tied to the UI’s presentation state. If you have a view that needs that, you have the right tool to do it now!

Chose wisely what suits you best!

Now that we have taken a look at all SwiftUI native view lifecycle let’s create ours that runs only on the first time the view appear and never more.

 

Code Example – SwiftUI .onAppear Only Running Once

We will create a view modifier to do that, copy and paste the code below:

public struct OnFirstAppearModifier: ViewModifier {

    private let onFirstAppearAction: () -> ()
    @State private var hasAppeared = false
    
    public init(_ onFirstAppearAction: @escaping () -> ()) {
        self.onFirstAppearAction = onFirstAppearAction
    }
    
    public func body(content: Content) -> some View {
        content
            .onAppear {
                guard !hasAppeared else { return }
                hasAppeared = true
                onFirstAppearAction()
            }
    }
}

extension View {
    func onFirstAppear(_ onFirstAppearAction: @escaping () -> () ) -> some View {
        return modifier(OnFirstAppearModifier(onFirstAppearAction))
    }
}

What we are doing here? We are taking advantage of the @State property wrapper that keeps it state between view creations and destructions to run our action or not. It is pretty simple, right?

To use it you just need to do like the example below:

struct ContentView: View {
    var body: some View {
        NavigationStack {
            VStack {
                Text("First View!!!")
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                    .padding()
                
                NavigationLink("Next View") {
                    View2()
                }.buttonStyle(.borderedProminent)
            }
            .padding()
            .onFirstAppear {
                print("⭐️ On First Appear ! \(Date())")
            }
            .onAppear {
                print("Run on Appear!!! \(Date())")
            }
            .onDisappear {
                print("Run on Disappear!! \(Date())")
            }
            .task({
                print("📀 Task print!")
            })
        }
    }
}

struct View2: View {
    var body: some View {
        NavigationStack {
            VStack {
                Text("This is View 2")
                    .padding()
                Image(systemName: "star")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
            }
            .padding()
        }
    }
}

 

And you should see this:

how to run only once the onAppear for a view in SwiftUI?

If you tap in Next View and go back several times you should see this in the logs: 

⭐️ On First Appear ! 2023-09-11 05:32:44 +0000

Run on Appear!!! 2023-09-11 05:32:44 +0000

📀 Task print!

Run on Disappear!! 2023-09-11 05:33:07 +0000

Run on Appear!!! 2023-09-11 05:33:08 +0000

📀 Task print!

Run on Disappear!! 2023-09-11 05:33:10 +0000

Run on Appear!!! 2023-09-11 05:33:11 +0000

📀 Task print!

 

And that’s it! Now you can use your onFirstAppear to run something just once for each view when it appears.

 

Summary – Running Just Once View onAppear Code

The evolution of SwiftUI has brought numerous advantages, but also some challenges, especially for those used to UIKit’s lifecycle. While SwiftUI offers a variety of lifecycle methods, the lack of a built-in function to run a piece of code only once when a view appears may initially seem like an oversight.

However, with the onFirstAppear, we can create a seamless workaround. This ensures that developers have the power and flexibility to optimize their app’s behavior, by triggering certain actions or network calls only upon the view’s first appearance, enhancing efficiency and user experience.

As we continue to adapt and find new solutions within SwiftUI, it’s crucial to remember that the framework is still evolving. By sharing insights and techniques, like the one demonstrated here, the developer community can ensure that SwiftUI becomes an even more potent tool in the future. As always, the key is to understand the available tools and lifecycle methods and select the best-suited one for your specific needs, leading to more robust and dynamic applications.

Fellow Apple Developers, that’s all.

I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or say hello on Twitter. I’m available on LinkedIn or send me an e-mail through the contact page.

You can likewise sponsor this blog so I can get my blog free of ad networks.

Thanks for the reading and… That’s all folks.

Image credit: Featured Painting