Swift macOS Xcode : Доступ к массиву пар значений ключей, созданных в классе из contentView

#arrays #swift #class #key-value

Вопрос:

Я написал некоторый код, который успешно считывает файл JSON в то, что я считаю массивом пар значений ключей с 5 значениями, связанными с каждым ключом. Файл JSON считывается во время создания класса и, похоже, работает. В contentView я хотел бы иметь возможность просматривать ключи (которые являются датами) и значения, возможно, с помощью цикла ForEach. К сожалению, я не могу получить доступ к массиву пар ключ-значение из представления содержимого. Инструкция print в классе выводит то, что я ожидал бы. В представлении содержимого arrayname.count возвращает 0. Я бы предположил, что упускаю из виду какую-то простую вещь, но иногда, когда вы смотрите на что-то слишком долго, вы упускаете очевидное. Если бы кто-нибудь мог указать мне правильное направление, я был бы признателен.

ForEach был моим способом попытаться определить, как получить доступ к отдельным элементам vooData, таким как дата, которая является ключом, или определенное значение, такое как закрытие.

Ниже приведен мой тестовый код. С уважением, Крис

Ниже приведены примеры моего файла JSON и результата инструкции print в классе ReadData.

 {
    "Meta Data": {
        "1. Information": "Daily Prices (open, high, low, close) and Volumes",
        "2. Symbol": "VOO",
        "3. Last Refreshed": "2021-09-22",
        "4. Output Size": "Full size",
        "5. Time Zone": "US/Eastern"
    },
    "Time Series (Daily)": {
        "2021-09-22": {
            "1. open": "402.1700",
            "2. high": "405.8500",
            "3. low": "401.2600",
            "4. close": "403.9000",
            "5. volume": "5979811"
        },

Stock(timeSeriesDaily: [

"2011-07-22": ReadJSONData.TimeSeriesDaily
(Open: "61.5000", 
High: "61.6000", 
Low: "61.2000", 
Close: "61.5500", 
Volume: “180400"
), 

"2014-06-18": ReadJSONData.TimeSeriesDaily
(
Open: "178.5900", 
High: "179.9700", 
Low: "178.1700", 
Close: "179.8700", 
Volume: “501900"
), 

"2015-09-18": ReadJSONData.TimeSeriesDaily
(
Open: "180.4100", 
High: "182.0400", 
Low: "179.6900", 
Close: "180.1300", 
Volume: “3205186"
)
])



import SwiftUI
struct Stock: Codable {
    let timeSeriesDaily: [String: TimeSeriesDaily]
    enum CodingKeys: String, CodingKey {
        case timeSeriesDaily = "Time Series (Daily)"
    }
}
struct TimeSeriesDaily: Codable {
    let Open, High, Low, Close: String
    let Volume: String
    enum CodingKeys: String, CodingKey {
        case Open = "1. open"
        case High = "2. high"
        case Low = "3. low"
        case Close = "4. close"
        case Volume = "5. volume"
    }
}
class ReadData {
//class ReadData: ObservableObject  {
//    @Published var tmpData  = [Stock]()
    var tmpData : [Stock] = []
    init() {
        loadData()
    }
    func loadData() {
        guard let url = Bundle.main.url(forResource: "VOO", withExtension: "json")
        else {
            print("Json file not found")
            return
        }
        let data = try? Data(contentsOf: url)
        let tmpData = try? JSONDecoder().decode(Stock.self, from: data!)
        print(tmpData!)
    }
}
struct ContentView: View {
//    @ObservedObject var vooData = ReadData()
    let readClass = ReadData()
    @State var vooData : [Stock] = ReadData().tmpData
    var body: some View {
        VStack{
            Text("Hello, world!")
                .padding()
                .frame(width: 600, height: 400, alignment: .center)
                .onAppear(perform: {
                    print(vooData.count)
                })
        }
    }
}
 

Комментарии:

1. Есть несколько вещей, которые я могу здесь обсудить, но я обеспокоен тем, что это JSONDecoder().decode(Stock.self, from: data!) действительно работает. Похоже, на самом деле ты хочешь именно [Stock] этого . Можете ли вы включить образец фактических данных JSON? И, может быть, опишите, что вы пытаетесь увидеть в своем ForEach ?

2. То, что вы опубликовали, не является допустимым JSON. Не могли бы вы, пожалуйста, опубликовать фактический файл JSON, который вы пытаетесь декодировать?

Ответ №1:

JSON, который вы включили, на самом деле недействителен. Я делаю предположение, что на самом деле это выглядит примерно так:

 {
    "Time Series (Daily)": {
        "2021-09-22": {
            "1. open": "402.1700",
            "2. high": "405.8500",
            "3. low": "401.2600",
            "4. close": "403.9000",
            "5. volume": "5979811"
        }
    }
}
 

Если это так, то вы можете внести некоторые небольшие изменения в свой код и заставить его работать.

  • Похоже, что вы изначально пытались использовать ObservableObject -это то, что вы должны использовать.


  • В исходном коде вы использовали let tmpData = , но никогда не устанавливали self.tmpData
  • Использование do/try/catch вместо try? предпочтительно, потому что в случае возникновения ошибки вы действительно можете распечатать ее.
  • Неясно, что именно вы пытаетесь отобразить, но я включил пример a ForEach , который преобразует словарь в пары ключ/значение, а затем отображает цену открытия.

 struct TimeSeriesDaily: Codable {
    let Open, High, Low, Close: String
    let Volume: String
    enum CodingKeys: String, CodingKey {
        case Open = "1. open"
        case High = "2. high"
        case Low = "3. low"
        case Close = "4. close"
        case Volume = "5. volume"
    }
}
class ReadData: ObservableObject  {
    @Published var tmpData  = Stock(timeSeriesDaily: [:])
    
    init() {
        loadData()
    }
    
    func loadData() {
        guard let url = Bundle.main.url(forResource: "VOO", withExtension: "json")
        else {
            print("Json file not found")
            return
        }
        do {
            let data = try Data(contentsOf: url)
            self.tmpData = try JSONDecoder().decode(Stock.self, from: data)
            print(self.tmpData)
        } catch {
            print(error)
        }
    }
}
struct ContentView: View {
    @ObservedObject var vooData = ReadData()
    
    var body: some View {
        VStack{
            ForEach(vooData.tmpData.timeSeriesDaily.map { ($0.key,$0.value) }, id: .0) { keyValuePair in
                VStack {
                    Text(keyValuePair.0)
                    Text("Open: (keyValuePair.1.Open)")
                }
            }
        }
    }
}
 

Комментарии:

1. Спасибо. Можете ли вы объяснить комментарий о том, что вы никогда не устанавливаете self.tmpData. Я предполагаю, что это может быть частью причины, по которой contentView не получил данные. Кроме того, являются ли данные, содержащиеся в tmpData, массивом пар ключевых значений? Если да, то ваше преобразование берет его из массива пар ключ-значение и преобразует в словарь, где есть только ключи и значения?

2. «Можете ли вы объяснить комментарий о том , чтобы никогда не устанавливать self.tmpData» Конечно, если вы не установите self.tmpData , он никогда не получит значения. let tmpData это не то же самое, что self.tmpData … область действия let tmpData предназначена только для этой функции. self.tmpData задает свойство для класса.

3. «являются ли данные, содержащиеся в tmpData, массивом пар ключевых значений» — в основном. Это Dictionary . Но словарь-это не просто «массив пар ключ-значение» — это то, что я map делал раньше.

4. Если это ответ на ваш вопрос, пожалуйста, используйте зеленую галочку, чтобы принять его, а также подумайте о том, чтобы проголосовать с помощью стрелки, если вы нашли ответ полезным.

Ответ №2:

Я думаю, что у вас есть небольшая ошибка в вашем init

У тебя есть

 class ReadData {
    var tmpData : [Stock] = []
    init() {
        loadData()
    }
    func loadData() {
        guard let url = Bundle.main.url(forResource: "VOO", withExtension: "json")
        else {
            print("Json file not found")
            return
        }
        let data = try? Data(contentsOf: url)
        let tmpData = try? JSONDecoder().decode(Stock.self, from: data!)

     // ^^^^^^^^^^^ - don't think this should be let

        print(tmpData!)
    }
}
 

но я не думаю, что вам нужны let данные tmpData внутри loadData . Я думаю, что вы намерены назначить это переменной экземпляра, а не локальной переменной.

Попробуй:

 //
//  ContentView.swift
//  JSONSample
//
//  Created by Scott Thompson on 9/23/21.
//

import SwiftUI

let jsonData = """
{
    "Meta Data": {
        "1. Information": "Daily Prices (open, high, low, close) and Volumes",
        "2. Symbol": "VOO",
        "3. Last Refreshe": "2021-09-22",
        "4. Output Size": "Full size",
    "5. Time Zone": "US/Eastern"
    },
    "Time Series (Daily)": {
        "2021-09-22": {
            "1. open": "402.1700",
            "2. high": "405.8500",
            "3. low": "401.2600",
            "4. close": "403.9000",
            "5. volume": "5979811"
        },
    }
}
"""

struct Stock: Codable {
    let timeSeriesDaily: [String: TimeSeriesDaily]
    enum CodingKeys: String, CodingKey {
        case timeSeriesDaily = "Time Series (Daily)"
    }
}

struct TimeSeriesDaily: Codable {
    let Open, High, Low, Close: String
    let Volume: String
    enum CodingKeys: String, CodingKey {
        case Open = "1. open"
        case High = "2. high"
        case Low = "3. low"
        case Close = "4. close"
        case Volume = "5. volume"
    }
}

class ReadData {
    let tmpData : [Stock]
    init() {
        let data = jsonData.data(using: .utf8)

        if let parsedData = try? JSONDecoder().decode(Stock.self, from: data!) {
            tmpData = [parsedData]
        } else {
            tmpData = []
        }
    }
}

struct ContentView: View {
    @State var vooData : [Stock] = ReadData().tmpData
    var body: some View {
        VStack{
            Text("Hello, world!")
                .padding()
                .frame(width: 600, height: 400, alignment: .center)
                .onAppear(perform: {
                    debugPrint(vooData)
                    print(vooData.count)
                })
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewLayout(PreviewLayout.sizeThatFits)
            .padding()
            .previewDisplayName("Default preview")
    }
}
 

Комментарии:

1. Вы совершенно правы в отношении параметра let tmpData vs, устанавливающего свойство класса, о котором я также говорил в своем ответе. Однако в вашем ответе не учитывается тот факт, что операция использовала асинхронный URL-запрос для получения данных JSON, и заменяет его несинхронным вызовом. Без аннотирования ReadData с ObservableObject , ContentView он не будет знать, что tmpData было установлено, и не будет корректно отображать данные.

2. Спасибо всем вам. Я многому научился, и теперь у меня есть функционирующий код.