SwiftUI 已发布的变量未更新。

5

我试图设置一个名为allCountries的已发布变量。在我的DispatchQueue中,它被设置并且实际上将正确的信息打印到控制台中。在我的content view中,数组中没有任何内容。在content view的init函数中,我调用了将获取数据并设置变量的函数,但当我打印到控制台时,该变量为空白。有人可以告诉我我做错了什么吗?

这里是我获取数据的位置

class GetCountries: ObservableObject {

@Published var allCountries = [Countries]()

func getAllPosts() {

    guard let url = URL(string: "apiURL")
        else {
            fatalError("URL does not work!")
    }

    URLSession.shared.dataTask(with: url) { data, _, _ in
        do {
            if let data = data {
                let posts = try JSONDecoder().decode([Countries].self, from: data)


                DispatchQueue.main.async {

                    self.allCountries = posts
                    print(self.allCountries)

                }
            } else  {
                DispatchQueue.main.async {
                    print("didn't work")
                }
            }
        }
        catch {
            DispatchQueue.main.async {
                print(error.localizedDescription)
            }
        }

    }.resume()
}

这是我的内容视图

struct ContentView: View {

var countries = ["Select Your Country", "Canada", "Mexico", "United States"]

@ObservedObject var countries2 = GetCountries()

@State private var selectedCountries = 0

init() {
    GetCountries().getAllPosts()
    print(countries2.allCountries)

}

var body: some View {
    NavigationView {

        ZStack {

            VStack {
                Text("\(countries2.allCountries.count)")}}}
3个回答

1
问题出现在您的ContentView的init中。您调用了TYPE的函数getAllPosts,但随后尝试访问该类型countries2的新INSTANCE的数据。您的countries2.allCountries实例属性实际上与GetCountries.allCountries类型属性没有任何关系。
那甚至可以编译吗?通常,如果要在类型本身上调用它们而不是在其实例上调用类的函数和属性,则需要将其定义为static。
也许这很奇怪,因为您正在尝试在ContentView的初始化之前完成此操作。
您可能需要执行两个步骤才能解决此问题。
第一步是让您的自定义初始化程序实际初始化countries2。
第二步是在该自定义初始化程序中调用countries2.getAllPosts,这将填充您尝试在countries2.allCountries中访问的实例属性。
试试这个:
struct ContentView: View {

var countries = ["Select Your Country", "Canada", "Mexico", "United States"]

// You'll initialize this in the custom init
@ObservedObject var countries2: GetCountries

@State private var selectedCountries = 0

init() {
    countries2 = GetCountries()
    countries2.getAllPosts()
    print(countries2.allCountries)

}

var body: some View {
    NavigationView {

        ZStack {

            VStack {
                Text("\(countries2.allCountries.count)")}}}

但我不确定你是否会在ContentView的初始化器中遇到异步调用问题。最好的做法可能是将其移动到一个函数中,在视图加载时调用该函数,而不是在 SwiftUI 结构体初始化时调用。

为此,您可以将网络调用移动到 ContentView 的一个函数中(而不是它的 init),然后使用一个 .onAppear 修饰符到您的视图上,这将在 NavigationView 出现时调用该函数。(现在我们不再使用自定义初始化器,我们需要回到初始化 countries2 属性。)

struct ContentView: View {

    var countries = ["Select Your Country", "Canada", "Mexico", "United States"]

    // We're initializing countries2 again
    @ObservedObject var countries2 = GetCountries()

    @State private var selectedCountries = 0

    // This is the function that will get your country data
    func getTheCountries() {
        countries2.getAllPosts()
        print(countries2.allCountries)

    }

    var body: some View {
        NavigationView {
            ZStack {
                VStack {
                    Text("\(countries2.allCountries.count)")
                }
            }
        }
        .onAppear(perform: getTheCountries) // Execute this when the NavigationView appears
    }
}

注意这种方法。根据您的UI架构,每次NavigationView出现时,它都会调用getTheCountries。如果您使用NavigationLink调用新视图,则如果返回到此第一页,则可能会重新加载。但是,您可以轻松地在getTheCountries中添加一个检查,以查看是否实际需要执行该工作。

谢谢你的回答,但是这两种方法都对我没用。 - C.Jones
他没有在类型上调用 getAllPosts()。他是在一个新的实例上调用它的。请注意类型名称后面的括号。尽管如此,你说的其他部分是正确的。 - Peter Schorn

1

应该使用同一个实例来初始化请求和读取结果,因此这里提供了固定的变量:

struct ContentView: View {

    var countries = ["Select Your Country", "Canada", "Mexico", "United States"]

    @ObservedObject var countries2 = GetCountries()   // member
    @State private var selectedCountries = 0

    init() {
        // GetCountries().getAllPosts()      // [x] local variable
        // print(countries2.allCountries)
    }

    var body: some View {
        NavigationView {
            ZStack {
                VStack {
                    Text("\(countries2.allCountries.count)")
                }
            }
        }
        .onAppear { self.countries2.getAllPosts() } // << here !!
    }
}

这样可以正确获取数组计数,但当我将文本视图更改为Text("(countries2.allCountries[0].Culture)")时,由于某种原因它会显示索引超出范围。 - C.Jones
@C.Jones,获取国家是异步操作,因此在开始时数组为空,视图正确显示计数为0,然后当结果出现时,它会相应地更新,但如果您硬编码访问第0个元素,则在开始时会出现异常,因为在空数组中没有[0]元素。需要使用ForEach来迭代国家并显示结果,它可以正确处理空数组,因此在开始时不会有任何异常。 - Asperi

1
将ObservedObject更改为StateObject
@StateObject var countries2 = GetCountries()

这个建议真是救了我。非常感谢你。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接