【问题标题】:SwiftUI published variable not updatingSwiftUI 发布的变量未更新
【发布时间】:2023-04-08 09:39:01
【问题描述】:

我正在尝试设置一个名为 allCountries 的已发布变量。在我的 DispatchQueue 中,它正在被设置并且实际上正在将正确的信息打印到控制台。在我的内容视图中,数组中没有任何内容。在内容视图的 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)")}}}

【问题讨论】:

    标签: swift swiftui swift5


    【解决方案1】:

    问题发生在您的ContentViewinit 中。您正在调用 TYPE 的函数 getAllPosts,但随后您尝试访问该类型 countries2 的新实例的数据。您的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 中添加检查,以查看是否确实需要该工作。

    【讨论】:

    • 感谢您的回答,但这些方法都不适合我。
    • 他没有在类型上调用getAllPosts()。他在一个新的实例上调用它。注意类型名称后面的括号。尽管如此,你所说的其余部分是正确的。
    【解决方案2】:

    应该使用同一个实例来发起请求和读取结果,所以这里是固定的变种:

    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,获取国家是异步操作,所以一开始数组是空的,视图显示正确计数为 0,然后当结果出现时它会相应更新,但如果你硬编码访问0 元素然后在开始时会出现异常,因为在空数组中没有 [0] 元素。它需要使用 ForEach 来迭代国家并显示结果,它与空数组一起正常工作,所以在开始时不会有任何异常。
    猜你喜欢
    • 2020-11-26
    • 2021-12-19
    • 2021-09-07
    • 1970-01-01
    • 1970-01-01
    • 2020-12-05
    • 1970-01-01
    • 1970-01-01
    • 2020-04-02
    相关资源
    最近更新 更多