SwiftUI打开视图中的选项(末日金字塔)



我有一个示例程序,它做三件事

  1. 从-10…10生成一个随机整数(正则函数(
  2. 从-10…10生成1000万个随机数(异步函数(
  3. 计算#2(抛出异步函数(的平均值

以下是完整的工作代码。它工作时没有错误,但视图的可读性很差,有三个嵌套的if/lit循环。在这种情况下,摆脱厄运金字塔的最佳方式/惯例是什么?

结果截图(应该如何工作(

工作代码(方法(

class NumberManager: ObservableObject {
@Published var integer: Int?
@Published var numbers: [Double]?
@Published var average: Double?

func generateInt() {
self.integer = Int.random(in: -10...10)
}

func generateNumbers() async  {
self.numbers = (1...10_000_000).map { _ in Double.random(in: -10...10) }
// takes about 5 seconds to run...
}
func calculateAverageNumber(for numbers: [Double]) async throws {
guard !numbers.isEmpty else {
print("numbers not generated")
return
}
let total = numbers.reduce(0, +)
let average = total / Double(numbers.count)
self.average = average
}
}

工作代码(查看(

struct ContentView: View {

@StateObject var numberManager = NumberManager()

var body: some View {

if let integer = numberManager.integer {
if let numbers = numberManager.numbers {
if let average = numberManager.average {
Text("Integer is (integer)")
Text("First number is: (numbers[0])")
Text("Average is: (average)")
} else {
LoadingView(loadingType: "Calculating average")
.task {
do {
try await numberManager.calculateAverageNumber(for: numbers)
} catch {
print("empty numbers array")
}
}
}
} else {
LoadingView(loadingType: "Generating numbers")
.task {
await numberManager.generateNumbers()
}
}
} else {
LoadingView(loadingType: "Generating int")
.task {
numberManager.generateInt()
}
}
}
}

到目前为止我尝试了什么我尝试构建助手函数来构建视图,如下所示,并调用了那些在ContentView中返回视图的函数。当我运行它时,会生成并显示整数和数字数组,但计算平均值的最后一个任务根本不会被再次调用

结果截图(有问题(

代码(运行时没有错误。但计算平均值的最后一个任务没有执行(

struct ContentView: View {

@StateObject var numberManager = NumberManager()

var body: some View {

intergerView()
.task {
print("Generating Int")
numberManager.generateInt()
}
numbersView()
.task {
print("Generating Numbers")
await numberManager.generateNumbers()
}
averageView()
.task {
do {
print("Calculating Average")
try await numberManager.calculateAverageNumber(for: numberManager.numbers ?? [])
} catch {
print("error")
}
}
}
}
private func intergerView() -> some View {
guard let integer = numberManager.integer else {
return AnyView(LoadingView(loadingType: "Generating int"))
}
return AnyView(Text("Integer is (integer)"))
}
private func numbersView() -> some View {
guard let numbers = numberManager.numbers else {
return AnyView(LoadingView(loadingType: "Generating numbers"))
}
return AnyView(Text("First number is: (numbers[0])"))
}
private func averageView() -> some View {
guard let average = numberManager.average else {
return AnyView(LoadingView(loadingType: "Calculating average"))
}
return AnyView(Text("Average is: (average)"))
}

编辑:在我的应用程序中,我有一个在一个视图中执行所有不同功能的视图(就像一个仪表板(。有些要求其他先运行(比如计算平均值(,而有些则可以自己运行(比如生成一个随机整数(。我想显示首先加载的内容,同时为尚未加载的零件显示loadingview占位符

这里的几个问题:

  • generateNumberscalculateAverageNumber相互依赖。所以他们需要互相等待。

  • 您的";工作代码";与代码的描述不匹配。你说你想显示先完成的内容,但你的if/else语句引入了所有3个函数/视图之间的依赖关系

  • 你不需要三种不同的观点。一个可以定制的就足够了。


class NumberManager: ObservableObject {
@Published var integer: Int?
@Published var numbers: [Double]?
@Published var average: Double?


func generateInt() {
self.integer = Int.random(in: -10...10)
}

func generateNumbers() async  {
self.numbers = (1...10_000_000).map { _ in Double.random(in: -10...10) }
// takes about 5 seconds to run...
}

// No need for arguments here
func calculateAverageNumber() async throws {
guard let numbers = numbers, !numbers.isEmpty else {
print("numbers not generated")
return
}
let total = numbers.reduce(0, +)
let average = total / Double(numbers.count)
self.average = average
}

//This function will handle the dependenies of generating the values and calculating the avarage
func calculateNumbersAndAvarage() async throws{
await generateNumbers()
try await calculateAverageNumber()
}
}

视图:

struct ContentView: View{

@StateObject private var numberManager = NumberManager()

var body: some View{
//Show the different detail views.
VStack{
Spacer()
Spacer()

DetailView(text: numberManager.integer != nil ? "Integer is (numberManager.integer!)" : nil)
.onAppear {
numberManager.generateInt()
}
Spacer()
Group{
DetailView(text: isNumbersValid ? "First number is: (numberManager.numbers![0])" : nil)

Spacer()

DetailView(text: numberManager.average != nil ? "Average is: (numberManager.average!)" : nil)
}.onAppear {
Task{
do{
try await numberManager.calculateNumbersAndAvarage()
}
catch{
print("error")
}
}
}

Spacer()
Spacer()
}
}

//Just to make it more readable
var isNumbersValid: Bool{
numberManager.numbers != nil && numberManager.numbers?.count != 0
}
}

DetailView:

struct DetailView: View{

let text: String?

var body: some View{
// If no text to show, show `ProgressView`, or `LoadingView` in your case. You can inject the view directly or use a property for the String argument.
if let text = text {
Text(text)
.font(.headline)
.padding()
} else{
ProgressView()
}
}
}

代码应该不言自明。如果你对这个代码有任何进一步的问题,请随时这样做,但请先阅读并尝试了解它是如何工作的。

编辑:

在显示numbers[0]之前,这不会等待calculateAverageNumber完成。它同时显示的原因是几乎不需要时间来计算贪婪。尝试在calculateNumbersAndAvarage中的两个函数之间添加此项。

try await Task.sleep(nanoseconds: 4_000_000_000)

你会看到它显示出了应有的样子。

您差不多完成了。使用@ViewBuilder,删除AnyView包装,不要使用保护

@ViewBuilder
var intergerView: some View {
if let integer = numberManager.integer {
LoadingView(loadingType: "Generating int")
} else {
Text("Integer is (integer)")
}
}

最新更新