I am trying to create a picker using an array that is built from an API call (if that's even how to word it). I can create a list just fine with the array but the Picker doesn't show up at all. Not sure what to do. Any help would be appreciated.
Below is my code. I'm new and experimenting with APIs for the first time so bear with me.
import SwiftUI
import Foundation
struct NHLAPI: View {
#State private var teams = [Team]()
#State private var teamsDict = [String: Int]()
#State private var selectedTeam = "Anaheim Ducks"
var body: some View {
VStack {
Picker("Teams", selection: $selectedTeam) {
ForEach(teams, id: \.id) { team in
Text(team.name)
}
}
List(teams, id: \.id) { team in
VStack(alignment: .leading) {
Text("\(team.name) \(Int(teamsDict[team.name] ?? 1))")
}
}
.task {
await loadData()
}
}
}
func loadData() async {
guard let url = URL(string: "https://statsapi.web.nhl.com/api/v1/teams") else {
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decodedResponse = try? JSONDecoder().decode(Welcome.self, from: data) {
teams = decodedResponse.teams
for team in teams {
teamsDict[team.name] = team.id
}
}
} catch {
return
}
}
}
// MARK: - Welcome
struct Welcome: Codable {
let copyright: String
let teams: [Team]
}
// MARK: - Team
struct Team: Codable {
let id: Int
let name, link: String
let venue: Venue
let abbreviation, teamName, locationName, firstYearOfPlay: String
let division, conference: Conference
let franchise: Franchise
let shortName: String
let officialSiteURL: String
let franchiseID: Int
let active: Bool
enum CodingKeys: String, CodingKey {
case id, name, link, venue, abbreviation, teamName, locationName, firstYearOfPlay, division, conference, franchise, shortName
case officialSiteURL = "officialSiteUrl"
case franchiseID = "franchiseId"
case active
}
}
// MARK: - Conference
struct Conference: Codable {
let id: Int
let name, link: String
}
// MARK: - Franchise
struct Franchise: Codable {
let franchiseID: Int
let teamName, link: String
enum CodingKeys: String, CodingKey {
case franchiseID = "franchiseId"
case teamName, link
}
}
// MARK: - Venue
struct Venue: Codable {
let name, link, city: String
let timeZone: TimeZone
}
// MARK: - TimeZone
struct TimeZone: Codable {
let id: String
let offset: Int
let tz: String
}
struct NHLAPI_Previews: PreviewProvider {
static var previews: some View {
NHLAPI()
}
}
You could try the following approach. I included Hashable to most models and
Identifiable to Team. Used selectedTeam: Team? and a matching tag in the Picker.
struct NHLAPI: View {
#State private var teams = [Team]()
#State private var teamsDict = [String: Int]()
#State private var selectedTeam: Team? // <-- here
var body: some View {
VStack {
Picker("Teams", selection: $selectedTeam) {
ForEach(teams) { team in // <-- here
Text(team.name)
.tag(team as Team?) // <-- here
}
}.padding(20)
List(teams) { team in
VStack(alignment: .leading) {
Text("\(team.name) \(Int(teamsDict[team.name] ?? 1))")
}
}
.task {
await loadData()
}
}
}
func loadData() async {
guard let url = URL(string: "https://statsapi.web.nhl.com/api/v1/teams") else {
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decodedResponse = try? JSONDecoder().decode(Welcome.self, from: data) {
teams = decodedResponse.teams
for team in teams {
teamsDict[team.name] = team.id
}
// -- here
if let teem = teams.first(where: {$0.name == "Anaheim Ducks"}) {
selectedTeam = teem
} else {
selectedTeam = teams.first
}
}
} catch {
return
}
}
}
// MARK: - Welcome
struct Welcome: Codable {
let copyright: String
let teams: [Team]
}
// MARK: - Team
struct Team: Identifiable, Hashable, Codable { // <-- here
let id: Int
let name, link: String
let venue: Venue
let abbreviation, teamName, locationName, firstYearOfPlay: String
let division, conference: Conference
let franchise: Franchise
let shortName: String
let officialSiteURL: String
let franchiseID: Int
let active: Bool
enum CodingKeys: String, CodingKey {
case id, name, link, venue, abbreviation, teamName, locationName, firstYearOfPlay, division, conference, franchise, shortName
case officialSiteURL = "officialSiteUrl"
case franchiseID = "franchiseId"
case active
}
}
// MARK: - Conference
struct Conference: Hashable, Codable { // <-- here
let id: Int
let name, link: String
}
// MARK: - Franchise
struct Franchise: Hashable, Codable { // <-- here
let franchiseID: Int
let teamName, link: String
enum CodingKeys: String, CodingKey {
case franchiseID = "franchiseId"
case teamName, link
}
}
// MARK: - Venue
struct Venue: Hashable, Codable { // <-- here
let name, link, city: String
let timeZone: TimeZone
}
// MARK: - TimeZone
struct TimeZone: Hashable, Codable { // <-- here
let id: String
let offset: Int
let tz: String
}
You can also select a different Picker style, or wrap it in a Form, such as
Form {
Picker(...)
}.frame(height: 123)
Related
I'm having a hard time accessing data from my JSONDecoder. I see that I am definitely getting a result, but I keep getting an indexOutOfRange error when trying to access the first index.
Model:
struct Movie: Codable {
let page: Int
let results: [Result]
let totalPages, totalResults: Int
enum CodingKeys: String, CodingKey {
case page, results
case totalPages = "total_pages"
case totalResults = "total_results"
}
}
// MARK: - Result
struct Result: Codable, Identifiable {
let adult: Bool
let backdropPath: String
let id: Int
let title: String?
let originalLanguage: String
let originalTitle: String?
let overview, posterPath: String
let mediaType: MediaType
let genreIDS: [Int]
let popularity: Double
let releaseDate: String?
let video: Bool?
let voteAverage: Double
let voteCount: Int
let name, originalName, firstAirDate: String?
let originCountry: [String]?
enum CodingKeys: String, CodingKey {
case adult
case backdropPath = "backdrop_path"
case id, title
case originalLanguage = "original_language"
case originalTitle = "original_title"
case overview
case posterPath = "poster_path"
case mediaType = "media_type"
case genreIDS = "genre_ids"
case popularity
case releaseDate = "release_date"
case video
case voteAverage = "vote_average"
case voteCount = "vote_count"
case name
case originalName = "original_name"
case firstAirDate = "first_air_date"
case originCountry = "origin_country"
}
}
enum MediaType: String, Codable {
case movie = "movie"
case tv = "tv"
}
Main View
struct MainView: View {
#State var movieList: [Movie] = []
#StateObject var network = myNetwork() // <-- for testing
var sampleText = "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout...."
var body: some View {
VStack {
GeometryReader{ proxy in
TabView{
ForEach(0..<5){ num in
VStack {
Button(action: {}) {
Image("Image")
.frame(width: 300, height: 200)
.scaledToFit()
.clipped()
}
Text("\(movieList[0].results)" as String)
.lineLimit(nil)
.foregroundColor(.white)
.multilineTextAlignment(.leading)
.padding()
.frame(width: 300, height: 200)
.background(Rectangle().fill(Color("Grey")).shadow(radius: 1))
}
}
}.tabViewStyle(PageTabViewStyle())
.padding()
.frame(width: proxy.size.width, height: proxy.size.height)
}
.onAppear {
network.getMovies(onCompletion: { (fetchedCity: Movie) in
DispatchQueue.main.async {
self.movieList = [fetchedCity]
}
})
}
}
.padding()
}
}
API:
class movieAPI:ObservableObject{
func getMovies(onCompletion: #escaping ([Movie]) -> ()){
let apiKey = "bb062ca1a1471913abf6b680168fc5ca"
let url = URL(string: "https://api.themoviedb.org/3/trending/all/day?api_key=\(apiKey)")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
let session = URLSession.shared
let dataTask = session.dataTask(with: request, completionHandler: { (data1, response1, error1) -> Void in
guard let data = data1 else {
return
}
do {
let results = try JSONDecoder().decode([Movie].self, from: data)
DispatchQueue.main.async {
let tmp = results[0].results
print("hotel model \(tmp)")
}
onCompletion(results)
} catch {
print("Couldn't decode hotel json \(error.localizedDescription)")
}
})
dataTask.resume()
}
}
Results:
Text("\(movieList[0].results)" as String)
Result: Thread 1: Fatal error: Index out of range
Text("\(movieList.description)")
Result: Movie(page: 1, results: [SliderExample.Result(adult: false, backdropPath: "/rfnmMYuZ6EKOBvQLp2wqP21v7sI.jpg", id: 774752, title: Optional("The Guardians of the Galaxy Holiday Special"), originalLanguage: "en", originalTitle: Optional("The Guardians of the Galaxy Holiday Special"), overview: "On a mission to make Christmas unforgettable for Quill, the Guardians head to Earth in search of the perfect present.", posterPath: "/8dqXyslZ2hv49Oiob9UjlGSHSTR.jpg", mediaType: SliderExample.MediaType.movie, genreIDS: [35, 878, 12], popularity: 539.588, releaseDate: Optional("2022-11-25"), video: Optional(false), voteAverage: 7.521, voteCount: 382, name: nil, originalName: nil, firstAirDate: nil, originCountry: nil)
My goal is to access the overview information.
I'm currently trying to utilize the NewsApi api to allow me to retrieve certain articles. I currently do have a model for which I generated
import Foundation
// MARK: - Welcome
struct NewsResponse: Codable {
let status: String
let totalResults: Int
let articles: [Article]
enum CodingKeys: String, CodingKey {
case status = "status"
case totalResults = "totalResults"
case articles = "articles"
}
}
// MARK: - Article
struct Article: Codable, Identifiable {
let id = UUID()
let source: Source
let author: String?
let title: String
let articleDescription: String
let url: String
let urlToImage: String?
let publishedAt: Date
let content: String
enum CodingKeys: String, CodingKey {
case source = "source"
case author = "author"
case title = "title"
case articleDescription = "description"
case url = "url"
case urlToImage = "urlToImage"
case publishedAt = "publishedAt"
case content = "content"
}
}
// MARK: - Source
struct Source: Codable {
let id: String?
let name: String
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
}
}
I also do have my Webservice that decodes the API link which is at let pokeNews = URL(string:"https://newsapi.org/v2/everything?q=pokemon&apiKey=*APIKey*")!
The webservice code is here and I had used a Mohammed Azam tutorial for that: https://www.youtube.com/watch?v=YOCZuZz4vAw
import Foundation
class NewsWebService: ObservableObject{
#Published var news: [Article] = []
func getNews() async throws{
let (data, _) = try await URLSession.shared.data(from: Constants.url.pokeNews)
Task{#MainActor in
self.news = try JSONDecoder().decode([Article].self, from: data)
}
}
}
Now the main issue I'm having is that I'm at a loss of understanding at how I would access the articles array from the NewsResponse model and retrieve the article.title, etc. I've attempted it multiple times, but am not quite sure how to proceed. I've looked at other tutorials, but I feel that I'm still not gaining a sense of understanding at how to effectively call apis in swift. The code in which I'm attempting to call the API within is here:
import SwiftUI
struct NewsListView: View {
#EnvironmentObject var newsWebService: NewsWebService
var body: some View {
List(newsWebService.news){ article in
Text(article.title)
}
HStack(alignment: .center, spacing: 16, content: {
//Article Image
Image("yak0")
.resizable()
.scaledToFill()
.frame(width: 50, height: 50)
.clipShape(RoundedRectangle(cornerRadius: 12))
//Article Title
VStack(alignment: .leading, spacing: 8){
Text("Massive News Surrounding Pokemon")
.font(.title2)
.fontWeight(.heavy)
.foregroundColor(.red)
//Article Source
Text("CNN").font(.footnote)
.fontWeight(.bold)
.multilineTextAlignment(.leading)
.foregroundColor(.yellow)
}
}).onTapGesture {
//go to webview
}
}
}
struct NewsListView_Previews: PreviewProvider {
static var previews: some View {
NewsListView()
.previewLayout(.sizeThatFits)
.padding()
.environmentObject(NewsWebService())
}
}
Any help would be appreciated. Thank you so much!
Edited for NewsResponse portion
import Foundation
class NewsWebService: ObservableObject{
#Published var news: NewsResponse?
func getNews() async throws{
let (data, _) = try await URLSession.shared.data(from: Constants.url.pokeNews)
Task{#MainActor in
self.news = try JSONDecoder().decode(NewsResponse.self, from: data)
}
}
}
The Line showing the error: Initializer 'init(_:rowContent:)' requires that 'NewsResponse' conform to 'RandomAccessCollection'
List(newsWebService.news!){ article in
Text(article.title)
}
try this example code (note the mods in Article), works well for me. Note, do not show your secret api key, remove it:
struct ContentView: View {
#StateObject var newsWebService = NewsWebService()
var body: some View {
NewsListView().environmentObject(newsWebService)
}
}
struct NewsListView: View {
#EnvironmentObject var newsWebService: NewsWebService
var body: some View {
List(newsWebService.news?.articles ?? []){ article in // <-- here
Text(article.title)
}
.task {
do{
try await newsWebService.getNews()
} catch{
print("---> task error: \(error)")
}
}
}
}
class NewsWebService: ObservableObject{
#Published var news: NewsResponse?
func getNews() async throws {
let (data, _) = try await URLSession.shared.data(from: Constants.url.pokeNews)
Task{#MainActor in
self.news = try JSONDecoder().decode(NewsResponse.self, from: data)
}
}
}
struct NewsResponse: Codable {
let status: String
let totalResults: Int
let articles: [Article]
}
struct Article: Codable, Identifiable {
let id = UUID()
let source: Source
let author: String?
let title, articleDescription: String
let url: String
let urlToImage: String?
let publishedAt: String? // <-- here
let content: String
enum CodingKeys: String, CodingKey {
case source, author, title
case articleDescription = "description"
case url, urlToImage, publishedAt, content
}
}
struct Source: Codable {
let id: String?
let name: String
}
This is assuming Constants.url.pokeNews is the url for "https://newsapi.org/v2/everything?q=pokemon&apiKey=YOURKEY"
EDIT-1:
If you want to use Article, let publishedAt: Date?, instead of String?, then use:
func getNews() async throws {
let (data, _) = try await URLSession.shared.data(from: Constants.url.pokeNews)
Task{#MainActor in
let decoder = JSONDecoder() // <-- here
decoder.dateDecodingStrategy = .iso8601 // <-- here
self.news = try decoder.decode(NewsResponse.self, from: data)
}
}
I'm currently having trouble with utilizing the PokeApi. I have code that allows me to view a pokemon's name and URL to the other JSON for a pokemon, but I'm not quite sure how I would retrieve that data. Here is what I have so far. And here is the link to the api! let pokeList = https://pokeapi.co/api/v2/pokemon?limit=100000&offset=0
import Foundation
// MARK: - Pokemon
struct PokemonList: Codable {
let count: Int
let results: [Result]
}
// MARK: - Result
struct Result: Codable, Identifiable {
let id = UUID()
let name: String
let url: String
enum CodingKeys: String, CodingKey {
case name, url
}
}
// MARK: - Pokemon
struct Pokemon: Codable, Identifiable {
let abilities: [Ability]
let baseExperience: Int
let forms: [Species]
let gameIndices: [GameIndex]
let height: Int
let id: Int
let isDefault: Bool
let locationAreaEncounters: String
let moves: [Move]
let name: String
let order: Int
let species: Species
let sprites: Sprites
let stats: [Stat]
let types: [TypeElement]
let weight: Int
}
// MARK: - Ability
struct Ability: Codable {
let ability: Species
let isHidden: Bool
let slot: Int
}
// MARK: - Species
struct Species: Codable {
let name: String
let url: String
}
// MARK: - GameIndex
struct GameIndex: Codable{
let gameIndex: Int
let version: Species
}
// MARK: - Move
struct Move: Codable {
let move: Species
}
// MARK: - Sprites
struct Sprites: Codable {
let backDefault: String
let backFemale: String
let backShiny: String
let backShinyFemale: String
let frontDefault: String
let frontFemale: String
let frontShiny: String
let frontShinyFemale: String
}
// MARK: - Home
struct Home: Codable {
let frontDefault: String
let frontFemale: String
let frontShiny: String
let frontShinyFemale: String
}
// MARK: - Other
struct Other: Codable {
let home: Home
let officialArtwork: OfficialArtwork
}
// MARK: - OfficialArtwork
struct OfficialArtwork: Codable {
let frontDefault: String
}
// MARK: - Stat
struct Stat: Codable {
let baseStat: Int
let effort: Int
let stat: Species
}
// MARK: - TypeElement
struct TypeElement: Codable {
let slot: Int
let type: Species
}
Code for Webservice to retrieve API Call
import Foundation
class PokeWebService: ObservableObject{
#Published var pokeList: PokemonList?
#Published var pokemonIndvl: Pokemon?
func getPokemonList() async throws {
let (data, _) = try await URLSession.shared.data(from: Constants.url.pokeList)
Task{#MainActor in
self.pokeList = try JSONDecoder().decode(PokemonList.self, from: data)
}
}
func getPokemonFromPokemonList(invlURL: String) async throws{
var plURL: URL = URL(string: invlURL)!
let (data, _) = try await URLSession.shared.data(from: plURL)
Task{#MainActor in
self.pokemonIndvl = try JSONDecoder().decode(Pokemon.self, from: data)
}
}
}
My Content View
import SwiftUI
struct PokeListView: View {
#EnvironmentObject var pokeWebService: PokeWebService
var body: some View {
List(pokeWebService.pokeList?.results ?? []){ pokemon in // <-- here
Text(pokemon.name)
Text(pokemon.url)
}
.task {
do{
try await pokeWebService.getPokemonList()
} catch{
print("---> task error: \(error)")
}
}
}
}
struct PokeListView_Previews: PreviewProvider {
static var previews: some View {
PokeListView()
.environmentObject(PokeWebService())
}
}
Since I'm still fairly new to working with APIs in Swift, how would I retrieve the data from the Pokemon.url?
Thank you!
try something like this approach (note the different model structs). You will need to find from the server docs, which fields are optional and adjust the various structs:
import Foundation
import SwiftUI
struct ContentView: View {
#StateObject var pokeWebService = PokeWebService()
var body: some View {
PokeListView().environmentObject(pokeWebService)
}
}
struct PokeListView: View {
#EnvironmentObject var pokeWebService: PokeWebService
var body: some View {
NavigationStack {
List(pokeWebService.pokeList?.results ?? []){ pokemon in
NavigationLink(pokemon.name, value: pokemon.url)
}
.navigationDestination(for: String.self) { urlString in
PokeDetailsView(urlString: urlString)
}
}
.environmentObject(pokeWebService)
.task {
do {
try await pokeWebService.getPokemonList()
} catch{
print("---> PokeListView error: \(error)")
}
}
}
}
struct PokeDetailsView: View {
#EnvironmentObject var pokeWebService: PokeWebService
#State var urlString: String
var body: some View {
VStack {
Text(pokeWebService.pokemonIndvl?.name ?? "no name")
Text("height: \(pokeWebService.pokemonIndvl?.height ?? 0)")
// ... other info
}
.task {
do {
try await pokeWebService.getPokemon(from: urlString)
} catch{
print("---> PokeDetailsView error: \(error)")
}
}
}
}
class PokeWebService: ObservableObject{
#Published var pokeList: PokemonList?
#Published var pokemonIndvl: Pokemon?
func getPokemonList() async throws {
let (data, _) = try await URLSession.shared.data(from: Constants.url.pokeList)
Task{#MainActor in
self.pokeList = try JSONDecoder().decode(PokemonList.self, from: data)
}
}
func getPokemon(from urlString: String) async throws {
if let url = URL(string: urlString) {
let (data, _) = try await URLSession.shared.data(from: url)
Task{#MainActor in
self.pokemonIndvl = try JSONDecoder().decode(Pokemon.self, from: data)
}
}
}
}
// using https://app.quicktype.io/
// MARK: - PokemonList
struct PokemonList: Codable {
let count: Int
let results: [ListItem] // <-- don't use the word Result
}
// MARK: - ListItem
struct ListItem: Codable, Identifiable {
let id = UUID()
let name: String
let url: String
enum CodingKeys: String, CodingKey {
case name, url
}
}
struct HeldItem: Codable {
let item: Species
let versionDetails: [VersionDetail]
enum CodingKeys: String, CodingKey {
case item
case versionDetails = "version_details"
}
}
struct VersionDetail: Codable {
let rarity: Int
let version: Species
}
// MARK: - Pokemon
struct Pokemon: Codable, Identifiable {
let abilities: [Ability]
let baseExperience: Int
let forms: [Species]
let gameIndices: [GameIndex]
let height: Int
let heldItems: [HeldItem]
let id: Int
let isDefault: Bool
let locationAreaEncounters: String
let moves: [Move]
let name: String
let order: Int
let pastTypes: [String]
let species: Species
let sprites: Sprites
let stats: [Stat]
let types: [TypeElement]
let weight: Int
enum CodingKeys: String, CodingKey {
case abilities
case baseExperience = "base_experience"
case forms
case gameIndices = "game_indices"
case height
case heldItems = "held_items"
case id
case isDefault = "is_default"
case locationAreaEncounters = "location_area_encounters"
case moves, name, order
case pastTypes = "past_types"
case species, sprites, stats, types, weight
}
}
// MARK: - Ability
struct Ability: Codable {
let ability: Species
let isHidden: Bool
let slot: Int
enum CodingKeys: String, CodingKey {
case ability
case isHidden = "is_hidden"
case slot
}
}
// MARK: - Species
struct Species: Codable {
let name: String
let url: String
}
// MARK: - GameIndex
struct GameIndex: Codable {
let gameIndex: Int
let version: Species
enum CodingKeys: String, CodingKey {
case gameIndex = "game_index"
case version
}
}
// MARK: - Move
struct Move: Codable {
let move: Species
let versionGroupDetails: [VersionGroupDetail]
enum CodingKeys: String, CodingKey {
case move
case versionGroupDetails = "version_group_details"
}
}
// MARK: - VersionGroupDetail
struct VersionGroupDetail: Codable {
let levelLearnedAt: Int
let moveLearnMethod, versionGroup: Species
enum CodingKeys: String, CodingKey {
case levelLearnedAt = "level_learned_at"
case moveLearnMethod = "move_learn_method"
case versionGroup = "version_group"
}
}
// MARK: - GenerationV
struct GenerationV: Codable {
let blackWhite: Sprites
enum CodingKeys: String, CodingKey {
case blackWhite = "black-white"
}
}
// MARK: - GenerationIv
struct GenerationIv: Codable {
let diamondPearl, heartgoldSoulsilver, platinum: Sprites
enum CodingKeys: String, CodingKey {
case diamondPearl = "diamond-pearl"
case heartgoldSoulsilver = "heartgold-soulsilver"
case platinum
}
}
// MARK: - Versions
struct Versions: Codable {
let generationI: GenerationI
let generationIi: GenerationIi
let generationIii: GenerationIii
let generationIv: GenerationIv
let generationV: GenerationV
let generationVi: [String: Home]
let generationVii: GenerationVii
let generationViii: GenerationViii
enum CodingKeys: String, CodingKey {
case generationI = "generation-i"
case generationIi = "generation-ii"
case generationIii = "generation-iii"
case generationIv = "generation-iv"
case generationV = "generation-v"
case generationVi = "generation-vi"
case generationVii = "generation-vii"
case generationViii = "generation-viii"
}
}
// MARK: - Sprites
class Sprites: Codable {
let backDefault: String
let backFemale: String?
let backShiny: String
let backShinyFemale: String?
let frontDefault: String
let frontFemale: String?
let frontShiny: String
let frontShinyFemale: String?
let other: Other?
let versions: Versions?
let animated: Sprites?
enum CodingKeys: String, CodingKey {
case backDefault = "back_default"
case backFemale = "back_female"
case backShiny = "back_shiny"
case backShinyFemale = "back_shiny_female"
case frontDefault = "front_default"
case frontFemale = "front_female"
case frontShiny = "front_shiny"
case frontShinyFemale = "front_shiny_female"
case other, versions, animated
}
}
// MARK: - GenerationI
struct GenerationI: Codable {
let redBlue, yellow: RedBlue
enum CodingKeys: String, CodingKey {
case redBlue = "red-blue"
case yellow
}
}
// MARK: - RedBlue
struct RedBlue: Codable {
let backDefault, backGray, backTransparent, frontDefault: String
let frontGray, frontTransparent: String
enum CodingKeys: String, CodingKey {
case backDefault = "back_default"
case backGray = "back_gray"
case backTransparent = "back_transparent"
case frontDefault = "front_default"
case frontGray = "front_gray"
case frontTransparent = "front_transparent"
}
}
// MARK: - GenerationIi
struct GenerationIi: Codable {
let crystal: Crystal
let gold, silver: Gold
}
// MARK: - Crystal
struct Crystal: Codable {
let backDefault, backShiny, backShinyTransparent, backTransparent: String
let frontDefault, frontShiny, frontShinyTransparent, frontTransparent: String
enum CodingKeys: String, CodingKey {
case backDefault = "back_default"
case backShiny = "back_shiny"
case backShinyTransparent = "back_shiny_transparent"
case backTransparent = "back_transparent"
case frontDefault = "front_default"
case frontShiny = "front_shiny"
case frontShinyTransparent = "front_shiny_transparent"
case frontTransparent = "front_transparent"
}
}
// MARK: - Gold
struct Gold: Codable {
let backDefault, backShiny, frontDefault, frontShiny: String
let frontTransparent: String?
enum CodingKeys: String, CodingKey {
case backDefault = "back_default"
case backShiny = "back_shiny"
case frontDefault = "front_default"
case frontShiny = "front_shiny"
case frontTransparent = "front_transparent"
}
}
// MARK: - GenerationIii
struct GenerationIii: Codable {
let emerald: Emerald
let fireredLeafgreen, rubySapphire: Gold
enum CodingKeys: String, CodingKey {
case emerald
case fireredLeafgreen = "firered-leafgreen"
case rubySapphire = "ruby-sapphire"
}
}
// MARK: - Emerald
struct Emerald: Codable {
let frontDefault, frontShiny: String
enum CodingKeys: String, CodingKey {
case frontDefault = "front_default"
case frontShiny = "front_shiny"
}
}
// MARK: - Home
struct Home: Codable {
let frontDefault: String
let frontFemale: String?
let frontShiny: String
let frontShinyFemale: String?
enum CodingKeys: String, CodingKey {
case frontDefault = "front_default"
case frontFemale = "front_female"
case frontShiny = "front_shiny"
case frontShinyFemale = "front_shiny_female"
}
}
// MARK: - GenerationVii
struct GenerationVii: Codable {
let icons: DreamWorld
let ultraSunUltraMoon: Home
enum CodingKeys: String, CodingKey {
case icons
case ultraSunUltraMoon = "ultra-sun-ultra-moon"
}
}
// MARK: - DreamWorld
struct DreamWorld: Codable {
let frontDefault: String
let frontFemale: String?
enum CodingKeys: String, CodingKey {
case frontDefault = "front_default"
case frontFemale = "front_female"
}
}
// MARK: - GenerationViii
struct GenerationViii: Codable {
let icons: DreamWorld
}
// MARK: - Other
struct Other: Codable {
let dreamWorld: DreamWorld
let home: Home
let officialArtwork: OfficialArtwork
enum CodingKeys: String, CodingKey {
case dreamWorld = "dream_world"
case home
case officialArtwork = "official-artwork"
}
}
// MARK: - OfficialArtwork
struct OfficialArtwork: Codable {
let frontDefault: String
enum CodingKeys: String, CodingKey {
case frontDefault = "front_default"
}
}
// MARK: - Stat
struct Stat: Codable {
let baseStat, effort: Int
let stat: Species
enum CodingKeys: String, CodingKey {
case baseStat = "base_stat"
case effort, stat
}
}
// MARK: - TypeElement
struct TypeElement: Codable {
let slot: Int
let type: Species
}
EDIT-1:
if you have problems with the NavigationStack, use this NavigationView instead.
NavigationView {
List(pokeWebService.pokeList?.results ?? []){ pokemon in
NavigationLink(destination: PokeDetailsView(urlString: pokemon.url)) {
Text(pokemon.name)
}
}
}
When I try to present the results I received this message "Response could not be decoded because of error:
The data couldn’t be read because it isn’t in the correct format."
This is my format and its right I think.
import Foundation
// MARK: - Response
struct Response: Codable {
let code: Int
let meta: Meta
let data: [Datum]
}
// MARK: - Datum
struct Datum: Codable {
let id, userID: Int
let title, body: String
enum CodingKeys: String, CodingKey {
case id
case userID = "user_id"
case title, body
}
}
// MARK: - Meta
struct Meta: Codable {
let pagination: Pagination
}
// MARK: - Pagination
struct Pagination: Codable {
let total, pages, page, limit: Int
}
also I try with this code to view the result.
private func fetchData() {
self.task = AF.request(self.baseUrl, method: .get, parameters: nil)
.publishDecodable(type: [Response].self)
.sink(receiveCompletion: {(completion) in
switch completion {
case .finished:
()
case .failure(let error):
print(String(describing: error))
//print(error.localizedDescription)
}
}, receiveValue: {[weak self ](response) in
switch response.result {
case .success(let model): self?.presenters = model.map {PostPresenter(with: $0)}
case.failure(let error):
print(String(describing: error))
// print(error.localizedDescription)
}
})
}
And my post presenter code is this
struct PostPresenter: Identifiable {
let id = UUID()
let title: String
init(with model:Response) {
self.title = model.data
}
}
Two mistakes
The root object is a dictionary so it's (type: Response.self)
and model.data is [Datum] so declare
struct PostPresenter: Identifiable {
let id = UUID()
let data: [Datum]
init(with response: Response) {
self.data = response.data
}
}
And in a Codable context print the error always print(error), not print(String(describing: error)) and never print(error.localizedDescription)
My right Post presenter
struct PostPresenter: Identifiable {
let id = UUID()
let data: [Datum]
init(with response: Response<[Datum]>) {
self.data = response.data
}
init() {
data = []
}
}
And my finally format as I right.
struct Response<T: Decodable>: Decodable {
let code: Int
let meta: Meta
let data: T
}
// MARK: - Datum
struct Datum: Decodable {
let id, userID: Int
let title, body: String
enum CodingKeys: String, CodingKey {
case id
case userID = "user_id"
case title, body
}
}
// MARK: - Meta
struct Meta: Decodable {
let pagination: Pagination
}
// MARK: - Pagination
struct Pagination: Decodable {
let total, pages, page, limit: Int
}
And finally my homeview and how I present
struct HomeView: View {
#ObservedObject var viewModel = HomeAPIViewModel()
var body: some View {
ZStack {
Color(.white)
// Present the API Here
List(self.viewModel.presenter.data, id: \.id) {
Text($0.title)
}
.padding()
}
}
}
I want to load an online json file into my application, but I am running into this error:
typeMismatch(Swift.Array,
Swift.DecodingError.Context(codingPath: [], debugDescription:
"Expected to decode Array but found a dictionary instead.",
underlyingError: nil))
I have looked on stackoverflow but other sollutions didn't help to solve mine.
My JSON:
{
"copyright" : "NHL and the NHL Shield are registered trademarks of the National Hockey League. NHL and NHL team marks are the property of the NHL and its teams. © NHL 2022. All Rights Reserved.",
"totalItems" : 0,
"totalEvents" : 0,
"totalGames" : 0,
"totalMatches" : 0,
"metaData" : {
"timeStamp" : "20220813_172145"
},
"wait" : 10,
"dates" : [ ]
}
My datamodel:
import Foundation
struct Initial: Codable {
let copyright: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let wait: Int
let dates: [Dates]
}
struct Dates: Codable {
let date: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let games: [Game]
}
struct Game: Codable {
let gamePk: Int
let link: String
let gameType: String
let season: String
let gameDate: String
let status: Status
let teams: Team
let venue: Venue
let content: Content
}
struct Status: Codable {
let abstractGameState: String
let codedGameState: Int
let detailedState: String
let statusCode: Int
let startTimeTBD: Bool
}
struct Team: Codable {
let away: Away
let home: Home
}
struct Away: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct Home: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct LeagueRecord: Codable {
let wins: Int
let losses: Int
let type: String
}
struct TeamInfo: Codable {
let id: Int
let name: String
let link: String
}
struct Venue: Codable {
let name: String
let link: String
}
struct Content: Codable {
let link: String
}
and here is my viewcontroller
import UIKit
class TodaysGamesTableViewController: UITableViewController {
var todaysGamesURL: URL = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule")!
var gameData: [Dates] = []
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
override func viewDidLoad() {
super.viewDidLoad()
loadTodaysGames()
}
func loadTodaysGames(){
print("load Games")
view.addSubview(activityIndicator)
activityIndicator.frame = view.bounds
activityIndicator.startAnimating()
let todaysGamesDatatask = URLSession.shared.dataTask(with: todaysGamesURL, completionHandler: dataLoaded)
todaysGamesDatatask.resume()
}
func dataLoaded(data:Data?,response:URLResponse?,error:Error?){
if let detailData = data{
print("detaildata", detailData)
let decoder = JSONDecoder()
do {
let jsondata = try decoder.decode([Dates].self, from: detailData)
gameData = jsondata //Hier .instantie wil doen krijg ik ook een error
DispatchQueue.main.async{
self.tableView.reloadData()
}
}catch let error{
print(error)
}
}else{
print(error!)
}
}
Please learn to understand the decoding error messages, they are very descriptive.
The error says you are going to decode an array but the actual object is a dictionary (the target struct).
First take a look at the beginning of the JSON
{
"copyright" : "NHL and the NHL Shield are registered trademarks of the National Hockey League. NHL and NHL team marks are the property of the NHL and its teams. © NHL 2018. All Rights Reserved.",
"totalItems" : 2,
"totalEvents" : 0,
"totalGames" : 2,
"totalMatches" : 0,
"wait" : 10,
"dates" : [ {
"date" : "2018-05-04",
It starts with a { which is a dictionary (an array is [) but you want to decode an array ([Dates]), that's the type mismatch the error message is referring to.
But this is only half the solution. After changing the line to try decoder.decode(Dates.self you will get another error that there is no value for key copyright.
Look again at the JSON and compare the keys with the struct members. The struct whose members match the JSON keys is Initial and you have to get the dates array to populate gameData.
let jsondata = try decoder.decode(Initial.self, from: detailData)
gameData = jsondata.dates
The JSON is represented by your Initial struct, not an array of Dates.
Change:
let jsondata = try decoder.decode([Dates].self, from: detailData)
to:
let jsondata = try decoder.decode(Initial.self, from: detailData)
Correct Answer is done previously from my two friends
but you have to do it better i will provide solution for you to make code more clean and will give you array of Dates
here is your model with codable
import Foundation
struct Initial: Codable {
let copyright: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let wait: Int
let dates: [Dates]
}
struct Dates: Codable {
let date: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let games: [Game]
}
struct Game: Codable {
let gamePk: Int
let link: String
let gameType: String
let season: String
let gameDate: String
let status: Status
let teams: Team
let venue: Venue
let content: Content
}
struct Status: Codable {
let abstractGameState: String
let codedGameState: Int
let detailedState: String
let statusCode: Int
let startTimeTBD: Bool
}
struct Team: Codable {
let away: Away
let home: Home
}
struct Away: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct Home: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct LeagueRecord: Codable {
let wins: Int
let losses: Int
let type: String
}
struct TeamInfo: Codable {
let id: Int
let name: String
let link: String
}
struct Venue: Codable {
let name: String
let link: String
}
struct Content: Codable {
let link: String
}
// MARK: Convenience initializers
extension Initial {
init(data: Data) throws {
self = try JSONDecoder().decode(Initial.self, from: data)
}
}
And Here is Controller Will make solution more easy
class ViewController: UIViewController {
var todaysGamesURL: URL = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule")!
var gameData: Initial?
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
override func viewDidLoad() {
super.viewDidLoad()
self.loadTodaysGames()
}
func loadTodaysGames(){
print("load Games")
let todaysGamesDatatask = URLSession.shared.dataTask(with: todaysGamesURL, completionHandler: dataLoaded)
todaysGamesDatatask.resume()
}
func dataLoaded(data:Data?,response:URLResponse?,error:Error?){
if let detailData = data {
if let inital = try? Initial.init(data: detailData){
print(inital.dates)
}else{
// print("Initial")
}
}else{
print(error!)
}
}
}