This topic is extended from https://www.hackingwithswift.com/forums/swiftui/best-convention-for-fixing-the-pyramid-of-doom/15417, - @Obelix already provided an invaluable response, but I'm creating a new topic with an example that aligns more to the original intent of the question.
I have two questions regarding async functions.
1. Alternatives to if-let pyramid of doom to better validate variables called from async functions.
In ViewA.swift
, I have a nested if-let loop that validates if numbers
and average
are properly instantiated. I am familiar that you could use guard-let to avoid an if-let pyramid of doom, but I am struggling with implementing it in my view.
2. How to switch tabs in TabView while asychronous function is running
In MainView.swift
, I have a tabview with ViewA and ViewB as tab items. View A takes ~5 seconds to go from LoadingView to a TextView, so in the mainview the loading view is displayed for a while as well. However, while View A loads its textview, I am unable to switch tabs as if the screen's frozen. Why would this happen and what can I change to avoid it??
Below is a sample code nearly identical to Paul's example in a post about Async functions.
Note: It might not be obvious from this example, but I'm using EnvironmentObject since I want to use the class across multiple views.
NumberManager.swift
class NumberManager: ObservableObject {
@Published var numbers: [Double]?
@Published var average: Double?
func generateNumbers() async {
self.numbers = (1...10_000_000).map { _ in Double.random(in: -10...30) }
// takes about 5 seconds to run...
}
func calculateAverageNumber(for numbers: [Double]) async{
let total = numbers.reduce(0, +)
let average = total / Double(numbers.count)
self.average = average
}
}
MainView.swift
struct MainView: View {
@StateObject var numberManager = NumberManager()
var body: some View {
TabView {
ViewA()
.tabItem {
Label("ViewA", systemImage: "person.3")
}
ViewB()
.tabItem {
Label("ViewA", systemImage: "person.3")
}
}
.environmentObject(numberManager)
}
}
ViewA.swift
struct ViewA: View {
@EnvironmentObject var numberManager: NumberManager
var body: some View {
if let numbers = numberManager.numbers {
if let average = numberManager.average {
Text("Average is: \(average)")
} else {
LoadingView(loadingType: "Calculating Average")
.task {
await numberManager.calculateAverageNumber(for: numbers)
}
}
} else {
LoadingView(loadingType: "Generating Numbers")
.task {
await numberManager.generateNumbers()
}
}
}
}
LoadingView.swift
struct LoadingView: View {
var loadingType: String
var body: some View {
Text(loadingType)
}
}