Использование DeepSeek при создании iOS приложения Countries Страны Мира. Часть 1. Список стран.

Я решила создать довольно простое тестовое iOS приложение Countries, которое показывает все страны Мира по регионам (Европа, Азия, Латинская Америка и т.д.) и для каждой страны её название и флаг, a также численность населения population и размер ВВП (валового внутреннего продукта) gdp:

Код находится на Github.

Создание iOS приложения Countries для отображения стран Мира 

Давайте попросим DeepSeek создать полноценного iOS приложения Countries для отображения стран Мира по категориям с использованием SwiftUI и данных Всемирного Банка. 

Создай iOS приложение, которое показывает страны Мира по категориям.
Используй информацию Всемирного Банка.

Мы использовали режимы “DeepThinkingn (R1)” и «Search», который обещает применять логические выводы для получения результата:

Рассуждения 1

В результате мы получаем рассуждения, которые длятся 184 секунд (более 3 минут):

И вот их перевод:

Хорошо, мне нужно создать  iOS приложение с помощью SwiftUI, которое отображает страны мира, классифицированные с помощью API Всемирного банка. Позвольте мне разбить мои действия на шаги.
Сначала я должен выяснить, какие endpoints API Всемирного банка использовать. Пользователь упомянул категории, так что, может быть, API индикаторов indicator? Позвольте мне проверить. У Всемирного банка есть API стран countries, которое может получать список стран с информацией о регионах region, в которых они находятся. O, верно, регионы region могут быть категориями. Например, страны могут быть сгруппированы по регионам, таким как Европа, Азия и т. д.
Итак, план такой: получить список стран countries, у каждой страны country есть регион region, а затем сгруппировать их по регионам. Затем отобразить эти группы в приложении.
Далее мне нужно создать Модель данные. API Всемирного банка возвращает страны country с такими свойствами, как идентификатор id,  имя name, регион region и т. д. Я создам структуру struct Country для страны, которая включает эти поля. Затем, возможно, структуру struct WorldBankResponse для API ответа response, который содержит массив стран [Country].

Подождите, структура struct WorldBankResponse API ответа response, может иметь другой формат. Позвольте мне напомнить: API Всемирного банка обычно “оборачивает” данные в массив JSON, где каждый элемент является объектом. Например, endpoints для стран — это что-то вроде http://api.worldbank.org/v2/country?format=json. Давайте проверим структуру. Ответ response может содержать информацию о страницах page в первом элементе и фактические данные во втором. Поэтому в коде мне нужно будет это обработать.
Итак, структура JSON — это массив, где первый элемент — это метаданные metadata (например, страница page, страницы pages и т. д.), а второй элемент — массив объектов стран [Country]. У каждой страны Country есть такие свойства, как «id», «iso2Code», «name», «region» (последний также является другим объектом с «id», «value») и т. д.

Следовательно, структура struct Country должна иметь id, name, iso2code и region. Структура struct Region будет иметь id и value. Затем при декодировании мне нужно правильно распарсить регион region: Region.

Теперь, что касается сетевой части. Я создам ObservableObject класс class, возможно, с именем CountryModel, который выбирает данные с помощью API. Он будет использовать URLSession для выполнения запроса. URL-адрес "http://api.worldbank.org/v2/country?format=json&per_page=300" для получения всех стран в одном запросе (per_page=300, чтобы избежать проблем с разбиением на страницы).
Но подождите, API Всемирного банка допускает per_page=300? Мне следует проверить. Возможно, максимум — 1000, так что 300 должно быть в порядке. Но, возможно, стран больше. Давайте посмотрим, общее количество стран составляет около 200 с чем-то, так что per_page=300 безопасно.

В CountryModel у меня будет @Published свойство countries для стран, сгруппированных по регионам region. Возможно, словарь Dictionary, где ключом key будет название региона region.value, а значением — массив стран [Country]. Затем в представлении SwiftUI View я могу отобразить Sections для каждого региона region.

Таким образом, CountryModel выбирает данные data,  парсит (декодирует) их, а затем группирует страны countries по значению value их региона region. Группировка может быть выполнена с помощью инициализатора словаря Dictionary, например Dictionary(grouping:countries, by: { $0.region.value }).
Но сначала нужно получить страны countries. Модель также должна обрабатывать ошибки. Поэтому, возможно, нужно иметь перечисление enum для состояния загрузки loadState: загрузка loading, загружено loaded, ошибка error.

Собирая всё это вместе: модель запускается в состоянии loading, получает данные data, декодирует (decodes) их, группирует по регионам region и обновляет состояние до  loaded. Если есть ошибка, она устанавливает состояние в error.

Для SwiftUI View: основным View будет список List с разделами Section для каждого региона region. Заголовок header каждого раздела Section — это название региона region, а строками его  — страны countries в этом регионе.
Таким образом, ContentView будет иметь @StateObject CountryModel. В зависимости от состояния он показывает индикатор загрузки, список или сообщение об ошибке.

Но как обрабатывать сгруппированные страны? Модель может иметь словарь Dictionary [String: [Country]], и в View будем итерировать по отсортированным ключам keys словаря Dictionary для создания разделов  Section.

Подождите, но порядок регионов может быть случайным. Можно, отсортировать ключи keys в алфавитном порядке для согласованности.

Кроме того, некоторые страны Country могут иметь регион region с пустым или нулевым null значением. Нужно отфильтровать их или обработать. Например, Всемирный банк может иметь такие регионы region, как "Aggregates", которые не являются фактическими странами. Поэтому в модели после выборки отфильтруйте страны countries, где region.id — это «NA» или что-то в этом роде, или где значение value региона region является "Aggregates".

Если посмотреть на API Всемирного банка, регионы region включают такие агрегаты, как "Arab World" («Арабский мир»), «Еврозона» ("Euro area") и т. д. Это не фактические регионы, а группировки. Поэтому, возможно, стоит отфильтровать страны countries, где значение value региона region не является агрегатом "Aggregates"
Как? Объект региона region для страны может иметь такой идентификатор id, как «EAS» для Восточной Азии и Тихоокеанского региона (East Asia & Pacific), в то время как агрегаты имеют другие идентификаторы id. В качестве альтернативы проверьте идентификатор региона region.id и исключите определенные коды. В качестве альтернативы ответ response API для региона region страны country может иметь код, начинающийся с чего-то другого. Например, фактические регионы имеют коды типа «LCN» для Латинской Америки и Карибского бассейна (Latin America & Caribbean) , в то время как агрегаты имеют коды типа «ARB» для Арабского мира (Arab World). Поэтому, возможно, следует отфильтровать страны countries, в которых идентификатор региона region.id отсутствует в списке регионов-агрегатов.
В качестве альтернативы  country endpoint  API Всемирного банка включает параметр для исключения агрегатов. Позвольте мне проверить: добавление ?incomeLevel=LIC&lendingType=IBD может отфильтровать, но, возможно, проще отфильтровать в коде. Поэтому после извлечения всех стран отфильтруйте те, где region.id находится в списке регионов-агрегатов. Например, идентификатор региона для таких агрегатов, как "ARB", "CEB" и т. д. Поэтому, возможно, в структуре struct Country проверьте, является ли идентификатор региона region.id одним из агрегатов, и исключите эти страны.
Но, возможно, для простоты пользователь хочет все страны, включая агрегаты. Подождите, пользователь сказал "Страны мира", что обычно относится к суверенным государствам. Поэтому, возможно, отфильтруйте агрегаты. Поэтому в модели после извлечения отфильтруйте страны, чтобы исключить те, где идентификатор региона region.id находится в списке регионов-агрегатов.

Но как узнать, какие регионы являются агрегатами? Позвольте мне проверить документацию Всемирного банка. Объекты регионов для стран могут иметь код, а агрегаты имеют разные коды. Например, регион для страны может иметь такие коды, как «EAS», «ECS» и т. д., в то время как агрегаты имеют «ARB», «CEB» и т. д. Поэтому, возможно, в коде после извлечения отфильтровать страны, где идентификатор региона region.id находится в предопределенном наборе кодов агрегатов.
В качестве альтернативы проверьте, содержит ли значение value региона region слово «aggregate» или уровень «income». Не уверен. В качестве альтернативы найдите список кодов регионов, которые являются агрегатами. Например, согласно документации API Всемирного банка, агрегаты включают такие регионы, как «ARB» (Арабский мир), «CEB» (Центральная Европа и страны Балтии) и т. д. Поэтому, возможно, создайте набор таких кодов и исключите страны, где region.id находится в этом наборе.

Но для этого нужно знать конкретные коды. Поскольку пользователь не указал, возможно, пропустите этот шаг на данный момент и просто отобразите все страны, включая агрегаты, но это может быть нежелательно. Хм. Возможно, исходный код может включать все, а затем упомянуть, что фильтрация может быть добавлена ​​позже.
В качестве альтернативы, продолжайте без фильтрации и смотрите. В качестве примера, возможно, продолжайте без фильтрации и отметьте это в комментариях к коду.

Итак, двигаемся дальше. CountryModel выбирает все страны countries, группирует их по регионам region и отобразит их. View покажет разделы Section для каждого региона region, отсортированные по имени.

Теперь обрабатываем ответ response API. Структура JSON представляет собой массив, где первый элемент — метаданные, второй — массив стран [Country]. Поэтому модели необходимо декодировать эту структуру. Поэтому создайте структуру для ответа: struct WorldBankResponse: Decodable { let countries: [Country] }, но подождите, фактическая структура — это массив из двух элементов. Так что, возможно, в Swift используйте массив массивов. Затем данные извлекаются как [[AnyCodable]], но это неправильно. Подождите, первый элемент — это объект с информацией о страницах page, второй — массив объектов стран [Country].

Итак, возможно, создайте,  например, такую структуру:
struct WBResponse: Decodable {
   let page: Int
   let pages: Int
   let perPage: Int
   let total: Int
}
Но поскольку первый элемент — это объект с этими свойствами, а второй — массив объектов Country, возможно, в Swift мы можем смоделировать response  как двухэлементный массив, где первый — WBResponseMetadata, а второй — [Country].
Но как декодировать это в Swift? Поскольку массивы JSON не имеют ключей, нам нужно использовать контейнер без ключей unkeyedContainer.
Итак, создайте структуру, которая декодирует первый элемент как метаданные, а второй — как массив стран countries:  [Country].
Дайте подумать. JSON верхнего уровня — это массив. Первый элемент — это словарь с метаданными, а второй — массив словарей [Country].
Итак, в Swift мы можем создать структуру, которая декодирует это с помощью контейнера без ключей unkeyedContainer. Вот как это можно сделать:
struct WorldBankCountryResponse: Decodable {
     let countries: [Country]
     
     init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
       // Декодировать первый элемент (метаданные)
       _ = try container.decode(ResponseMetadata.self)
       // Декодировать второй элемент (массив стран)        
       let countries = try container.decode([Country].self)                
       self.countries = countries
     }
 }

 struct ResponseMetadata: Decodable {
    let page: Int
    let pages: Int
    let perPage: String // perPage -- это строка вроде "50"
    let total: Int
 }
Подождите, но в ответе response API Всемирного банка структура первого элемента имеет такие ключи, как "page", "pages", "per_page", "total". Итак, структура ResponseMetadata должна иметь эти ключи, используя Swift CodingKey:
 struct ResponseMetadata: Decodable {
   let page: Int
   let pages: Int
   let perPage: String
   let total: Int

   enum CodingKeys: String, CodingKey {
        case page, pages
        case perPage = "per_page"
        case total
   }
 }
Но значение per_page иногда может быть числом в API? Или это всегда строка String? Позвольте мне проверить. Например, ответ может иметь "per_page": "50" в качестве строки String. Таким образом, в структуре ResponseMeta свойство perPage — это строка String. Но в Swift, если это число в JSON, декодирование не удастся. Поэтому, возможно, используйте собственную стратегию декодирования или сделайте perPage как Int. Но согласно документации API Всемирного банка параметр per_page возвращается как строка  String. Хм... Может быть, стоит посмотреть пример ответа response? 
Пример ответа response API Всемирного банка для стран:
[
{ "page": 1, "pages": 6, "per_page": "50", "total": 299},
  [ {  "id": "ABW",  "iso2Code": "AW", "name": "Aruba", 
       "region": { "id": "LCN", "iso2code": "ZJ", "value": "Latin America & Caribbean "},   
        "adminregion": { "id": "", "iso2code": "", "value": ""},
        ... },
    ...
  ]
]
Итак, в первом элементе массива "per_page" — это строка String, а perPage в ResponseMetadata также должен быть String. Но при инициализации, возможно, мы можем преобразовать его в Int для использования, но в метаданных, возможно, нам это не нужно. Поскольку модель извлекает все страны с per_page=300, возможно, метаданные не критичны, но коду все равно нужно их декодировать.
Двигаемся дальше. Структура struct Country будет иметь вид: 
struct Country: Decodable, Identifiable {
    let id: String
    let iso2Code: String
    let name: String
    let region: Region

    struct Region: Decodable {
        let id: String
        let value: String
    }
}
С CodingKeys для обработки ключей JSON. Например, iso2Code происходит от "iso2code" в JSON, что соответствует, но region является объектом. Поэтому структуры правильно вложены.

Теперь URL для выборки — «http://api.worldbank.org/v2/country?format=json&per_page=300». Но подождите, API Всемирного банка требует HTTPS
Позвольте мне проверить. 
Да, все их endPoints API — HTTPS. Поэтому URL должен быть «https://api.worldbank.org/v2/country?format=json&per_page=300».
В CountryModel функция выборки  fetchCountries()  создаст задачу URLSession.shared. data (from:url ) для выборки данных из этого URL, декодирует ответ response в WorldBankCountryResponse, извлечет страны countries, сгруппирует их по region.value, а затем обновит @Published свойства.

Но модель должна обрабатывать асинхронную async задачу. Использование async / await было бы лучше и если поддерживается iOS 15+, мы можем использовать async/await. В противном случае используйте обработчики завершения completionHandlers. SwiftUI совместим с iOS 13+, но пользователь не указал версию iOS и учитывая, что последняя версия Xcode — 15, а SwiftUI претерпел изменения, возможно, стоит использовать async/await с модификатором .task в View.

Итак, в CountryModel:
 @MainActor
 class CountryModel: ObservableObject {
       @Published var groupedCountries: [String: [Country]] = [:]
       @Published var isLoading = false
       @Published var errorMessage: String?


       func fetchCountries() async {
            isLoading = true
            errorMessage = nil
            do {
               let url = URL(string:  
            "https://api.worldbank.org/v2/country?format=json&per_page=300")!
               let (data, _) = try await URLSession.shared.data(from: url)
               let response = try 
              JSONDecoder().decode(WorldBankCountryResponse.self, from: data)
       // Отфильтруйте агрегаты, если это необходимо
                let countries = response.countries
                let grouped = Dictionary(grouping: countries, by:
                                                      { $0.region.value })
                groupedCountries = grouped
                isLoading = false
             } catch {
                    errorMessage = error.localizedDescription
                    isLoading = false
             }
        }
   }
Подождите, но WorldBankCountryResponse — это структура struct, которая декодирует массив в страны countries. Затем groupedCountries — это словарь [String: [Country]]. 
Но в View при отображении нам нужно отсортировать регионы region в алфавитном порядке и отобразить каждый в отдельном разделе Section.
Итак, в ContentView:
 struct ContentView: View {
      @StateObject private var model = CountryModel()

      var body: some View {
          Group {
             if model.isLoading {
                 ProgressView()
             } else if let errorMessage = model.errorMessage {
                 Text("Ошибка: \(errorMessage)")
             } else {
                 List {
                    ForEach(model.groupedCountries.keys.sorted(), 
                                                 id: \.self) { region in
                         Section(header: Text(region)) {
                             ForEach(model.groupedCountries[region] ?? []) 
                                { country in
                                     Text(country.name)
                                 }
                          }
                     }
                   }
               }
           }
           .task {
                await model.fetchCountries()
            }
       }
    }
Но для этого требуется, чтобы структура Country была идентифицируемой Identifiable. Идентификатор  id  структуры struct Country— это строка String, которая является идентификатором  id страны country из API, например, «ABW». Так что все в порядке. 
Но подождите, я объявил структуру struct Country как Identifiable, а идентификатор id — строка String, так что да.

А как насчет регионов с одинаковым названием? Ключи keys — это region.value, которые могут иметь пробелы в конце. Например, в примере ответа response «Latin America & Caribbean » имеет пробел в конце. Поэтому нужно обрезать пробелы при группировке. Поэтому в замыкании группировки обрежьте region.value.
Следовательно, вы будете группировать таким образом:
let grouped = Dictionary(grouping: countries, by: 
                { $0.region.value.trimmingCharacters(in: .whitespaces) })
Поэтому регионы с “хвостовыми”пробелами будут сгруппированы правильно.

Кроме того, region.value может необязательно иметь заглавную букву. Например, «latin america & caribbean» против «Latin America & Caribbean». Но API, похоже, возвращает их правильно, так что, возможно, это не проблема. Но для надежности, возможно, следует преобразовать их в стандартный регистр lowercased(), но API, скорее всего, вернет их с правильным использованием заглавных букв.

Еще один момент: некоторые регионы могут быть пустыми или неприемлемыми. Например, «adminregion» в примере имеет в качестве идентификатора id и значения value пустые строки “”, но основной «region» должен иметь действительные данные для реальных стран. Но агрегаты типа  «Arab World» ("Арабский мир") могут иметь значение value  "Arab World" для региона region.

Поэтому, если мы хотим исключить агрегаты, нам нужно отфильтровать эти страны countries. Но опять же, если мы не знаем, какие регионы region являются агрегатами, это сложно. Ради этого примера давайте продолжим без фильтрации, но упомянем, что в реальном приложении фильтрация может потребоваться.
Теперь, тестирование кода. Давайте проверим, скомпилируется ли код.

Структура struct Country является Decodable и Identifiable. WorldBankCountryResponse использует пользовательский  decoder для получения стран countries из второго элемента массива. Модель использует async/await для выборки  и декодирования.

Потенциальные проблемы:

- Инициализатор  Init(from:) структуры WorldBankCountryResponse может “выбрасывать” throws ошибку error, если структура не отвечает реальным JSON данным. Например, если API возвращает сообщение об ошибке, декодирование завершится неудачей. Необходимо обработать ошибки, возможно, проверить код состояния ответа HTTP перед декодированием.
Но в текущем коде модель перехватывает catch ошибки error декодирования и показывает сообщение об ошибке. Так что, это решено.

Еще одна проблема: регионы сортируются в алфавитном порядке, что правильно.
В View List использует ForEach для отсортированных ключей keys.sorted() словаря groupedCountries. Каждый раздел Section использует название региона в качестве заголовка header и перечисляет страны в этом регионе.

Теперь давайте напишем полный код.
Но подождите, WorldBankCountryResponse — это структура struct, которая декодирует первый элемент как метаданные, а второй — как страны countries. Но в Swift при декодировании массива осуществляется не ключевым контейнером unkeyedContainer, который должен обрабатывать элементы массива  по порядку. Код в инициализаторе init(from:) WorldBankCountryResponse делает это: сначала декодирует метаданные (которые отбрасываются), затем декодирует массив стран countries.

Структура struct ResponseMetadata является частью первого элемента. Но в WorldBankCountryResponse нам не нужны метаданные, поэтому код их отбрасывает. Сохраняются только страны countries.
Итак, код должен работать.
Теперь соберем все вместе. 
Возможные улучшения:

- Сообщения об ошибках от API (например, если ответ response является error объектом) не обрабатываются. API Всемирного банка может возвращать ошибку в другом формате. Но для простоты код предполагает, что ответ правильный.

- UI является базовым. Возможно, стоит добавить больше стилей, но пользователь не указал.

- Ячейки стран могут отображать больше информации, например код ISO.

Но пользователь попросил отображать страны по категориям, что код и делает, группируя по регионам.

Версия 1

Таким образом, окончательный код будет выглядеть так.

Вот SwiftUI код для iOS  приложения, которое отображает страны Всемирного банка, сгруппированные по регионам, с использованием API Всемирного банка:

import SwiftUI

struct ContentView: View {
    @StateObject private var viewModel = CountryViewModel()
    
    var body: some View {
        NavigationView {
            Group {
                switch viewModel.loadingState {
                case .loading:
                    ProgressView("Loading countries...")
                case .loaded:
                    List {
                        ForEach(viewModel.groupedCountries.keys.sorted(),
                                                        id: \.self) { region in
                            Section(header: Text(region)) {
                                ForEach(viewModel.groupedCountries[region] ?? []) 
                                 { country in
                                    CountryRow(country: country)
                                }
                            }
                        }
                    }
                case .error(let message):
                    Text(message)
                        .foregroundColor(.red)
                }
            }
            .navigationTitle("World Countries")
        }
        .task {
            await viewModel.fetchCountries()
        }
    }
}

struct CountryRow: View {
    let country: Country
    
    var body: some View {
        HStack {
            Text(country.name)
            Spacer()
            Text(country.iso2Code)
                .foregroundColor(.secondary)
        }
    }
}

struct Country: Decodable, Identifiable {
    let id: String
    let iso2Code: String
    let name: String
    let region: Region
    
    struct Region: Decodable {
        let id: String
        let value: String
    }
    
    enum CodingKeys: String, CodingKey {
        case id, name, region
        case iso2Code = "iso2code"
    }
}

class CountryViewModel: ObservableObject {
    enum LoadingState {
        case loading, loaded, error(String)
    }
    
    @Published var loadingState: LoadingState = .loading
    @Published var groupedCountries: [String: [Country]] = [:]
    
    private let apiURL = 
                  "https://api.worldbank.org/v2/country?format=json&per_page=300"
    
    func fetchCountries() async {
        guard let url = URL(string: apiURL) else {
            loadingState = .error("Invalid URL")
            return
        }
        
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            let countries = try parseResponse(data)
            
            // Group countries by region and filter out aggregates
            let filteredCountries = countries.filter { $0.region.id != "NA" }
            let grouped = Dictionary(grouping: filteredCountries) {
                $0.region.value.trimmingCharacters(in: .whitespaces)
            }
            
            await MainActor.run {
                groupedCountries = grouped
                loadingState = .loaded
            }
        } catch {
            await MainActor.run {
                loadingState = .error(error.localizedDescription)
            }
        }
    }
    
    private func parseResponse(_ data: Data) throws -> [Country] {
        struct WorldBankResponse: Decodable {
            let country: [Country]
            
            init(from decoder: Decoder) throws {
                var container = try decoder.unkeyedContainer()
                _ = try container.decode(ResponseMetadata.self) // Skip metadata
                country = try container.decode([Country].self)
            }
        }
        
        struct ResponseMetadata: Decodable {
            let page: Int
            let pages: Int
            let perPage: String
            let total: Int
            
            enum CodingKeys: String, CodingKey {
                case page, pages, total
                case perPage = "per_page"
            }
        }
          return try JSONDecoder().decode(WorldBankResponse.self, 
                                                       from: data).country
    }
}


#Preview {
    ContentView()
}

Основные особенности этой реализации:

  1. Выборка данных:
  • Использует endpoint country API Всемирного банка
  • Выбирает до 300 стран (охватывает все доступные страны)
  • Использует async/await для сетевых запросов

2. Обработка данных:

  • Анализирует JSON ответ response с помощью пользовательского декодирования
  • Фильтрует агрегированные регионы (например, «Арабский мир») с помощью проверок идентификаторов id регионов region
  • Группирует страны countries по их регионам с помощью словаря

3. Компоненты UI:

  • NavigationView с состояниями загрузки
  • Список List с разделами Section для каждого региона region
  • Пользовательский CountryRow, отображающий название страны и код ISO
  • Обработка ошибок 

4. Архитектура:

  • Шаблон MVVM с выделенной ViewM odel
  • @Published свойства для управления состоянием
  • Отделение логики доступа к сети  от компонентов UI

Чтобы использовать это приложение:

  1. Создайте новый проект SwiftUI в Xcode
  2. Замените ContentView этим кодом
  3. Убедитесь, что у проекта есть разрешения на доступ в Интернет (добавьте NSAppTransportSecurity в Info.plist (если необходимо)

При запуске приложение автоматически выберет и отобразит страны countries, сгруппированные по регионам. В каждом разделе Section показаны страны, принадлежащие определенному региону, с названием страны и кодом ISO, отображаемыми в каждой строке.

Примечание: API Всемирного банка может иногда быть недоступен или иметь ограничения по скорости. Для использования в производстве следует добавить надлежащую обработку ошибок и потенциальные механизмы кэширования.

Если мы запустим приложение, то получим сообщение об ошибке:

“ The data couldn’t be read because it is missing.”

Не удалось прочитать данные, поскольку они отсутствуют»)

Корректировка декодирования структуры struct Country

Проблема, по-видимому, в декодировании структуры struct Country, поэтому попросим DeepSeek исправить эту ошибку:

Данные не могут быть прочитаны, потому что они пропущены. Обратите внимание на имена полей в объекте Country

В результате мы получаем рассуждения, которые длятся 51 секунду (чуть менее 1 минуты):

Вот их перевод:

Хорошо, пользователь упомянул, что данные не могут быть прочитаны, потому что они отсутствуют, и они хотят, чтобы я проверил имена полей JSON в объекте Country. Давайте вернемся к исходному коду, который я предоставил, и посмотрим, в чем может быть проблема.

Во-первых, я помню, что API Всемирного банка возвращает массив JSON, где первый элемент — метаданные, а второй — фактический список стран. Исходный код использует структуру WorldBankResponse, которая пытается это проанализировать. Но, возможно, есть проблема с тем, как структурировано декодирование.

Если посмотреть на структуру Country, ключи кодирования — это id, name, region и iso2Code. JSON из Всемирного банка может использовать другие ключи. Давайте проверим фактический ответ API. Например, запись о стране может выглядеть так:
{"id": "BRA","iso2Code": "BR","name": "Brazil",
  "region": {"id": "LCN","value": "Latin America & Caribbean "},
...
}
Подождите, в структуре Country идентификатор id — это строка String, но в JSON идентификатор страны — это "BR", что является iso2Code. Но есть еще одно поле с именем "iso2Code". Это может быть проблемой. Структура Country исходного кода использует идентификатор в качестве идентификатора страны, но на самом деле идентификатор страны в JSON — это iso2Code. Так что, возможно, идентификатор структуры Country должен соответствовать полю "id" из JSON, которое на самом деле является iso2Code. Но есть отдельное поле "iso2Code". Подождите, это не имеет смысла. Давайте проверим еще раз.

В ответе JSON response у каждой страны есть поле "id", которое является двухбуквенным кодом ISO, а также есть поле "iso2code" (строчная буква "c"), которое может быть тем же самым. Подождите, может быть, API использует "iso2Code" со строчной буквой "c" в JSON, но код использует CodingKeys для сопоставления iso2Code с "iso2code". Давайте проверим.

В исходном коде структура Country имеет:
case iso2Code = "iso2code"
Но в JSON поле называется "iso2Code" или "iso2code"? Если JSON использует "iso2Code" с заглавной буквой "C", то ключ кодирования неверен. Например, если ключ JSON - "iso2code" (все строчные буквы, кроме "2"), то ключ кодирования правильный. Но, возможно, фактический ключ другой. Позвольте мне проверить документацию API Всемирного банка или пример ответа.

При поиске endpoint country API Всемирного банка пример ответа показывает:
[
  { "page": 1, "pages": 6, "per_page": "50", "total": 299},
  [
    { "id": "ABW", "iso2Code": "AW", "name": "Aruba",
      "region": {"id": "LCN","value": "Latin America & Caribbean"},
...
     },
...
  ]
]
А, здесь у страны есть "id": "ABW" (это трехбуквенный код) и "iso2Code": "AW" (двухбуквенный код). Итак, в JSON ключами являются "id", "iso2Code", "name", "region" и т. д. Но в структуре struct Country исходного кода свойство CodingKeys для iso2Code установлено на "iso2code" (строчная буква "c"), что неверно. Правильный ключ JSON — "iso2Code" с заглавной буквой "C". Поэтому ключ кодирования должен быть:
case iso2Code = "iso2Code"
Но в Swift ключи CodingKeys чувствительны к регистру. Поэтому в исходном коде было:
case iso2Code = "iso2code"
Что неверно, поскольку JSON использует "iso2Code" с заглавной буквой "C". Это ошибка. Следовательно, свойство iso2Code структуры Country не декодируется правильно, поскольку ключ кодирования неверен, что приводит к потере данных.

Кроме того, идентификатор структуры Country сопоставляется с полем "id" JSON, которое представляет собой трехбуквенный код (например, "ABW"), в то время как iso2Code представляет собой двухбуквенный код. Таким образом, идентификатор id структуры struct Country исходного кода представляет собой трехбуквенный код страны, а iso2Code представляет собой двухбуквенный код. Но в View CountryRow отображает iso2Code, что было бы правильным как двухбуквенный код.
Но главная проблема заключается в неправильном ключе кодирования для iso2Code. JSON использует "iso2Code", поэтому ключ кодирования должен быть чувствителен к регистру. Изменение ключа кодирования с "iso2code" на "iso2Code" должно устранить проблему декодирования.

Другая возможная проблема заключается в синтаксическом анализе WorldBankResponse. Исходный код использует структуру WorldBankResponse, которая пытается декодировать первый элемент как метаданные, а второй как список стран. Однако исходный массив может иметь больше элементов или структура может быть другой. Давайте проверим пример ответа еще раз.

Ответ response представляет собой массив, где первый элемент — объект с метаданными, а второй элемент — массив объектов стран. Поэтому декодирование в WorldBankResponse верно: используется не ключевой контейнер unkeyedContainer, декодирование первого элемента как ResponseMetadata, а затем декодирование следующего элемента как [Country].

Но, возможно, структура метаданных отличается. Давайте проверим структуру ResponseMetadata. В ней есть page, pages, perPage (сопоставлено с "per_page") и total. JSON для метаданных имеет "per_page" в качестве строки String, которая соответствует CodingKey. Так что все должно быть в порядке.
Другая возможная проблема заключается в том, что регион структуры Country является необязательным. Если регион отсутствует в некоторых странах, декодирование не удастся. Но API Всемирного банка, вероятно, включает регион для всех стран. Однако в исходном коде при группировке они отфильтровывают регионы, где region.id == "NA", что может быть агрегированным. Но если регион отсутствует, декодирование не удастся. Но согласно ответу API, у каждой страны есть регион.

Поэтому основное исправление — это исправление ключа кодирования iso2Code с "iso2code" на "iso2Code".

Еще одна вещь, которую нужно проверить: в структуре Country идентификатор id — это трехбуквенный код, но в исходном коде при отображении CountryRow используется country.iso2Code, который должен быть двухбуквенным кодом. Это верно.

Поэтому ошибка пользователя, скорее всего, связана с тем, что iso2Code не декодируется, что приводит к отсутствию некоторых данных. Но поскольку исходный код использовал "iso2code" в качестве ключа кодирования вместо "iso2Code", свойство iso2Code не устанавливалось, что приводило к отсутствию данных при попытке отобразить его или использовать в другом месте.

Исправленная структура Country должна иметь:
case iso2Code = "iso2Code"
Это ключевое исправление. Другие возможные проблемы могут быть связаны с декодированием региона, но пользователь специально упомянул имена полей JSON в объекте Country, поэтому сосредоточился на iso2Code.

Это ответ пользователю:

Вы абсолютно правы — была ошибка в отображении поля JSON. API Всемирного банка использует iso2Code (с заглавной буквой C) в своем ответе JSON, но наш код использовал iso2code (строчная c) в CodingKeys. Вот исправленная реализация:

struct Country: Decodable, Identifiable {
    let id: String
    let iso2Code: String
    let name: String
    let region: Region
    
    struct Region: Decodable {
        let id: String
        let value: String
    }
    enum CodingKeys: String, CodingKey {
        case id, name, region
        case iso2Code = "iso2Code"  // Fixed the case here
    }
}

Ключевое исправление находится в перечислении enum CodingKeys, где мы теперь правильно отображается:

case iso2Code = "iso2Code"

Версия 2

Вот полностью исправленная View Model с правильной обработкой JSON:

class CountryViewModel: ObservableObject {
    enum LoadingState {
        case loading, loaded, error(String)
    }
    
    @Published var loadingState: LoadingState = .loading
    @Published var groupedCountries: [String: [Country]] = [:]
    
    private let apiURL = 
                 "https://api.worldbank.org/v2/country?format=json&per_page=300"
    
    func fetchCountries() async {
        guard let url = URL(string: apiURL) else {
            loadingState = .error("Invalid URL")
            return
        }
        
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            let countries = try parseResponse(data)
            
            // Filter out regional aggregates and group by region
            let filteredCountries = countries.filter {
                !$0.region.value.lowercased().contains("aggregate") &&
                $0.region.id != "NA"
            }
            
            let grouped = Dictionary(grouping: filteredCountries) { country in
                country.region.value.trimmingCharacters(in: .whitespacesAndNewlines)
            }
            
            await MainActor.run {
                groupedCountries = grouped
                loadingState = .loaded
            }
        } catch {
            await MainActor.run {
                loadingState = .error("Failed to load countries: 
                                              \(error.localizedDescription)")
            }
        }
    }
    
    private func parseResponse(_ data: Data) throws -> [Country] {
        struct WorldBankResponse: Decodable {
            let countries: [Country]
            
            init(from decoder: Decoder) throws {
                var container = try decoder.unkeyedContainer()
                _ = try container.decode(ResponseMetadata.self)
                countries = try container.decode([Country].self)
            }
        }
        
        struct ResponseMetadata: Decodable {
            let page: Int
            let pages: Int
            let perPage: String
            let total: Int
        }
        
        return try JSONDecoder().decode(WorldBankResponse.self, 
                                                    from: data).countries
    }
}

Ключевые улучшения в этой версии:

  1. Исправленное сопоставление iso2Code JSON
  2. Лучшая фильтрация агрегированных регионов
  3. Более надежная очистка имен регионов
  4. Улучшенные сообщения об ошибках
  5. Правильная обработка структуры не ключевого контейнера unkeyedContainer

Структура ответа response API Всемирного банка:

[
  {  // Metadata object
    "page": 1,
    "pages": 5,
    "per_page": "50",
    "total": 299
  },
  [  // Actual countries array
    {
      "id": "ABW",
      "iso2Code": "AW",
      "name": "Aruba",
      "region": {
        "id": "LCN",
        "value": "Latin America & Caribbean "
      },
      // ... other fields
    }
  ]
]

Это решение теперь правильно:

  1. Обрабатывает структуру массива из двух элементов
  2. Сопоставляет все обязательные поля с правильным регистром
  3. Более надежно отфильтровывает агрегированные регионы
  4. Удаляет пробелы из имен регионов
  5. Использует правильную многопоточность  Swift с async/await

Ошибка, которую вы видели («Не удалось прочитать данные, поскольку они отсутствуют»), была вызвана именно неправильным сопоставлением iso2code / iso2Code, которое теперь устранено.

Да, эта ошибка устранена, но в представленном выше коде появилась новая ошибка:

Теперь  struct ResponseMetadata выглядит немного по-другому:

struct ResponseMetadata: Decodable {
            let page: Int
            let pages: Int
            let perPage: String
            let total: Int
 }

A раньше это было так:

struct ResponseMetadata: Decodable {
            let page: Int
            let pages: Int
            let perPage: String
            let total: Int
            
            enum CodingKeys: String, CodingKey {
                case page, pages, total
                case perPage = "per_page"
            }
 }

И это было правильно.
Поэтому, если вы в предыдущем коде только изменили строку 

case iso2Code = "iso2code"

на

case iso2Code = "iso2Code"

, оставив:

struct ResponseMetadata: Decodable {
            let page: Int
            let pages: Int
            let perPage: String
            let total: Int
            
            enum CodingKeys: String, CodingKey {
                case page, pages, total
                case perPage = "per_page"
            }
 }

… то приложение будет работать правильно.

A вот если вы полностью заменили функцию  func parseResponse (_ data: Data) throws -> [Country] на новую, то опять получите ту же самую ошибку.

Вывод: DeepSeek не держит контекст, может самопроизвольно изменить правильный код. Будьте осторожны!!!

Запускаем правильное приложение с заменой одной буквы в названии свойства и получаем результат:

Что прекрасно в этой реализации iOS приложения, предоставленной DeepSeek в отличие от версии, предоставленной Claude 3.5 Sonnet :

  1. Используется современный многопоточный механизм async / await, a не CGD
  2. Прекрасно настроен пользовательский init с помощью unkeyedContainer для Decode JSON данных, представляющих собой массив неоднородных элементов, что обычно не является стандартным форматом JSON

Добавляем численность населения population и ВВП gdp 

Теперь мы хотим добавить численность населения population и ВВП gdp для каждой страны:

добавь population, gdp, флаг flag и столицу capitalCity для каждой страны. Возьмите capitalCity из JSON для country и сконструируйте флаг из эмодзи  

В результате мы получаем рассуждения, которые длятся 53 секунду (чуть менее 1 минуты):

Вот их перевод:

Хорошо, давайте рассмотрим запрос пользователя на добавление населения population, ВВП gdp, столицы и флагов для каждой страны в приложении SwiftUI. Сначала мне нужно выяснить, где получить эти данные из API Всемирного банка. Предыдущая реализация использовала базовую country endpoint, которая могла не давать всех нужных нам полей.
Подождите, пользователь упомянул добавление населения population и ВВП gdp. API Всемирного банка имеет отдельные индикаторы indicator для них: SP.POP.TOTL для населения population и NY.GDP.MKTP.CD для ВВП gdp. Поэтому мне нужно сделать дополнительные API вызовы, чтобы получить эти показатели для каждой страны. Но обработка нескольких вызовов API может усложнить ситуацию, особенно потому, что нам нужно получать данные для каждой страны по отдельности. Это может быть медленно или иметь ограничения по лимиту на запросы. Хм, может быть, есть способ запросить сразу пакет данных  по населения population и ВВП gdp или использовать другую endpoint, которая включает больше данных.
Кроме того, пользователь хочет столицу. Я помню, что country endpoint Всемирного банка предоставляет некоторую базовую информацию, включая столицу. Позвольте мне проверить документацию API. Да, country endpoint возвращает поле capitalCity, то есть имя столицы. Отлично, теперь мы можем извлечь его напрямую, без другого вызова API.
Флаг пользователь хочет создать из эмодзи. Я помню, что коды стран можно преобразовать в эмодзи флагов с помощью региональных индикаторных символов Unicode. Каждая буква кода ISO 3166-1 alpha-2 соответствует региональному индикаторному символу. Например, «US» становится 🇺🇸. Поэтому мне нужно написать функцию, которая берет код ISO2 и генерирует эмодзи флага.
Теперь интегрируем их в существующий код. Структуре struct Country нужны новые свойства: population, gdp, capitalCity и flag. Подождите, пользователь может ожидать данных в реальном времени, но выборка ВВП gdp и населения population для каждой страны при запуске приложения может быть слишком медленным. Может быть, нам следует добавить их как необязательные Optional свойства и выбирать их в фоновом (background) режиме, обновляя UI по мере их поступления. Однако это усложняет View Model. Или, возможно, пользователю нужны статические данные, но данные Всемирного банка являются ежегодно обновляемыми. Нужно уточнить, но поскольку пользователь не указал, может быть, мы можем включить последние доступные значения.
Подождите, первоначальная реализация использовала country endpoint для списка стран, которая может не включать население population и ВВП gdp. Поэтому, чтобы получить их, нам нужно запросить индикаторы через их  indicator endpoint. Но каждый индикатор требует отдельного вызова для каждой страны. Это слишком много вызовов. Возможно, пользователь не знает об этом и ожидает все данные из одной endpoint. Позвольте мне еще раз проверить API. Точка country endpoint (v2/country) возвращает некоторую базовую информацию, включая capitalCity, но не население population или ВВП gdp. Так что да, для этого требуются отдельные API вызовы.
Но делать сотни вызовов API нецелесообразно. Может быть, есть способ получить население и ВВП всех стран за один вызов? Если посмотреть на документацию API, то формат endpoint будет примерно таким: /v2/country/all/indicator/SP.POP.TOTL?format=json&date=2022 для населения population. Аналогично для ВВП gdp. Таким образом, мы можем получить данные о населении всех стран за один вызов, и то же самое для ВВП. Затем сопоставить эти значения с каждой страной.
Это лучший подход. Итак, план такой:
1. Получить список стран, как и раньше.
2. Получить последние данные о населении для всех стран за один вызов.
3. Получить последние данные о ВВП для всех стран за один вызов.
4. Объединить все эти данные в моделе struct Country.

Но для этого требуется обработка трех отдельных API вызовов и объединение данных. Необходимо структурировать View Model для асинхронной обработки, возможно, используя async let для многопоточных запросов.
Кроме того, данные о населении и ВВП могут быть доступны не для всех стран, поэтому они должны быть необязательными Optional в Модели.
Для эмодзи флага функция возьмет iso2Code страны и преобразует каждый символ в соответствующий региональный индикатор. Например, «US» станет «U» и «S», которые являются значениями Unicode, начинающимися с 0x1F1E6 (A — это 0x1F1E6). Таким образом, вычитание «A» из каждого символа дает смещение. Но нужно обрабатывать строчные буквы и гарантировать, что код состоит из двух символов.
Теперь обновим структуру struct Country:

1. Добавим capitalCity из существующей country endpoint
2. Добавим population и gdp в качестве необязательных Optional свойств. 
3. Добавим вычисляемое свойство для flag.
В View Model после выборки стран выбираем данные о населении population и ВВП gdp, затем “привязываем” их к соответствующим странам.
Но как структурировать API вызовы для population и GDP? Ответ response для indicator endpoint немного отличается от ответа response для country endpoint. Это массив записей, где каждая запись имеет countryiso2code, значение value и дату date. Таким образом, мы можем создать словарь, сопоставляющий коды стран с их значениями населения population и ВВП  gdp.
Итак, шаги в функции func fetchCountries ViewModel:

1. Выбрать страны countries.
2. Выбрать  данные о населении population.
3. Выбрать данные о ВВП  gdp.
4. Объединить все данные в модели страны struct Country.
5. Группировать по регионам region.
Необходимо обрабатывать ошибки для каждого API вызова. Возможно, использовать async let для их многопоточного запуска, а затем ожидать await  все три результата. 
Но обработка ошибок может оказаться сложной. В качестве альтернативы выбирайте их последовательно, но это будет медленнее.
После того, как данные будут выбраны, создайте словари Dictionary для населения population и ВВП  gdp, затем обновите каждую страну Country соответствующими значениями.
Для флага вычисляемое свойство в структуре Country сгенерирует эмодзи.
В UI обновите CountryRow для отображения capitalCity, population, GDP и флага. Возможно, с помощью NavigationLink создание более подробного View, но пользователь не указал, поэтому, просто расширяем существующую строку.
Тестирование пограничных случаев: страны без кодов ISO2, отсутствующие данные и т. д. Также убедитесь, что функция flag обрабатывает случаи, когда код не состоит из двух букв.
Теперь обновим код:

В структуре struct Country:

- Добавьте свойства capitalCity, population, gdp.
- Добавьте вычисляемое свойство flag.

В View Model:

- Измените функцию func fetchCountries, чтобы выбирать также данные о населении и ВВП.
- Проанализируйте эти ответы response и сопоставьте их со странами.

В CountryRow:

- Отображение флага, столицы, населения, ВВП.
Потенциальные проблемы: ограничения скорости API, отсутствующие данные, неверные форматы дат, обработка необязательных Optional значений в UI. 
Кроме того, API Всемирного банка может возвращать данные о населении и ВВП с разными кодами стран (например, некоторые агрегаты  “Aggregates”), поэтому их нужно отфильтровывать, как и раньше.
Реализация функции эмодзи флага как расширения  extension для строки String.
Собираем все вместе, проверяем каждую часть.

Вот улучшенная реализация с эмодзи населения, ВВП, столицы и флага:

import SwiftUI

struct ContentView: View {
    @StateObject private var viewModel = CountryViewModel()
   
    var body: some View {
        NavigationView {
            Group {
                switch viewModel.loadingState {
                case .loading:
                    ProgressView("Loading countries...")
                case .loaded:
                    List {
                      ForEach(viewModel.groupedCountries.keys.sorted(),
                                                 id: \.self) { region in
                        Section(header: Text(region)) {
                            ForEach(viewModel.groupedCountries[region] ?? 
                                                        []){ country in
                               CountryRow(country: country)
                             }
                        }
                      }
                    }
                case .error(let message):
                    Text(message)
                        .foregroundColor(.red)
                }
            }
            .navigationTitle("World Countries")
        }
        .task {
            await viewModel.fetchAllData()
        }
    }
}

struct CountryRow: View {
    let country: Country
   
    var body: some View {
        HStack(spacing: 12) {
            Text(country.flag)
                .font(.system(size: 32))
           
            VStack(alignment: .leading, spacing: 4) {
                Text(country.name)
                    .font(.headline)
               
                HStack(spacing: 16) {
                    Label(country.capitalCity, systemImage: "building.2")
                    Text(country.iso2Code)
                        .monospaced()
                }
                .font(.caption)
               
                HStack(spacing: 16) {
                    if let population = country.population {
                      Label(population.formatted() +"people",
                                            systemImage: "person.2")
                    }
                    if let gdp = country.gdp {
                        Label("$" + gdp.formatted(),
                                           systemImage: "dollarsign.circle")
                    }
                }
                .font(.caption)
                .foregroundColor(.secondary)
            }
        }
    }
}

struct Country: Decodable, Identifiable {
    let id: String
    let iso2Code: String
    let name: String
    let capitalCity: String
    let region: Region
    var population: Int?
    var gdp: Double?
   
    var flag: String {
        iso2Code.unicodeScalars
            .map { 127397 + $0.value }
            .compactMap(UnicodeScalar.init)
            .map(String.init)
            .joined()
    }
   
    struct Region: Decodable {
        let id: String
        let value: String
    }
   
    enum CodingKeys: String, CodingKey {
        case id, name, region
        case iso2Code = "iso2Code"
        case capitalCity = "capitalCity"
    }
}

class CountryViewModel: ObservableObject {
    enum LoadingState {
        case loading, loaded, error(String)
    }
   
    @Published var loadingState: LoadingState = .loading
    @Published var groupedCountries: [String: [Country]] = [:]
   
    private let countryURL = "https://api.worldbank.org/v2/country?format=json&per_page=300"
    private let populationURL = "https://api.worldbank.org/v2/country/all/indicator/SP.POP.TOTL?format=json&date=2022&per_page=300"
    private let gdpURL = "https://api.worldbank.org/v2/country/all/indicator/NY.GDP.MKTP.CD?format=json&date=2022&per_page=300"
   
    func fetchAllData() async {
        do {
            async let countries = fetchCountries()
            async let populationData = fetchIndicatorData(url: populationURL)
            async let gdpData = fetchIndicatorData(url: gdpURL)
           
            var finalCountries = try await countries
            let populationDict = try await populationData
            let gdpDict = try await gdpData
           
            // Merge economic data
            finalCountries = finalCountries.map { country in
                var modified = country
                modified.population = Int(populationDict[country.iso2Code] ?? 0)
                modified.gdp = gdpDict[country.iso2Code]
                return modified
            }
           
            let filtered = finalCountries.filter {
                !$0.region.value.lowercased().contains("aggregate") &&
                $0.region.id != "NA" &&
                $0.capitalCity != ""
            }
           
            let grouped = Dictionary(grouping: filtered) {
                $0.region.value.trimmingCharacters(in: 
                                                   .whitespacesAndNewlines)
            }
           
            await MainActor.run {
                groupedCountries = grouped
                loadingState = .loaded
            }
        } catch {
            await MainActor.run {
                loadingState = 
                .error("Failed to load data: \(error.localizedDescription)")
            }
        }
    }
   
    private func fetchCountries() async throws -> [Country] {
        struct WorldBankResponse: Decodable {
            let countries: [Country]
           
            init(from decoder: Decoder) throws {
                var container = try decoder.unkeyedContainer()
                _ = try container.decode(ResponseMetadata.self)
                countries = try container.decode([Country].self)
            }
        }
       
        let (data, _) = try await URLSession.shared.data(from: URL(string: countryURL)!)
        return try JSONDecoder().decode(WorldBankResponse.self, from: data).countries
    }
   
    private func fetchIndicatorData(url: String) async throws -> [String: Double] {
        struct IndicatorResponse: Decodable {
            let entries: [Entry]
           
            init(from decoder: Decoder) throws {
                var container = try decoder.unkeyedContainer()
                _ = try container.decode(ResponseMetadata.self)
                entries = try container.decode([Entry].self)
            }
           
            struct Entry: Decodable {
                let countryiso2code: String
                let value: Double?
               
                enum CodingKeys: String, CodingKey {
                    case countryiso2code = "countryiso2code"
                    case value = "value"
                }
            }
        }
       
        let (data, _) = 
                 try await URLSession.shared.data(from: URL(string: url)!)
        let response = 
                 try JSONDecoder().decode(IndicatorResponse.self, from: data)
       
        return response.entries.reduce(into: [:]) { dict, entry in
            guard let value = entry.value else { return }
            let code = String(entry.countryiso2code.suffix(2))
            dict[code] = value
        }
    }
}

struct ResponseMetadata: Decodable {
    let page: Int
    let pages: Int
    let perPage: String
    let total: Int
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Ключевые улучшения:

Новые поля данных:

  • Столица capitalCity (из базового API country)
  • Население population (из индикатора SP.POP.TOTL)
  • ВВП gdp (из индикатора NY.GDP.MKTP.CD)
  • Флаг эмодзи, сгенерированный из кода ISO2

Улучшенный UI:

  • Отображение флага эмодзи
  • Метка Label для столицы
  • Отформатированные значения населения и ВВП
  • Системные иконки для разных типов данных
  • Лучшее размещение и иерархия

Обработка данных:

  • Многопоточные вызовы API для данных о стране, населении и ВВП
  • Объединение данных с использованием async / await
  • Обработка ошибок для нескольких источников данных
  • Фильтрация недопустимых записей

Форматирование:

  • Столица capitalCity (из API базовой страны)
  • Население population (из индикатора SP.POP.TOTL)
  • ВВП gdp (из индикатора NY.GDP.MKTP.CD)
  • Флаг эмодзи, сгенерированный из кода ISO2

Примечание:

  • Вызовы API используют жестко закодированные даты данных 2022 года — возможно, вы захотите сделать это динамически
  • Некоторые страны могут не иметь последних экономических данных
  • Система эмодзи флагов использует коды ISO2 для генерации региональных индикаторных символов
  • Значения ВВП отображаются в необработанных суммах в долларах США (вы можете отформатировать как миллиарды/триллионы)

Чтобы использовать эту реализацию:

  1. Убедитесь, что у вашего приложения есть разрешения на доступ в Интернет
  2. Обработайте потенциальные ограничения скорости API (Всемирный банк разрешает 100 запросов в минуту)
  3. Добавьте состояния загрузки для данных отдельных стран
  4. Рассмотрите возможность добавления refresh функциональности 
  5. Реализуйте обработку ошибок для отдельных endpoints

Теперь приложение будет отображать:

  • Эмодзи флага страны 🇺🇸 🇨🇳 🇮🇳 и т. д.
  • Столицу
  • Численность населения
  • ВВП в долларах США
  • Региональную группировку
  • Код ISO2

Версия 3

Если мы запустим данный код, то получим уже привычное нам сообщение об ошибке:

“ The data couldn’t be read because it is missing.”

Это опять связано с декодированием данных.

Корректируем поля в Индикаторе 

И опять в  улучшенной реализации с эмодзи населения, ВВП, столицы и флага мы имеем НЕВЕРНЫЙ код для ResponseMetadata 

Продолжение следует в посте «Использование DeepSeek при создании iOS приложения Countries Страны Мира. Часть 2. Численность населения, ВВП, флаг.»

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *