skip to Main Content

Aim: To show the API data into Tab bar using SwiftUI

What i tried : I tried to implement the API using model and implemented it into tab view.

What is the issue: i have tried to change my model many time, still the data is not correctly fetched.

Getting error: The data couldn’t be read because it isn’t in the correct format.

API url: https://dev.myhss.org.uk/api/v1/events/eventlist

Optional parameter(can be used):
key : event_type
value: 1(1- upcoming events, 2- past events)

body: x-www-form-unlencoded

Ideal response:

{
    "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
            }
        ]
    }
}

Updated the suggested code from the answer , but still, the issue persist,
Error: The data couldn’t be read because it isn’t in the correct format.

SwiftUI code:

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
    }
}

2

Answers


  1. You should change your DTO as

     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"
        }
    } 
    

    in your codes i can not see the data part but that should be like these. After change code please check the url is correct and data is coming correctly.

    Login or Signup to reply.
  2. Try the following code, using a URLRequest with a POST header, instead of your url, and
    modified Event model, note especially, the var isChargeable: String? not Bool, works for me.

    In addition, you need to consult the server docs, to determine which properties are optional, and adjust the code (add ?) accordingly.

    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"
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search