【问题标题】:How to refer to @Published var within: class NetworkManager: ObservableObject如何在:类 NetworkManager: ObservableObject 中引用@Published var
【发布时间】:2020-03-02 06:51:24
【问题描述】:

我需要在

中定义引用 @Published var tests:[Test] 的 testData:[Test]

类 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"
},
...
]

【问题讨论】:

  • 您确定两个json 文件相同吗?
  • 是的,当然除了url:
  • // 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", "description":" test1a", "category":"test1" }, ... ]

标签: ios swift swiftui ios13 jsondecoder


【解决方案1】:

从 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
  }
}

【讨论】:

  • 感谢您对改进代码的及时答复。但是,我仍然需要将该代码链接到 testData,以便进一步使用。在我的第二个示例中,定义“let testData:[Test] = load("local.json")” 工作正常。但是我不知道如何为“class NetworkManager:ObservableObject”的情况定义它。任何进一步的提示都非常感谢。
  • // 我正在使用: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:字符串、CaseIterable、Codable、Hashable { case t1 = "test1" case t2 = "test2" case t3 = "test3" } }
  • P.S.我已经编辑了我的问题以显示附加代码。
  • 我让应用程序使用定义“let testData:[Test] = NetworkManager().tests”(参见上面的代码),但它没有显示远程 json 数据。我还缺少什么?
  • let testData:[Test] = NetworkManager().tests 在网络请求完成之前执行,所以它的初始值是一个空数组。你应该使用一个单独的视图,它拥有这个 ObservableObject 作为@ObservedObject。每当@ObservedObject@Published 属性发生变化时,视图就会自行重建。
【解决方案2】:

将“grouping: testData”替换为“grouping: networkManager.tests”并使用“@ObservedObject var networkManager: NetworkManager = NetworkManager()”使 testData 的定义变得多余,从而解决了问题。感谢@Josh Homann 的回答和评论,帮助我克服了这个问题。

【讨论】:

    猜你喜欢
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-14
    • 2021-01-25
    • 2019-12-28
    • 2022-07-05
    相关资源
    最近更新 更多