【问题标题】:Fetch data from server swiftUI从服务器 swiftUI 获取数据
【发布时间】:2021-10-21 01:32:01
【问题描述】:

我需要在视图加载之前获取数据并在按钮文本中显示数据。

Locations.swift

class GetLocations :ObservableObject{

 @Published var arrLocations = NSArray()
 
   func getLocNames(Action:String, Id: String, completion: @escaping (NSArray) -> Void){

       //fetch data from server
     let session = URLSession.shared
        session.dataTask(with: request) { (data, response, error)  
    in 
if let response = response {
                    print(response)
                }
 if let data = data {
                    let parseResult: NSDictionary!
                    do {
                        parseResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary
self.arrLocations = (parseResult.value(forKey: "InfoList"))as! NSArray 
                    }catch {
                       print(error)
                   }
                }
            }.resume()
           
           return self.arrLocations
    }
} 

现在我在另一个 swiftUI 文件中调用了上述函数

GetLocationData.swift
    import SwiftUI
    struct GetLocationData: View {
     @State var arrloc = NSArray()
     init() {
                let g = GetLocations()
                g.getLocNames(Action: "US", Id: "ba6fcd92-3d1e-4fb6-b135-0c47ea1815cd"){locationsarray in
                    self.arrloc = locationsarray
       }
}
    var body: some View {
      VStack(spacing: 20){
        Button(action: {},
         label: {
                 Text("Select Menu"). //Here I need to assign the locationsarray I got from server and assign the valueof key to it (In obj-C we have [array objectAtIndex:0]objectForKey:@"Name"]].
 I couldnt find the equivalent of it in swift

                .frame(minWidth: 0, maxWidth: 500)
                .padding()
                .background(Color.clear)
                .foregroundColor(Color.black)
                .font(.custom("Open Sans", size: 18))
                .overlay(
                          RoundedRectangle(cornerRadius: 10)
                           .stroke(Color.gray, lineWidth: 2)
                                )

        })
      }
    }

init() 中的错误:Escaping closure captures mutating 'self' parameter 如何在视图加载之前解决它并获取数据并将数据分配给按钮文本?

【问题讨论】:

  • 您的 getLocNamesgetLocationsByAppId 名称不匹配。除此之外,在getLocNames 中,您正在返回一个值,而getLocationsByAppId 似乎正在使用闭包。这些方法不同。
  • 首先,不要在 Swift 中使用 NS... 类。使用 swift 本机类型。其次,将您的数据移动到模型对象中。模型和视图的分离是 SwiftUI 中一个非常重要的概念。对数组使用@Published,您最初将有一个空数组,一旦填充数组,视图将自动重绘。
  • @George 我编辑了我的代码。实际上我更改了方法的名称,所以在发布 SO 名称时这样做是为了匹配。
  • @Paulw11 Published is already used 。请检查我的代码
  • 您的 init() 缺少“}”,这是您的问题。

标签: ios xcode ipad swiftui


【解决方案1】:

让我们简化问题。您正在尝试在 init 中运行异步函数,并将其结果设置为属性。问题是因为GetLocationData 还没有被实例化,所以没有self 的实例可以使用。

这是问题的简化示例:

struct ContentView: View {
    @State private var completed = false

    init() {
        someAsyncronousTask { newCompleted in // <- Error here
            completed = newCompleted
        }
    }

    var body: some View {
        Text("Completed: \(completed ? "yes" : "no")")
    }

    private func someAsyncronousTask(completion: @escaping (Bool) -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            // Waited 3 seconds to simulate something like URLSession request
            completion(true)
        }
    }
}

这给了我们错误,就像你有的那样:

转义闭包捕获变异的“自我”参数

要解决此问题,只需在视图的onAppear 中运行此代码即可。

固定示例代码:

struct ContentView: View {
    @State private var completed = false

    var body: some View {
        Text("Completed: \(completed ? "yes" : "no")")
            .onAppear {
                someAsyncronousTask { newCompleted in
                    completed = newCompleted
                }
            }
    }

    private func someAsyncronousTask(completion: @escaping (Bool) -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            // Waited 3 seconds to simulate something like URLSession request
            completion(true)
        }
    }
}

这显然需要转换成你的代码,但我希望这给你的想法,因为简化的代码。

最终结果应该是这样的:

struct GetLocationData: View {
    @State var arrloc = NSArray()

    var body: some View {
        VStack(spacing: 20){
            Button(action: {},
                   label: {
                Text("Select Menu"). //Here I need to assign the locationsarray I got from server and assign the valueof key to it (In obj-C we have [array objectAtIndex:0]objectForKey:@"Name"]].
                I couldnt find the equivalent of it in swift

                    .frame(minWidth: 0, maxWidth: 500)
                    .padding()
                    .background(Color.clear)
                    .foregroundColor(Color.black)
                    .font(.custom("Open Sans", size: 18))
                    .overlay(
                        RoundedRectangle(cornerRadius: 10)
                            .stroke(Color.gray, lineWidth: 2)
                    )

            })
        }
        .onAppear {
            let g = GetLocations()
            g.getLocNames(Action: "US", Id: "ba6fcd92-3d1e-4fb6-b135-0c47ea1815cd"){locationsarray in
                self.arrloc = locationsarray
            }
        }
    }
}

注意:你应该使用Array 而不是NSArray

【讨论】:

  • 但我不想等待 3 秒。可能 b 我不需要异步请求,因为我想在 viewload 中显示数据
  • @Honey 不,这不是重点。 3秒只是为了表明一段时间后可能会发生一些事情,就像你的URLSession一样。你的 URLSession 不是即时的 - 这就是为什么它有一个闭包而不是一个返回类型。
  • @Honey 添加了你应该是什么样子的代码
【解决方案2】:

首先需要明确的是模型和视图之间的分离。

GetLocations 是您的模型类。它是一个ObservableObject,它有一个@Published 您的位置名称数组。不需要@State 数组。您应该直接引用模型对象的实例。

由于模型对象将更新其自己的 arrLocations 属性,因此也无需将完成处理程序传递给 getLocNames - 这是您的错误的原因。您正在尝试在其自己的初始化程序的闭包中改变 self - 您的视图结构。这是不可能的,因为结构是不可变的。

在我们查看模型对象时,让我们摆脱使用 NSArray(在 Swift 中应该始终使用正确类型的数组)和旧式 JSONSerialization 并使用 Codable。我假设您所追求的数组是某个结构的数组,它至少有一个 Name 属性,它是一个字符串 - 如果您想要其他属性,您需要将这些属性添加到结构中。我们还将添加codingKeys 以将大写的属性名称映射到驼峰式。

struct LocationResponse {
    let infoList: [LocationInfo]

    enum CodingKeys: String, CodingKey {
        case infoList = "InfoList"
    }
}

struct LocationInfo {
    let name: String

    enum CodingKeys: String, CodingKey {
        case name = "Name"
    }
}

class GetLocations: ObservableObject{

 @Published var arrLocations = [String]()
 
   func getLocNames(Action:String, Id: String {

       //fetch data from server
       let session = URLSession.shared
       session.dataTask(with: request) { (data, response, error) in 
           if let response = response {
                    print(response)
           }

           if let data = data {
               do {
                   let locationResponse = try JSONDecoder().decode(LocationResponse.self, from: data)
                   DispatchQueue.main.async {
                       self.arrLocations = locationResponse.infoList
                   }
               } catch {
                   print("JSON decoding error: \(error)")
               }
           }
        }.resume()
    }
} 

现在,您需要将模型对象的实例传递给您的视图。我建议使用环境。使用创建 GetLocationData 视图的 .environmentObject 视图修饰符将实例添加到您的环境中

GetLocationData().environmentObject(GetLocations())

然后您可以在内容视图中使用它。建议您不要使用init,而是使用.onAppear 来获取位置。

我无法完全按照您想要对按钮执行的操作,因此我已将该代码替换为一个简单的位置名称列表 - 您应该可以使用此操作

struct GetLocationData: View {
    @EnvironmentObject: getLocations: GetLocations
      
    var body: some View {
      VStack(spacing: 20){
          ForEach(getLocations.infoList, id: \.self) { location in
              Text(location.name)
          }
      }.onAppear {
          self.getLocations.getLocNames(Action: "US", Id: "ba6fcd92-3d1e-4fb6-b135-0c47ea1815cd")
      }
    }
}

【讨论】:

    猜你喜欢
    • 2021-05-10
    • 1970-01-01
    • 2017-11-20
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 2022-11-21
    • 2019-11-14
    相关资源
    最近更新 更多