Как ссылаться на @Published var в: class NetworkManager: ObservableObject

Мне нужно определить testData: [Test], который относится к @Published var tests: [Test] в

class NetworkManager: ObservableObject (см. код).

Я пробовал следующее определение:

/// The app does not compile with this definition
//let testData:[Test] = NetworkManager(tests: Test)

/// The app works with this definition, but shows no remote json data
let testData:[Test] = NetworkManager().tests

class NetworkManager: ObservableObject {

@Published var tests:[Test] = [Test]()

func getAllTests() {
    let file = URLRequest(url: URL(string: "https://my-url/remote.json")!)
    let task = URLSession.shared.dataTask(with: file) { (data, _, error) in
    guard error == nil else { return }

        do {
            let tests = try JSONDecoder().decode([Test].self, from: data!)

            DispatchQueue.main.async {
                    self.tests = tests
                print(tests)

            }
        } catch {
            print("Failed To decode: ", error)
        }
    }
    task.resume()
}
    init() {
        getAllTests()
}
    init(tests: [Test]) {
        self.tests = tests
}
}

Код ниже работает нормально

/// The app works with this definition and shows the local json data
let testData:[Test] = load("local.json")

func load<T:Decodable>(_ filename:String, as type:T.Type = T.self) -> T {
let data:Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
    else {
        fatalError("Couldn't find \(filename) in main bundle.")
}
do {
    data = try Data(contentsOf: file)
} catch {
    fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}

do {
    let decoder = JSONDecoder()
    return try decoder.decode(T.self, from: data)
} catch {
    fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}

Однако в первой части я получаю сообщение об ошибке:

«Невозможно преобразовать значение типа Test.Type в ожидаемый тип аргумента [Test]» »

Что мне здесь не хватает? Любая помощь высоко ценится.

Дополнительная информация в ответ на ответ и вопрос, показывающий, как используется testData:

import SwiftUI
import Combine

struct Test: Hashable, Codable, Identifiable {
    var id:Int
    var imageName:String
    var imageUrl:String
    var category:Category
    var description:String

    enum Category: String, CaseIterable, Codable, Hashable {
        case t1 = "test1"
        case t2 = "test2"
        case t3 = "test3"
    }
}

class NetworkManager: ObservableObject {

      @Published var tests:[Test] = [Test]()
      private var subscriptions = Set<AnyCancellable>()

      func getAllTests() {
        let file = URLRequest(url: URL(string: "https://my-url/remote.json")!)
        URLSession
          .shared
          .dataTaskPublisher(for: file)
          .map(\.data)
          .decode(type: [Test].self, decoder: JSONDecoder())
          .replaceError(with: [])
          .receive(on: RunLoop.main)
          .assign(to: \.tests, on: self)
          .store(in: &subscriptions)
      }
      init() {
        getAllTests()
      }
      init(tests: [Test]) {
        self.tests = tests
      }
    }

let testData:[Test] = NetworkManager().tests

struct ContentView: View {

var categories:[String:[Test]] {
    .init(
        grouping: testData,
        by: {$0.category.rawValue}
    )
}

var body: some View {
    NavigationView{
        List (categories.keys.sorted(), id: \String.self) {key in TestRow(categoryName: "\(key) - Case".uppercased(), tests: self.categories[key]!)
            .frame(height: 320)
            .padding(.top)
            .padding(.bottom)
        }
        .navigationBarTitle(Text("TEST"))
    }
}

}

struct TestRow: View {

var categoryName:String
var tests:[Test]

    var body: some View {

        VStack {

            Text(self.categoryName)
                .font(.title)
                .multilineTextAlignment(.leading)

                ScrollView(.horizontal, showsIndicators: false) {
                    HStack(alignment: .top) {
                        ForEach(self.tests, id: \.self) { tests in

                            NavigationLink(destination:
                                TestDetail(test: tests)) {
                                TestItem(test: tests)
                                    .frame(width: 300)
                                    .padding(.trailing, 30)
                                  Spacer()

                        }}
                }
                .padding(.leading)
            }
        }

    }
}

struct TestDetail: View {

    var test:Test
    var body: some View {
        List{
        ZStack(alignment: .bottom) {

            Image(test.imageUrl)
                .resizable()
                .aspectRatio(contentMode: .fit)
            Rectangle()
                .padding()
                .frame(height: 80.0)
                .opacity(0.25)
                .blur(radius: 10)
            HStack{
                VStack(alignment: .leading) {
                    Text(test.imageName)
                        .padding()
//                    .color(.white)
                    .colorScheme(.light)
                    .font(.largeTitle)
            }
            .padding(.leading)
            .padding(.bottom)
            Spacer()
            }
        }
            .listRowInsets(EdgeInsets())
            VStack(alignment: .leading) {
                Text(test.description)
//                  .padding(.bottom)
//                  .color(.primary)
                    .colorScheme(.light)
                    .font(.body)
                    .lineLimit(nil)
                    .lineSpacing(12)

                HStack {
                    Spacer()
                    OrderButton()
                    Spacer()
                }.padding(.top, 50)
            }.padding(.top)
            .padding(.bottom)
        }
        .edgesIgnoringSafeArea(.top)
    .navigationBarHidden(true)
    }
}

struct TestItem: View {

    var test:Test
    var body:some View{
        VStack(spacing: 16.0)
        {
            Image(test.imageUrl)
            .resizable()
            .renderingMode(.original)
            .aspectRatio(contentMode: .fill)
            .frame(width: 300, height: 170)
            .cornerRadius(10)
            .shadow(radius: 10)

            VStack(alignment: .leading, spacing: 5.0)
            {
            Text(test.imageName)
//                .color(.primary)
                .font(.headline)
            Text(test.description)
                .font(.subheadline)
                //                .color(.secondary)
                .multilineTextAlignment(.leading)
                .lineLimit(2)
                .frame(height: 40)
            }
        }
    }
}

struct OrderButton : View {
    var body: some View {
        Button(action: {}) {
            Text("Order Now")
        }.frame(width: 200, height: 50)
            .foregroundColor(.white)
            .font(.headline)
            .background(Color.blue)
        .cornerRadius(10)
    }
}

class ImageLoader:ObservableObject
{
    @Published var data:Data = Data()
    func getImage(imageURL:String) {
        guard let test = URL(string: imageURL) else { return }

        URLSession.shared.dataTask(with: test) { (data, response, error) in
            DispatchQueue.main.async {
                if let data = data {
                    self.data = data
                }
            }
            print(data as Any)
        }.resume()
    }
    init(imageURL:String) {
        getImage(imageURL: imageURL)
    }
}

struct ContentView_Previews: PreviewProvider {
    @ObservedObject var imageLoader: ImageLoader
    init(test:String)
    {
        imageLoader = ImageLoader(imageURL: test)
    }
    static var previews: some View {
        ContentView()
    }
}

// local.json

[
{
"id":101,
"imageName":"test-f1a",
"imageUrl":"test-f1a",
"description":"test1a",
"category":"test1"
},
...
]

// remote.json

[
{
"id":101,
"imageName":"test-f1a",
"imageUrl":"https://my-url/test-f1a",
"description":"test1a",
"category":"test1"
},
...
]

person user12265655    schedule 05.11.2019    source источник
comment
Вы уверены, что оба json файла одинаковы?   -  person E.Coms    schedule 06.11.2019
comment
Да, кроме URL-адреса, конечно:   -  person user12265655    schedule 06.11.2019
comment
// local.json [{id: 101, imageName: test-f1a, imageUrl: test-f1a, description: test1a, category: test1}, ...] // remote.json [{id: 101, imageName: test -f1a, imageUrl: my-url / test-f1a, описание: test1a, category: test1}, ...]   -  person user12265655    schedule 06.11.2019


Ответы (2)


Начиная с iOS 13, URLSession был расширен издателем, поэтому идиоматически ваш код выглядит следующим образом:

import UIKit
import Combine

struct Test: Codable {
  var name: String
}

class NetworkManager: ObservableObject {

  @Published var tests:[Test] = [Test]()
  private var subscriptions = Set<AnyCancellable>()

  func getAllTests() {
    let file = URLRequest(url: URL(string: "https://my-url/remote.json")!)
    URLSession
      .shared
      .dataTaskPublisher(for: file)
      .map(\.data)
      .decode(type: [Test].self, decoder: JSONDecoder())
      .replaceError(with: [])
      .receive(on: RunLoop.main)
      .assign(to: \.tests, on: self)
      .store(in: &subscriptions)
  }
  init() {
    getAllTests()
  }
  init(tests: [Test]) {
    self.tests = tests
  }
}
person Josh Homann    schedule 05.11.2019
comment
Спасибо за быстрый ответ с улучшенным кодом. Однако мне все еще нужно связать этот код с testData, который будет использоваться в дальнейшем. В моем 2-м примере определение let testData: [Test] = load (local.json) работает нормально. Но я не могу понять, как определить его для класса дела NetworkManager: ObservableObject. Любые дальнейшие подсказки приветствуются. - person user12265655; 06.11.2019
comment
// Я использую: import SwiftUI import Combine struct Test: Hashable, Codable, Identifiable {var id: Int var imageName: String var imageUrl: String var category: Category var description: String enum Category: String, CaseIterable, Codable, Hashable { case t1 = test1 case t2 = test2 case t3 = test3}} - person user12265655; 06.11.2019
comment
P.S. Я отредактировал свой вопрос, чтобы показать дополнительный код. - person user12265655; 06.11.2019
comment
Я получил приложение, работающее с определением let testData: [Test] = NetworkManager (). Tests (см. Код выше), но оно не показывает удаленных данных json. Я все еще что-то упускаю? - person user12265655; 07.11.2019
comment
let testData:[Test] = NetworkManager().tests выполняется до того, как сетевой запрос завершится, поэтому его начальное значение будет пустым массивом. вы должны использовать отдельное представление, которому этот ObservableObject принадлежит как @ObservedObject. Затем представление будет перестраиваться при изменении @Published свойств @ObservedObject. - person Josh Homann; 07.11.2019
comment
Спасибо, Джош, за ваш дополнительный вклад. Я пробовал это, но у меня ничего не вышло. Для \ @ObservedObject требуется переменная, а не константа. Таким образом, let не работает. А для var я получаю сообщение об ошибке. Ссылка на инициализатор 'init (wrappedValue :)' в 'ObservedObject' требует, чтобы '[Test]' соответствовал 'ObservableObject', если я определяю его в struct ContentView: \ @ObservedObject var testData: [Test ] = NetworkManager (). Тесты. Так что я не уверен, правильно ли я вас понял. Может, можно привести пример кода? - person user12265655; 07.11.2019
comment
P.S. Я снова отредактировал свой вопрос, чтобы показать полный код с использованием вашего обновления кода для класса NetworkManager (однако он все еще без отдельного представления, поскольку я еще не понял этого). - person user12265655; 07.11.2019
comment
Хорошо, еще раз подумав о твоих комментариях, Джош, я мог бы решить свою проблему. Все, что мне нужно было сделать, это заменить grouping: testData на grouping: networkManager.tests и использовать @ObservedObject var networkManager: NetworkManager = NetworkManager (), как в ContentView. Это делает определение testData излишним. Далее мне пришлось заменить imageUrl на imageName в моем коде (TestDetail и TestItem). Еще раз большое спасибо за ваш ценный вклад. - person user12265655; 07.11.2019

Замена grouping: testData на grouping: networkManager.tests и использование «@ObservedObject var networkManager: NetworkManager = NetworkManager ()» делает определение testData избыточным и, таким образом, решает проблему. Спасибо @Josh Homann за его ответ и комментарий, которые помогли мне преодолеть эту проблему.

person user12265655    schedule 07.11.2019