top of page
  • Writer's pictureLuda Fux

The ViewDidLoad Doppelgänger in SwiftUI

Updated: Jul 8


I recently faced screen flickering in my SwiftUI app whenever I switched tabs and came back. Caching remote images wasn't implemented yet, and although it's basic functionality, its absence revealed a bug where data was fetched on every viewWillAppear. This is, of course, not desirable. The data for the screen should be loaded once and remain even when navigating away and back to the screen.


Here's how it looked:




The data was fetched in the .task modifier:

 .task { viewModel.fetch() }

It turned out .task is triggered every time a tab is changed or reselected, behaving exactly like viewDidAppear in UIKit.


Missing viewDidLoad

The problem is that SwiftUI lacks a viewDidLoad alternative that is called only once. Methods like .onAppear or .task trigger their closures every time a view appears, leading to multiple executions of my data fetching logic. This results in unnecessary network calls and a poor user experience with screen flickering.


Iterating for Solution #1: ViewDidLoadModifier

To solve this, we could use ViewDidLoadModifier to ensure that a specified action is only executed once when the view first appears, mimicking the viewDidLoad behavior from UIKit.


import SwiftUI
public struct ViewDidLoadModifier: ViewModifier {
  @State private var viewDidLoad = false
  let action: (() -> Void)?

  public func body(content: Content) -> some View {
    content
      .onAppear {
        if viewDidLoad == false {
           viewDidLoad = true
          action?()
        }
      }
  }
}

public extension View 
  func onViewDidLoad(perform action: (() -> Void)? = nil) -> some View {
     self.modifier(ViewDidLoadModifier(action: action))
  }
}

  • State Management: The ViewDidLoadModifier uses a @State property to track if the view has appeared before. This state is local to each instance of the view, ensuring that the action is only executed once per view lifecycle.

  • Conditional Execution: Inside the onAppear modifier, it checks if viewDidLoad is false. If so, it sets viewDidLoad to true and executes the provided action. Subsequent appearances of the view do not trigger the action again.


Applying this modifier to a view like this:

struct ContentView: View {
  var body: some View {
    Text("Hello, World!")
      .onViewDidLoad {
        print("View did load")
        viewModel.fetch()
      }
  }
}
 

Semantic Issue

There is a semantic issue with this solution:

In UIKit, viewDidLoad is called before viewDidAppear. However, in this implementation, we create the viewDidLoad behavior after viewDidAppear, as viewDidAppear is actually triggering our function. Therefore, the name onViewDidLoad is somewhat inappropriate. Some other alternatives could be performOnceOnViewDidAppear, executeOnceOnAppear, or runOnceOnViewAppear. Let's go with the second one:

Iterating for solution #2: ExecuteOnceOnAppearModifier

import SwiftUI

public struct ExecuteOnceOnAppearModifier: ViewModifier {
  @State private var hasAppeared = false
  let action: (() -> Void)?

  public func body(content: Content) -> some View {
    content
      .onAppear {
        if hasAppeared == false {
          hasAppeared = true
          action?()
        }
      }
  }
}

public extension View {
  func executeOnceOnAppear(perform action: (() -> Void)? = nil) -> some View {
    self.modifier(ExecuteOnceOnAppearModifier(action: action))
  }
}

Use:

struct ContentView: View {
  var body: some View {
    Text("Hello, World!")
      .executeOnceOnAppear {
        print("View did load")
        viewModel.fetch()
      }
  }
}

Final Result:




101 views

Recent Posts

See All

Navigating the Sea of iOS Knowledge 🌊

For the past few months, I've been diligently keeping a list of all the things I want to learn and implement in iOS development. Every day, I learn something new and put it into practice. Yet, the mor

The Benefits of Modularization

When embarking on a greenfield project, the initial development phase often feels like a breeze. Features are fresh, and there’s no need to grapple with someone else's code. Everything flows smoothly,

Consolidated ViewState

Managing multiple states in SwiftUI views can be complex and error-prone. This post addresses the issue by introducing a consolidated generic ViewState enum, simplifying state management, reducing bug

Comments


bottom of page