Try my fully functional example code that fetches the pokemons data as required.
The code gets the server response with the results
when the PokeListView
first appears (in .task {...}).
Then, as the user scrolls to the bottom of the current list, another page is fetched, until
all data is presented.
The new page fetching is triggered by checking for the last creature id displayed and if more data is available.
This is the crux of the paging. Note, you can adjust to trigger before the last creature is displayed.
As the user tap on any one of the creatures name, the details view is presented.
As the PokeDetailsView
appears, the details are fetched from the server or from cache.
This alleviates the server burden.
The ApiService
manages all server processing. With this approach
you are not fetching all the details before hand, only as required.
Since you are fetching data from a remote server, there will be times when you will see the progress view,
as it takes somethimes to download the data.
struct ContentView: View {
@StateObject var apiService = ApiService()
var body: some View {
PokeListView()
.environmentObject(apiService)
}
}
struct PokeListView: View {
@EnvironmentObject var apiService: ApiService
var body: some View {
NavigationStack {
List(apiService.pokeList.results) { pokemon in
NavigationLink(pokemon.name, value: pokemon.url)
// check if need to paginate
if let lastPoke = apiService.pokeList.results.last {
if pokemon.id == lastPoke.id && apiService.pokeList.next.hasPrefix("https") {
ProgressView()
.task {
do {
try await apiService.getPokemonList()
} catch {
print("---> refresh error: \(error)")
}
}
}
}
}
.navigationDestination(for: String.self) { urlString in
PokeDetailsView(urlString: urlString)
}
}
.environmentObject(apiService)
.task {
do {
try await apiService.getPokemonList()
} catch{
print(error)
}
}
}
}
struct PokeDetailsView: View {
@EnvironmentObject var apiService: ApiService
@State var urlString: String
@State var poky: Pokemon?
var body: some View {
VStack {
Text(poky?.name ?? "no name")
Text("height: \(poky?.height ?? 0)")
// ... other info
}
.task {
do {
poky = try await apiService.getPokemon(from: urlString)
} catch{
print(error)
}
}
}
}
class ApiService: ObservableObject {
var serverUrl = "https://pokeapi.co/api/v2/pokemon?limit=20&offset=0"
// the response from the server with the list of names and urls in `results`
@Published var pokeList: PokemonList = PokemonList(count: 0, results: [])
// dictionary store of Pokemons details [urlString:Pokemon]
@Published var pokemonStore: [String : Pokemon] = [:]
func getPokemonList() async throws {
guard let url = URL(string: serverUrl) else { return }
let (data, _) = try await URLSession.shared.data(from: url)
Task{@MainActor in
let morePoke = try JSONDecoder().decode(PokemonList.self, from: data)
self.pokeList.count = morePoke.count // <-- here
self.pokeList.next = morePoke.next
self.serverUrl = morePoke.next
self.pokeList.results.append(contentsOf: morePoke.results)
}
}
func getPokemon(from urlString: String) async throws -> Pokemon? {
if let poky = pokemonStore[urlString] {
// if already have it
return poky
} else {
// fetch it from the server
guard let url = URL(string: urlString) else { return nil }
let (data, _) = try await URLSession.shared.data(from: url)
do {
let poky = try JSONDecoder().decode(Pokemon.self, from: data)
Task{@MainActor in
// store it for later use
pokemonStore[urlString] = poky
}
return poky
} catch {
return nil
}
}
}
}
// MARK: - PokemonList
struct PokemonList: Codable {
var count: Int // <-- here
var next: String
var results: [ListItem] // <-- don't use the word Result
init(count: Int, results: [ListItem], next: String = "") {
self.count = count
self.results = results
self.next = next
}
}
// 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
}