首页 > 解决方案 > swiftUI / Xcode 12 从服务器获取数据

问题描述

我对 swiftUI 非常陌生,并且一直在学习景观应用教程。我一直在尝试将数据源从捆绑的 JSON 文件切换到远程 JSON 源,但到目前为止,我对如何将我所学到的关于 URLSession 的知识与教程加载代码相结合感到迷茫。

苹果的代码:

final class ModelData: ObservableObject {
    @Published var landmarks: [Landmark] = load("landmarkData.json")
 //   @Published var landmarks: [Landmark] = apiCall.getLocations(locations)
}

func load<T: Decodable>(_ filename: String) -> 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)")
    }
}

我必须从远程源加载:

struct Location: Codable, Identifiable {
    let id = UUID()
    let country: String
    let name: String
}

class apiCall {
    func getLocations(completion:@escaping ([Location]) -> ()) {
        guard let url = URL(string: "https://overseer.cyou/heritage/heritageData.json") else { return }
        URLSession.shared.dataTask(with: url) { (data, _, _) in
            let locations = try! JSONDecoder().decode([Location].self, from: data!)
            print(locations)
            
            DispatchQueue.main.async {
                completion(locations)
            }
        }
        .resume()
    }
}

任何人都可以告诉我我是如何做到这一点的,最好是从一个完整的初学者的角度来看?

标签: jsonxcodeswiftui

解决方案



// framework support
import SwiftUI
import Combine

// List view setup
struct LocationsView: View {
    
    @ObservedObject var viewModel = LocationModel()
    
    var body: some View {
        List(viewModel.locations) { location in
            HStack {
                VStack(alignment: .leading) {
                    Text(location.name)
                        .font(.headline)
                    Text(location.country)
                        .font(.subheadline)
                }
            }
        }
    }
}

// Location model
struct Location: Codable, Identifiable {
    var id = UUID()
    let country: String
    let name: String
    let locationId: Int = 0
    
    enum CodingKeys: String, CodingKey {
        case locationId = "id"
        case country
        case name
    }
}

// Location view model class
class LocationModel: ObservableObject {
    
    @Published var locations: [Location] = []
    var cancellationToken: AnyCancellable?
    
    init() {
        getLocations()
    }
    
}

extension LocationModel {
    
    func getLocations() {
        cancellationToken = self.request("https://overseer.cyou/heritage/heritageData.json")?
            .mapError({ (error) -> Error in
                print(error)
                return error
            })
            .sink(receiveCompletion: { _ in },
                  receiveValue: {
                    self.locations = $0
            })
    }

    // API request
    private func request(_ path: String) -> AnyPublisher<[Location], Error>? {

        guard let url = URL(string: path)
        else { return nil }
        let request = URLRequest(url: url)

        return apiCall.run(request)
            .map(\.value)
            .eraseToAnyPublisher()
    }
}


// API setup
struct apiCall {
    
    struct Response<T> {
        let value: T
        let response: URLResponse
    }
    
    static func run<T: Decodable>(_ request: URLRequest) -> AnyPublisher<Response<T>, Error> {
        return URLSession.shared
            .dataTaskPublisher(for: request)
            .tryMap { result -> Response<T> in
                let value = try JSONDecoder().decode(T.self, from: result.data)
                return Response(value: value, response: result.response)
            }
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
}




推荐阅读