简体   繁体   English

SwiftUI:将值或键:值对写入 JSON 解码字典

[英]SwiftUI: Writing Values or Key:Value Pairs to JSON Decoded Dictionary

I am struggling with modifying a value in a Dictionary that is made up of data from JSON download data from a php query to SQL.我正在努力修改由来自 JSON 的数据组成的字典中的值,将来自 php 查询的数据下载到 SQL。 I need to either create a new pair key:value pair (best approach) or reuse a field from the original SQL data that I am not using and rewrite the value in the pair.我需要创建一个新的键:值对(最佳方法)或重用我未使用的原始 SQL 数据中的一个字段,并重写该对中的值。 In the code below I am trying the second approach (rewrite the value in a key:value pair I am not using).在下面的代码中,我正在尝试第二种方法(重写我未使用的键:值对中的值)。 The issue is in the getData function (near the bottom) and is noted by the error comment.问题出在 getData function(靠近底部)中,并在错误注释中注明。 There is a LocationManager to get the users current location and some extensions that help the search box capability that are not shown, but let me know if needed.有一个 LocationManager 可以获取用户的当前位置,以及一些有助于搜索框功能的扩展,但没有显示,但如果需要,请告诉我。

import SwiftUI
import MapKit

struct Response: Codable, Hashable {
    
    var District: String
    var BusinessName: String
    var LocationAddress: String
    var LocationCity: String
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(BusinessName)
    }
}

struct ContentView: View {
    @ObservedObject var lm = LocationManager()
    
    @State var response = [Response]()
    @State var search: String = ""
    @State private var showCancelButton: Bool = false
    @State private var searchPerfromed: Bool = false
    @State var location2: CLLocationCoordinate2D?
    @State var distance: Double = 0
    @State var dist: [String: Double] = Dictionary()
    
    var body: some View {
        VStack {
            HStack {
                HStack {
                    Image(systemName: "magnifyingglass")
                    TextField("Search", text: $search, onEditingChanged: { isEditing in
                        self.showCancelButton = true
                    }, onCommit: {
                        self.searchPerfromed = true
                        getData()  // Function to get JSON data executed after search execution
                    }).foregroundColor(.primary)
                    Button(action: {
                        self.search = ""
                        self.searchPerfromed = false
                    }) {
                        Image(systemName: "xmark.circle.fill").opacity(search == "" ? 0 : 1)
                    }
                }
                .padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
                .foregroundColor(.secondary)
                .background(Color(.secondarySystemBackground))
                .cornerRadius(10.0)
                if showCancelButton  {
                    Button("Cancel") {
                        UIApplication.shared.endEditing(true)
                        self.search = ""
                        self.showCancelButton = false
                        self.searchPerfromed = false
                    }
                }
            }
            .padding(EdgeInsets(top: 10, leading: 0, bottom: 1, trailing: 0))
            .padding(.horizontal)
            Spacer()
            if searchPerfromed == true {
                List {
                    ForEach(response.sorted {$0.LocationAddress > $1.LocationAddress}, id: \.self) { item in
                        VStack(alignment: .leading) {
                            HStack(alignment: .center) {
                                Text("\(item.BusinessName)").font(.subheadline)
                                Spacer()
                                if dist[item.LocationAddress] == 0.0 {
                                    Text("Unknown").font(.caption).foregroundColor(.gray)
                                } else {
                                    Text("\(dist[item.LocationAddress] ?? 0, specifier: "%.0f") mi")
                                    .font(.caption).foregroundColor(.gray)
                                }
                            }
                            Text("\(item.LocationAddress), \(item.LocationCity)").font(.subheadline)
                        }
                    }
                }
            }
        }
    }
    
    func getLocation(from address: String, completion: @escaping (_ location: CLLocationCoordinate2D?)-> Void) {
        let geocoder = CLGeocoder()
        geocoder.geocodeAddressString(address) { (placemarks, error) in
            guard let placemarks = placemarks,
            let location = placemarks.first?.location?.coordinate else {
                completion(nil)
                return
            }
            completion(location)
        }
    }
    
    func getDistance() {
        let loc1 = CLLocation(latitude: lm.location1!.latitude, longitude: lm.location1!.longitude)
        let loc2 = CLLocation(latitude: location2?.latitude ?? lm.location1!.latitude, longitude: location2?.longitude ?? lm.location1!.longitude)
        let dist = loc1.distance(from: loc2)
        distance = dist * 0.00062 // convert from meters to miles
    }
    
    func getData() {
        let baseURL = "https://???"
        let combinedURL = baseURL + search
        let encodedURL = combinedURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        let url = URL(string: encodedURL)!
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            do {
                if let responseData = data {
                    let decodedData = try JSONDecoder().decode([Response].self, from: responseData)
                    DispatchQueue.main.async {
                        self.response = decodedData
                        print(decodedData)
                        for item in self.response {
                            let address = item.LocationAddress
                            getLocation(from: address) { coordinates in
                                self.location2 = coordinates
                                getDistance()
                                dist[address] = distance
                                let d = String(format: "%.1f", distance)
                                print(item.District)
                                item[District] = d  //Error: Cannot find 'District' in scope
                            }
                        }
                    }
                } else {
                    print("No data retrieved")
                }
            } catch {
                print("Error: \(error)")
            }
        }.resume()
    }
    
}

Just above the issue I assigned the distance value to a new Dictionary and match the values based on address, but this has limitations with other changes I need to make to get to the end state with the app.就在问题之上,我将距离值分配给新字典并根据地址匹配值,但这对我需要进行的其他更改有限制,以便使用应用程序到达 state 的末尾。 I have tried lots of different ways to assign the distance to an existing key:value pair in Response or create a new key value pair in Response.我尝试了许多不同的方法来将距离分配给现有的键:响应中的值对或在响应中创建新的键值对。 Some examples below:下面的一些例子:

item.District = d  //Error: Cannot assign to property: 'item' is a 'let' constant
item[Distance] = d  //Error: Cannot find 'Distance' in scope
item["Distance"] = d  //Error: Value of type 'Response' has no subscripts

How do I create a new key:value pair in Response, or assign the value of d to the District key?如何在 Response 中创建新的 key:value 对,或者将 d 的值分配给 District 键? Thanks so much in advance for any help you can provide.非常感谢您提供的任何帮助。

First thing, your instance variables on your Response struct are a little confusing because they don't follow the convention of having variables in all-lowercase, and types capitalized.首先,您的Response结构上的实例变量有点令人困惑,因为它们不遵循变量全小写和类型大写的约定。

Second, for your first error in your list of 3, item is a let constant because it is inside a for loop.其次,对于 3 列表中的第一个错误, item是一个 let 常量,因为它位于 for 循环内。 You can get around this by declaring the loop this way:您可以通过以下方式声明循环来解决此问题:

for var item in self.response {
    // I can modify item in this loop because it is declared a var
}

The other two errors are pretty self-explanatory, I think.我认为其他两个错误是不言自明的。

Third, it sounds like you want to alter your Response object programmatically after receiving it, which is also a bit of an anti-pattern.第三,听起来您想在收到Response object 后以编程方式更改它,这也是一种反模式。 If you want to modify an object you have downloaded from a server, that's understandable, but it is confusing for someone reading your code to alter an object called "Response."如果您想修改从服务器下载的 object,这是可以理解的,但是对于阅读您的代码来更改名为“响应”的 object 的人来说,这是令人困惑的。 (once you modify it, it no longer represents the server response for which it is named) At a minimum, you could change District to be a computed property of Response . (一旦你修改它,它就不再代表它被命名的服务器响应)至少,你可以将District更改为Response的计算属性。

All that said, if you instantiate your loop using the var keyword, you should be able to do:综上所述,如果您使用var关键字实例化循环,您应该能够:

item.District = d

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM