Цель: показать данные API на панели вкладок с помощью SwiftUI.
Что я пробовал: я попытался реализовать API с помощью модели и реализовал его в виде вкладок.
В чем проблема: я много раз пытался изменить свою модель, но данные все равно не извлекались правильно.
Получение ошибки: данные не могут быть прочитаны, поскольку они имеют неправильный формат.
URL API: https://dev.myhss.org.uk/api/v1/events/eventlist
Необязательный параметр (можно использовать): ключ : тип_события значение: 1(1- предстоящие события, 2- прошедшие события)
тело: x-www-form-unlencoded
Идеальный ответ:
{
"status": true,
"message": "Event Listing",
"data": {
"upcoming": [
{
"event_id": "1",
"event_title": "event tile",
"event_start_date": "2024-02-02 16:55:08",
"event_end_date": "2024-06-20 16:55:08",
"event_img": "https://dev.myhss.org.uk/assets/events/event-1.png",
"event_chargable_or_not": "0"
}
],
"past": [
{
"event_id": "2",
"event_title": "event title 2",
"event_start_date": "2023-04-15 16:55:08",
"event_end_date": "2023-05-31 16:55:08",
"event_img": "https://dev.myhss.org.uk/assets/events/event-2.png",
"event_chargable_or_not": null
}
]
}
}
Обновил предложенный код из ответа, но проблема не устранена, Ошибка: данные не могут быть прочитаны из-за неправильного формата.
Код SwiftUI:
import SwiftUI
struct EventListing: Codable {
var status: Bool
var message: String
var data: Event2
}
struct Event2: Codable {
var upcoming: [Event]
var past: [Event]
}
struct Event: Codable, Identifiable {
var id: String // Add this property to conform to Identifiable
var title: String
var startDate: Date
var endDate: Date
var imageURL: URL?
var isChargeable: Bool
enum CodingKeys: String, CodingKey {
case id = "event_id"
case title = "event_title"
case startDate = "event_start_date"
case endDate = "event_end_date"
case imageURL = "event_img"
case isChargeable = "event_chargable_or_not"
}
}
import SwiftUI
struct OkView: View {
var body: some View {
TabView {
UpcomingEventsView()
.tabItem {
Image(systemName: "calendar.circle")
Text("Upcoming")
}
PastEventsView()
.tabItem {
Image(systemName: "calendar.circle.fill")
Text("Past")
}
}
}
}
struct UpcomingEventsView: View {
@State private var events: [Event] = []
var body: some View {
List(events) { event in
Text(event.title)
}
.onAppear {
ApiService.fetchEvents(type: .upcoming) { result in
switch result {
case .success(let eventListing):
self.events = eventListing.data.upcoming
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
}
struct PastEventsView: View {
@State private var events: [Event] = []
var body: some View {
List(events) { event in
Text(event.title)
}
.onAppear {
ApiService.fetchEvents(type: .past) { result in
switch result {
case .success(let eventListing):
self.events = eventListing.data.past
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
}
enum EventType {
case upcoming
case past
}
class ApiService {
static let baseUrl = "https://dev.myhss.org.uk/api/v1/events/eventlist"
static let dateFormatter = DateFormatter()
static func fetchEvents(type: EventType, completion: @escaping (Result<EventListing, Error>) -> Void) {
let endpoint = type == .upcoming ? "events/upcoming" : "events/past"
guard let url = URL(string: "\(baseUrl)/\(endpoint)") else {
completion(.failure(ApiError.invalidUrl))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(ApiError.noData))
return
}
do {
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let eventListing = try decoder.decode(EventListing.self, from: data)
completion(.success(eventListing))
} catch let error {
completion(.failure(error))
}
}.resume()
}
enum ApiError: Error {
case invalidUrl
case noData
}
}
Вам нужно декодировать ответ json с верхнего уровня, вы не можете начать на один уровень ниже. Поэтому добавьте одну структуру, содержащую ключ data
, и используйте этот тип при декодировании. Кроме того, при печати ошибки декодирования используйте print(error)
, чтобы получить более подробное сообщение, чем при использовании .localizedDescription
Связанный: stackoverflow.com/questions/52110124/…
Не добавляйте свои попытки решить это к своему вопросу, это значительно усложняет понимание вопроса (особенно, когда вы удаляете исходный контент). Если ответ не работает, добавьте свой отзыв к этому ответу. А также найдите время, чтобы прочитать комментарии, которые люди дали.
если вы введете свой URL, https://dev.myhss.org.uk/api/v1/events/eventlist/events/upcoming
или с past
в свой браузер, вы увидите, что не получаете того, что ожидаете. Вот почему вы получаете ошибку. Поставьте print("--> response: \(String(data: data, encoding: .utf8))")
перед do{...}
и убедитесь сами.
Вы должны изменить свой DTO как
struct EventListing: Codable {
var status: Bool
var message: String
var data: Event2
}
struct Event2: Codable {
var upcoming: [Event]
var past: [Event]
}
struct Event: Codable, Identifiable {
var id: String // Add this property to conform to Identifiable
var title: String
var startDate: Date
var endDate: Date
var imageURL: URL?
var isChargeable: Bool
enum CodingKeys: String, CodingKey {
case id = "event_id"
case title = "event_title"
case startDate = "event_start_date"
case endDate = "event_end_date"
case imageURL = "event_img"
case isChargeable = "event_chargable_or_not"
}
}
в ваших кодах я не вижу часть данных, но это должно быть так. После изменения кода проверьте правильность URL и правильность поступления данных.
Попробуйте следующий код, используя URLRequest
с заголовком POST
вместо вашего URL, и
модифицированная модель Event
, особенно обратите внимание, var isChargeable: String?
не Bool
, у меня работает.
Кроме того, вам необходимо обратиться к документации сервера, чтобы определить, какие свойства являются необязательными, и соответствующим образом скорректировать код (добавить ?
).
struct ContentView: View {
var body: some View {
OkView()
}
}
struct OkView: View {
var body: some View {
TabView {
UpcomingEventsView()
.tabItem {
Image(systemName: "calendar.circle")
Text("Upcoming")
}
PastEventsView()
.tabItem {
Image(systemName: "calendar.circle.fill")
Text("Past")
}
}
}
}
struct UpcomingEventsView: View {
@State private var events: [Event] = []
var body: some View {
List(events) { event in
Text(event.title)
}
.onAppear {
ApiService.fetchEvents(type: .upcoming) { result in
switch result {
case .success(let eventListing):
self.events = eventListing.data.upcoming
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
}
struct PastEventsView: View {
@State private var events: [Event] = []
var body: some View {
List(events) { event in
Text(event.title)
}
.onAppear {
ApiService.fetchEvents(type: .past) { result in
switch result {
case .success(let eventListing):
self.events = eventListing.data.past
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
}
enum EventType {
case upcoming
case past
}
class ApiService {
static let baseUrl = "https://dev.myhss.org.uk/api/v1/events/eventlist"
static let dateFormatter = DateFormatter()
static func fetchEvents(type: EventType, completion: @escaping (Result<EventListing, Error>) -> Void) {
let eventType = type == .upcoming ? "1" : "0" // <--- here
// --- here
guard let url = URL(string: baseUrl) else {
completion(.failure(ApiError.invalidUrl))
return
}
// --- here
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpBody = "event_type=\(eventType)".data(using: .utf8)! // <--- here
// --- here (with: request)
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(ApiError.noData))
return
}
// print("\n----> data: \(String(data: data, encoding: .utf8) as AnyObject) \n")
do {
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let eventListing = try decoder.decode(EventListing.self, from: data)
completion(.success(eventListing))
} catch let error {
print("\n----> decoding error: \(error) \n")
completion(.failure(error))
}
}.resume()
}
enum ApiError: Error {
case invalidUrl
case noData
}
}
struct EventListing: Codable {
let status: Bool
let message: String
let data: DataClass
}
struct DataClass: Codable {
let upcoming: [Event]
let past: [Event]
}
struct Event: Codable, Identifiable {
var id: String // Add this property to conform to Identifiable
var title: String
var startDate: Date
var endDate: Date
var imageURL: URL?
var isChargeable: String? // <--- here
enum CodingKeys: String, CodingKey {
case id = "event_id"
case title = "event_title"
case startDate = "event_start_date"
case endDate = "event_end_date"
case imageURL = "event_img"
case isChargeable = "event_chargable_or_not"
}
}
Похоже, есть проблема с вашим php-сервисом. Он возвращает
405 Method Not Allowed