Początkujący tutaj, trochę nad głową. ;)

Znalazłem przykłady, które pokazały mi, jak uzyskać dane z kanału API JSON, jeśli kanał jest zorganizowany jako tablicę obiektów, ale nie wiem, jak podejść do uzyskania danych (konkretnie, URL i tytuł ) Jeśli dane, które pobierają, wraca w bardziej złożonej zagnieżdżonej strukturze, takiej jak ta:

{
    "races": {
        "videos": [{
            "id": 1,
            "url": "firsturl",
            "title": "1st Video Title"
        }, {
            "id": 2,
            "url": "secondurl",
            "title": "2nd Video Title"
        }]
    }
}

Udało mi się uzyskać dane z innego kanału API, który jest zorganizowany jako prosty zestaw obiektów - jest to jak powyżej, ale bez dodatkowych dwóch obiektów ołowiowych, a mianowicie: {"Races": {"Filmy" :

Oto kod, który zrozumiałem razem z kilku przykładów, które działały dla prostej tablicy:

import SwiftUI

struct Video: Codable, Identifiable {
    public var id: Int
    public var url: String
    public var title: String
}

class Videos: ObservableObject {
  @Published var videos = [Video]()
     
    init() {
        let url = URL(string: "https://exampledomain.com/jsonapi")!
        URLSession.shared.dataTask(with: url) {(data, response, error) in
            do {
                if let videoData = data {
                    let decodedData = try JSONDecoder().decode([Video].self, from: videoData)
                    DispatchQueue.main.async {
                        self.videos = decodedData
                    }
                } else {
                    print("no data found")
                }
            } catch {
                print("an error occurred")
            }
        }.resume()
    }
}

struct VideosView: View {
    @ObservedObject var fetch = Videos()
    var body: some View {
        VStack {
            List(fetch.videos) { video in
                VStack(alignment: .leading) {
                    Text(video.title)
                    Text("\(video.url)")
                }
            }
        }
    }
}

Spędziłem kilka godzin w ciągu kilku dni czytanie i oglądanie samouczków, ale do tej pory nic nie tonie, aby pomóc mi rozwiązać bardziej złożoną karmę JSON API. Wszelkie wskazówki byłyby bardzo doceniane!

AKTUALIZACJA:

Z pomocą Swift Placground Tutorial i sugerowane struktury wymienione w poniższych komentarzach, udało mi się pobrać bardziej złożone dane, ale tylko w Swift Playas, korzystając z tego:

import SwiftUI

struct Welcome: Codable {
    let races: Races
}

struct Races: Codable {
    let videos: [Video]
}

struct Video: Codable {
    let id: Int
    let url, title: String
}

func getJSON<T: Decodable>(urlString: String, completion: @escaping (T?) -> Void) {
    guard let url = URL(string: urlString) else {
        return
    }
    let request = URLRequest(url: url)
    URLSession.shared.dataTask(with: request) { (data, response, error) in
        if let error = error {
            print(error.localizedDescription)
            completion(nil)
            return
        }
        guard let data = data else {
            completion(nil)
            return
        }
        let decoder = JSONDecoder()
        guard let decodedData = try? decoder.decode(T.self, from: data) else {
            completion(nil)
            return
        }
        completion(decodedData)
    }.resume()
}

getJSON(urlString: "https://not-the-real-domain.123/api/") { (followers:Welcome?) in
    if let followers = followers {
        for result in followers.races.videos {
            print(result.title )
        }
    }
}

Teraz zmagam się z tym, jak prawidłowo zintegrować fragment fragmentami placów zabaw do funkcji Video Working Swiftui Fileviews itp.

AKTUALIZACJA 2:

import SwiftUI

struct Welcome: Codable {
    let races: RaceItem
}

struct RaceItem: Codable {
    let videos: [VideoItem]
}

struct VideoItem: Codable {
    let id: Int
    let url: String
    let title: String
}

class Fetcher: ObservableObject {
    func getJSON<T: Decodable>(urlString: String, completion: @escaping (T?) -> Void) {
        guard let url = URL(string: urlString) else {
            return
        }
        let request = URLRequest(url: url)
        URLSession.shared.dataTask(with: request) { (data, response, error) in
            if let error = error {
                print(error.localizedDescription)
                completion(nil)
                return
            }
            guard let data = data else {
                completion(nil)
                return
            }
            let decoder = JSONDecoder()
            guard let decodedData = try? decoder.decode(T.self, from: data) else {
                completion(nil)
                return
            }
            completion(decodedData)
        }.resume()
    }
}

struct JSONRacesView: View {
    
    @ObservedObject var fetch = Fetcher()
    
    getJSON(urlString:"https://not-the-real-domain.123/api/") { (followers:Welcome?) in
        if let followers = followers {
            for result in followers.races.videos {
                print(result.title )
            }
        }
    }
    
    var body: some View {
        VStack {
            List(fetch.tracks) { track in
                VStack(alignment: .leading) {
                    Text(track.title)
                    Text("\(track.url)")
                }
            }
        }
    }
0
Bei 13 marzec 2021, 06:17

1 odpowiedź

Najlepsza odpowiedź

Jest świetna strona o nazwie QuickType (app.quicktype.io), gdzie można wkleić w niektórych JSON i wygenerowali dla Ciebie Swift Strustls. Oto, co ci daje:

import Foundation

// MARK: - Welcome
struct Welcome: Codable {
    let races: Races
}

// MARK: - Races
struct Races: Codable {
    let videos: [Video]
}

// MARK: - Video
struct Video: Codable {
    let id: Int
    let url, title: String
}

Mają błąd w swoim generatorze szablonu, który łączy linię demonstracyjną (złożyłem żądanie pull, które jest scalone, ale nie jest na miejscu w miejscu tego pisania), ale tutaj powinno wyglądać:

let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)

Korzystanie z do/try, dzięki czemu można złapać błędy, możesz dekodować dane i osiągnąć niższe poziomy, robiąc:

do {
  let welcome = try JSONDecoder().decode(Welcome.self, from: jsonData)
  let videos = welcome.races.videos //<-- this ends up being your [Video] array
} catch {
  //handle any errors
}

Aktualizacja , na podstawie komentarzy i aktualizacji : Wybrałeś trochę innej trasy niż moja początkowa sugestia, ale to dobrze. Jedyną rzeczą, którą sugerowałem, jest to, że chcieć poradzić sobie z błędami w pewnym momencie, a nie tylko powracając nil we wszystkich zakończeniach (zakładając, że musisz obsługiwać błędy - może po prostu nie ładuje się, jest dopuszczalne ).

Oto światła refaktor twojego kodu:

class Fetcher: ObservableObject {
    @Published var tracks : [VideoItem] = []
    
    private func getJSON<T: Decodable>(urlString: String, completion: @escaping (T?) -> Void) {
        guard let url = URL(string: urlString) else {
            return
        }
        let request = URLRequest(url: url)
        URLSession.shared.dataTask(with: request) { (data, response, error) in
            if let error = error {
                print(error.localizedDescription)
                completion(nil)
                return
            }
            guard let data = data else {
                completion(nil)
                return
            }
            let decoder = JSONDecoder()
            guard let decodedData = try? decoder.decode(T.self, from: data) else {
                completion(nil)
                return
            }
            completion(decodedData)
        }.resume()
    }
    
    func fetchData() {
        getJSON(urlString:"https://not-the-real-domain.123/api/") { (followers:Welcome?) in
            DispatchQueue.main.async {
                self.tracks = followers?.races.videos ?? []
            }
        }
    }
}

struct JSONRacesView: View {
    @StateObject var fetch = Fetcher()
    
    var body: some View {
        VStack {
            List(fetch.tracks, id: \.id) { track in
                VStack(alignment: .leading) {
                    Text(track.title)
                    Text("\(track.url)")
                }
            }
        }.onAppear {
            fetch.fetchData()
        }
    }
}

Widać, że teraz Fetcher ma właściwość @Published, która będzie przechowywać utwory ([Wideoitem]). getJSON jest nadal w Fetcher, ale teraz jest private, aby pokazać, że nie ma być wywołane bezpośrednio. Ale teraz jest nowa funkcja o nazwie fetchData(), że twój widok zadzwoni. Kiedy fetchData otrzymuje dane, ustawia @published właściwość do tych danych. Użyłem operatora ??, aby powiedzieć kompilatorowi, że jeśli followers jest zero, a następnie użyj []. Jest to wszystko w bloku DispatchQueue.main.async, ponieważ połączenie URL prawdopodobnie nie zamierza powrócić na główny wątek i musimy upewnić się, że zawsze zaktualizuje interfejs użytkownika w głównym wątku (XCode ostrzega cię o tym w czasie wykonywania, jeśli Zaktualizujesz UI na innym wątku).

JSONRacesView Połączenia fetchData w onAppear, który zdarza się dokładnie, gdy brzmi to tak, jak będzie.

Ostatnia rzecz do zanotowania jest używana @StateObject zamiast @ObservedObject. Jeśli nie jesteś jeszcze na iOS 14 lub MacOS 11, możesz zamiast tego użyć @observedObject. Istnieją pewne różnice poza zakresem tej odpowiedzi, ale łatwo jest to Google.

2
jnpdx 14 marzec 2021, 03:52