Accessing data after calling an API - swift

first, I'm very (very) new to Swift programming. Challenging but so interesting!
Right now, in a Playground, I'm trying to fetch the data from a JSON that I can access using a URL.
I need to store the data somewhere (in this case I need to store an array of BixiStationViewModel so I can later on play with the data I fetch from the URL.
I think the issue is coming from the asynchronous process that is fetching the data and then having my code processing it.
You can see at the end of the code the print(allBixi.allStations) statement: it returns an empty array.
import Foundation
// JSON structure
struct BixiStationDataModel: Codable {
let lastUpdated, ttl: Int?
let data: StationsData?
enum CodingKeys: String, CodingKey {
case lastUpdated = "last_updated"
case ttl, data
}
}
struct StationsData: Codable {
let stations: [StationData]?
}
struct StationData: Codable {
let stationID: String?
let numBikesAvailable, numEbikesAvailable, numBikesDisabled, numDocksAvailable: Int?
let numDocksDisabled, isInstalled, isRenting, isReturning: Int?
let lastReported: Int?
let eightdHasAvailableKeys: Bool?
let eightdActiveStationServices: [EightdActiveStationService]?
enum CodingKeys: String, CodingKey {
case stationID = "station_id"
case numBikesAvailable = "num_bikes_available"
case numEbikesAvailable = "num_ebikes_available"
case numBikesDisabled = "num_bikes_disabled"
case numDocksAvailable = "num_docks_available"
case numDocksDisabled = "num_docks_disabled"
case isInstalled = "is_installed"
case isRenting = "is_renting"
case isReturning = "is_returning"
case lastReported = "last_reported"
case eightdHasAvailableKeys = "eightd_has_available_keys"
case eightdActiveStationServices = "eightd_active_station_services"
}
}
struct EightdActiveStationService: Codable {
let id: String?
}
// Calling the API
class WebserviceBixiStationData {
func loadBixiStationDataModel(url: URL, completion: #escaping ([StationData]?) -> ()) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(nil)
return
}
let response = try? JSONDecoder().decode(BixiStationDataModel.self, from: data)
if let response = response {
DispatchQueue.main.async {
completion(response.data?.stations)
}
}
}.resume()
}
}
// Data Model
class BixiStationViewModel {
let id = UUID()
let station: StationData
init(station: StationData) {
self.station = station
}
var stationID: String {
return self.station.stationID ?? ""
}
var numBikesAvailable: Int {
return self.station.numBikesAvailable ?? 0
}
var numDocksAvailable: Int {
return self.station.numDocksAvailable ?? 0
}
var isInstalled: Int {
return self.station.isInstalled ?? 0
}
var isReturning: Int {
return self.station.isReturning ?? 0
}
}
class BixiStationListModel {
init() { fetchBixiApiDataModel() }
var allStations = [BixiStationViewModel]()
private func fetchBixiApiDataModel() {
guard let url = URL(string: "https://api-core.bixi.com/gbfs/en/station_status.json") else {
fatalError("URL is not correct")
}
WebserviceBixiStationData().loadBixiStationDataModel(url: url) { stations in
if let stations = stations {
self.allStations = stations.map(BixiStationViewModel.init)
}
}
}
}
// Checking if the data has been dowloaded
let allBixi = BixiStationListModel()
print(allBixi.allStations)
How can I fix the code so I could access the values in the var allStations = [BixiStationViewModel]()
Thanks in advance, I've Benn working on it this issue for a while now and this would help me a lot in my app development

In a playground you need continuous execution to work with a url response.
Add PlaygroundPage.current.needsIndefiniteExecution = true to the top of your file (doesn't matter where it's added but I always do the top)
To get your data to print, add a print statement inside your loadBixiStationDataModel callback
WebserviceBixiStationData().loadBixiStationDataModel(url: url) { stations in
if let stations = stations {
self.allStations = stations.map(BixiStationViewModel.init)
print(stations)
}
}

Related

Fetch API Swift

Previously the API I was working on was as below
{
"kurumsicilno": 457.0,
"yillikizin": 30.0,
}
and I built my model as below
struct Leave: Decodable, Identifiable {
var id: Double? {
return registerNumber
}
let registerNumber: Double
let annualLeave: Double
enum CodingKeys: String, CodingKey {
case registerNumber = "kurumsicilno"
case annualLeave = "yillikizin"
}
}
This was my network function
let task = session.dataTask(with: request) { (data, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let result = try decoder.decode(Leave.self, from: safeData)
DispatchQueue.main.async {
completion(result)
}
} catch {
print(error)
}
}
}
}
task.resume()
But for some reason, they have changed the API to this.
{
"isSucceed": true,
"singleData": {
"sicilNo": "457",
"yillikIzin": "30",
},
How should I modify my model so that I can reach and fetch the data as before?
Just create a new root struct
struct Response : Decodable {
let isSucceed: Bool
let singleData: Leave
}
and you have to change the types and (one of) the CodingKeys
struct Leave: Decodable, Identifiable {
var id: String { // no Optional!!
return registerNumber
}
let registerNumber: String
let annualLeave: String
enum CodingKeys: String, CodingKey {
case registerNumber = "sicilNo"
case annualLeave = "yillikIzin" // is this really a capital `I`?
}
}
Finally change the decoding code
let result = try decoder.decode(Response.self, from: safeData)
DispatchQueue.main.async {
completion(result.singleData)
}
And you might have to manage the type change Double → String in your other code.

No results view freezes; doesn't change after adding new input

I am creating mobile GitHub repository search app and I've just figured out how to handle no responses with my friend, but this solution doesn't allow to change screens between the the noResults and List views (I commented them for you in var body).
The code:
import SwiftUI
import Combine
private final class ContentViewState: ObservableObject {
#Published var isLoading = false
#Published var query = ""
#Published var stuff = [String]()
#Published var noResults = false
private var subscription: AnyCancellable?
func fetchRepos(query: String) {
isLoading = true
subscription = Just("test")
.delay(for: 2, scheduler: RunLoop.main)
.sink(receiveValue: {[weak self] (title: String) in
self?.isLoading = false
self?.stuff.append(title)
})
}
}
struct ContentView: View {
#StateObject private var state = ContentViewState()
#State private var items = [Item]()
var body: some View {
VStack {
if state.isLoading {
ProgressView()
}
//noResults view here
else if state.noResults {
HStack {
TextField("Enter search", text: $state.query)
Button("Search") {
state.fetchRepos(query: state.query)
}
}
Text("No results... Try again!")
}
//List view here
else {
HStack {
TextField("Enter search", text: $state.query)
Button("Search") {
state.fetchRepos(query: state.query)
}
}
List(items, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.fullName).font(.headline)
Text(item.urlCode)
}
}.task {
await loadData()
}
}
}
}
func loadData() async {
guard let url = URL(string: "https://api.github.com/search/repositories?q=" + state.query + "&per_page=20") else
{
DispatchQueue.main.async {
state.noResults = true }
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decodedResponse = try? JSONDecoder().decode(Root.self, from: data) {
items = decodedResponse.items
if items.isEmpty {
DispatchQueue.main.async {
state.noResults = true }
}
}
} catch {
DispatchQueue.main.async {
state.noResults = true}
}
}
}
The problem is, if you pass the valid input, i.e.: Data, Core, then you get the search results loaded into the app.
If you pass the invalid input, i.e.: 'Brigmhnst', you get noResults view, but if you pass the valid input from that state, you won't get the List view.
Both views look almost the same. The only difference is one of them is else if and the other is else and I can't have two 'elses', can I?
I have already tried adding the start-point view (similar to list View) bound to else condition, another list View with else if and created an else condition inside do in func loadData that would run if the items were not empty. The app seemed to work, but the first start-point view wouldn't swap to listView after passing the data in it (it is not in the code, since this solution worked worse than that I already have, but I can add it if you want to see it).
There are a few problems with the code you have.
First, I believe the models (which are not included) are incorrect. I pasted the JSON into quicktype.io and this is the result:
import Foundation
// MARK: - Repos
struct Repos: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [Item]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
// MARK: - Item
struct Item: Codable {
let id: Int
let nodeID, name, fullName: String
let itemPrivate: Bool
let owner: Owner
let htmlURL: String
let itemDescription: String?
let fork: Bool
let url, forksURL: String
let keysURL, collaboratorsURL: String
let teamsURL, hooksURL: String
let issueEventsURL: String
let eventsURL: String
let assigneesURL, branchesURL: String
let tagsURL: String
let blobsURL, gitTagsURL, gitRefsURL, treesURL: String
let statusesURL: String
let languagesURL, stargazersURL, contributorsURL, subscribersURL: String
let subscriptionURL: String
let commitsURL, gitCommitsURL, commentsURL, issueCommentURL: String
let contentsURL, compareURL: String
let mergesURL: String
let archiveURL: String
let downloadsURL: String
let issuesURL, pullsURL, milestonesURL, notificationsURL: String
let labelsURL, releasesURL: String
let deploymentsURL: String
let createdAt, updatedAt, pushedAt: String // Quicktype said these were dates, that didn't work
let gitURL, sshURL: String
let cloneURL: String
let svnURL: String
let homepage: String?
let size, stargazersCount, watchersCount: Int
let language: JSONNull?
let hasIssues, hasProjects, hasDownloads, hasWiki: Bool
let hasPages: Bool
let forksCount: Int
let mirrorURL: JSONNull?
let archived, disabled: Bool
let openIssuesCount: Int
let license: JSONNull?
let allowForking, isTemplate: Bool
let topics: [String]
let visibility: String
let forks, openIssues, watchers: Int
let defaultBranch: String
let score: Int
enum CodingKeys: String, CodingKey {
case id
case nodeID = "node_id"
case name
case fullName = "full_name"
case itemPrivate = "private"
case owner
case htmlURL = "html_url"
case itemDescription = "description"
case fork, url
case forksURL = "forks_url"
case keysURL = "keys_url"
case collaboratorsURL = "collaborators_url"
case teamsURL = "teams_url"
case hooksURL = "hooks_url"
case issueEventsURL = "issue_events_url"
case eventsURL = "events_url"
case assigneesURL = "assignees_url"
case branchesURL = "branches_url"
case tagsURL = "tags_url"
case blobsURL = "blobs_url"
case gitTagsURL = "git_tags_url"
case gitRefsURL = "git_refs_url"
case treesURL = "trees_url"
case statusesURL = "statuses_url"
case languagesURL = "languages_url"
case stargazersURL = "stargazers_url"
case contributorsURL = "contributors_url"
case subscribersURL = "subscribers_url"
case subscriptionURL = "subscription_url"
case commitsURL = "commits_url"
case gitCommitsURL = "git_commits_url"
case commentsURL = "comments_url"
case issueCommentURL = "issue_comment_url"
case contentsURL = "contents_url"
case compareURL = "compare_url"
case mergesURL = "merges_url"
case archiveURL = "archive_url"
case downloadsURL = "downloads_url"
case issuesURL = "issues_url"
case pullsURL = "pulls_url"
case milestonesURL = "milestones_url"
case notificationsURL = "notifications_url"
case labelsURL = "labels_url"
case releasesURL = "releases_url"
case deploymentsURL = "deployments_url"
case createdAt = "created_at"
case updatedAt = "updated_at"
case pushedAt = "pushed_at"
case gitURL = "git_url"
case sshURL = "ssh_url"
case cloneURL = "clone_url"
case svnURL = "svn_url"
case homepage, size
case stargazersCount = "stargazers_count"
case watchersCount = "watchers_count"
case language
case hasIssues = "has_issues"
case hasProjects = "has_projects"
case hasDownloads = "has_downloads"
case hasWiki = "has_wiki"
case hasPages = "has_pages"
case forksCount = "forks_count"
case mirrorURL = "mirror_url"
case archived, disabled
case openIssuesCount = "open_issues_count"
case license
case allowForking = "allow_forking"
case isTemplate = "is_template"
case topics, visibility, forks
case openIssues = "open_issues"
case watchers
case defaultBranch = "default_branch"
case score
}
}
// MARK: - Owner
struct Owner: Codable {
let login: String
let id: Int
let nodeID: String
let avatarURL: String
let gravatarID: String
let url, htmlURL, followersURL: String
let followingURL, gistsURL, starredURL: String
let subscriptionsURL, organizationsURL, reposURL: String
let eventsURL: String
let receivedEventsURL: String
let type: String
let siteAdmin: Bool
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case htmlURL = "html_url"
case followersURL = "followers_url"
case followingURL = "following_url"
case gistsURL = "gists_url"
case starredURL = "starred_url"
case subscriptionsURL = "subscriptions_url"
case organizationsURL = "organizations_url"
case reposURL = "repos_url"
case eventsURL = "events_url"
case receivedEventsURL = "received_events_url"
case type
case siteAdmin = "site_admin"
}
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
Next, your fetchRepos function isn't doing anything. It's really only presenting a loader and stopping that later. The loadData function is actually what you want to use.
Finally, if you keep the task of loadData near the end of the code, you'll be in an infinite loop.
So these changes will clean it up a bit.
#MainActor private final class ContentViewState: ObservableObject {
#Published var isLoading = false
#Published var query = ""
#Published var items = [Item]()
func loadData(query: String = "") async {
guard let url = URL(string: "https://api.github.com/search/repositories?q=" + query + "&per_page=20") else {
return
}
isLoading = true
defer { isLoading = false }
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decodedResponse = try JSONDecoder().decode(Repos.self, from: data)
items = decodedResponse.items
} catch {
print("error \(error)")
}
}
}
struct ContentView: View {
#StateObject private var state = ContentViewState()
var searchView: some View {
HStack {
TextField("Enter search", text: $state.query)
Button("Search") {
Task {
await state.loadData(query: state.query)
}
}
}
}
var body: some View {
if state.isLoading {
ProgressView()
} else {
VStack {
searchView
if state.items.isEmpty {
Text("No results...\nTry again!")
} else {
List(state.items) { item in
VStack(alignment: .leading) {
Text(item.fullName).font(.headline)
Text(item.htmlURL)
}.padding(.vertical)
}
}
}.padding()
}
}
}
The last thing you'll need to do is conform Item to Identifiable
extension Item: Identifiable {} // You could also just add Identifiable to the actual declaration

Firebase - How do I read this map via embedded structs?

I am reading data from Firestore to be able to populate into expanding tableview cells. I have a really simple struct:
protocol PlanSerializable {
init?(dictionary:[String:Any])
}
struct Plan{
var menuItemName: String
var menuItemQuantity: Int
var menuItemPrice: Double
var dictionary: [String: Any] {
return [
"menuItemName": menuItemName,
"menuItemQuantity": menuItemQuantity,
"menuItemPrice": menuItemPrice
]
}
}
extension Plan : PlanSerializable {
init?(dictionary: [String : Any]) {
guard let menuItemName = dictionary["menuItemName"] as? String,
let menuItemQuantity = dictionary["menuItemQuantity"] as? Int,
let menuItemPrice = dictionary["menuItemPrice"] as? Double
else { return nil }
self.init(menuItemName: menuItemName, menuItemQuantity: menuItemQuantity, menuItemPrice: menuItemPrice)
}
}
And this is embedded in this struct:
protocol ComplainSerializable {
init?(dictionary:[String:Any])
}
struct Complain{
var status: Bool
var header: String
var message: String
var timeStamp: Timestamp
var email: String
var planDetails: Plan
var dictionary: [String: Any] {
return [
"status": status,
"E-mail": header,
"Message": message,
"Time_Stamp": timeStamp,
"User_Email": email,
"planDetails": planDetails
]
}
}
extension Complain : ComplainSerializable {
init?(dictionary: [String : Any]) {
guard let status = dictionary["status"] as? Bool,
let header = dictionary["E-mail"] as? String,
let message = dictionary["Message"] as? String,
let timeStamp = dictionary["Time_Stamp"] as? Timestamp,
let email = dictionary["User_Email"] as? String,
let planDetails = dictionary["planDetails"] as? Plan
else { return nil }
self.init(status: status, header: header, message: message, timeStamp: timeStamp, email: email, planDetails: planDetails)
}
}
However, I am not able to query any data from Firestore which looks like this:
Here is my query, although I am just reading all the files:
let db = Firestore.firestore()
var messageArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
self.messageArray = documentSnapshot!.documents.compactMap({Complain(dictionary: $0.data())})
for plan in self.messageArray {
print("\(plan.email)")
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
What am I doing wrong?
EDIT:
As suggested, here is the updated embedded struct:
import Foundation
// MARK: - Complain
struct Complain: Codable {
let eMail, message, timeStamp, userEmail: String
let status: Bool
let planDetails: PlanDetails
enum CodingKeys: String, CodingKey {
case eMail = "E-mail"
case message = "Message"
case timeStamp = "Time_Stamp"
case userEmail = "User_Email"
case status, planDetails
}
}
// MARK: - PlanDetails
struct PlanDetails: Codable {
let menuItemName: String
let menuItemQuantity: Int
let menuItemPrice: Double
}
Using quicktype.io, you can generate the struct. From there, all you need to do is run this tiny fragment of code within your response handler.
var compainArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
guard let snapshot = documentSnapshot else {return}
for document in snapshot.documents {
if let jsonData = try? JSONSerialization.data(withJSONObject: document.data()){
if let converted = try? JSONDecoder().decode(Complain.self, from: jsonData){
self.compainArray.append(converted)
}
}
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
Which will handle the looping, and mapping of certain variables. Let me know if you have any trouble with this.

Swift: Dictionary with multi-types value

I get difficulties to deal with below code, the value in dict sectionMoviesBundle = [HomeSection: [T]] could be MovieViewModel or ActorViewModel which two are struct type.
So generally how could I deal with this dict [String: [typeA or typeB...]], using generic or AnyObject like nowPlaying.results.map(MovieViewModel.init) as AnyObject? And how to implement it in code?
import SwiftUI
import Combine
class MovieListViewModel: ObservableObject {
private var webService = WebService()
private var cancellableSet: Set<AnyCancellable> = []
#Published var sectionMoviesBundle = [HomeSection: [T]]() // Don't know how to deal with it now=.=!
func getSectionMoviesBundle() {
webService.getSectionsPublisher()
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { status in
switch status {
case .finished:
break
case .failure(let error):
print("ERROR: \(error)")
break
}
}) { (nowPlaying, popular, upComing, topActor) in
self.sectionMoviesBundle[.NowPlaying] = nowPlaying.results.map(MovieViewModel.init)
self.sectionMoviesBundle[.Popular] = popular.results.map(MovieViewModel.init)
self.sectionMoviesBundle[.Upcoming] = upComing.results.map(MovieViewModel.init)
self.sectionMoviesBundle[.TopActor] = topActor.results.map(ActorViewModel.init)
}.store(in: &self.cancellableSet)
}
}
Some alternative that I think you can try
[String: [Any]]
Create a Protocol and implement it to all Struct that you want to use in the dictionary and use [String: [someProtocol]]
to insert it to the Dictionary, you can use your current code
One of the many approach that you can use is:
enum MovieSectionType {
case actor, movie
}
protocol MovieSectionViewModelProtocol {
var sectionType: MovieSectionType { get }
var name: String { get set }
}
struct MovieViewModel: MovieSectionViewModelProtocol{
var sectionType: MovieSectionType = .movie
var name: String
var title: String
}
struct ActorViewModel: MovieSectionViewModelProtocol {
var sectionType: MovieSectionType = .actor
var name: String
var birthday: Date
}
class MovieListViewModel: ObservableObject {
private var webService = WebService()
private var cancellableSet: Set<AnyCancellable> = []
#Published var sectionMoviesBundle = [HomeSection: [MovieSectionViewModelProtocol]]() // Don't know how to deal with it now=.=!
func getSectionMoviesBundle() {
self.sectionMoviesBundle[.Upcoming] = [MovieViewModel(name: "Movie", title: "Movie Title")]
self.sectionMoviesBundle[.TopActor] = [ActorViewModel(name: "Actor", birthday: Date())]
let item = self.sectionMoviesBundle[.Upcoming]!.first!
switch item.sectionType {
case .actor:
guard let actor = item as? ActorViewModel else {
return
}
print(actor.birthday)
break
case .movie:
guard let movie = item as? MovieViewModel else {
return
}
print(movie.title)
break
}
}
}
You could use a “sum type”, which in Swift is an enum:
enum SectionContent {
case actors([ActorViewModel])
case movies([MovieViewModel])
}
#Published var sectionMoviesBundle = [HomeSection: SectionContent]()
func getSectionMoviesBundle() {
webService.getSectionsPublisher()
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { status in
switch status {
case .finished:
break
case .failure(let error):
print("ERROR: \(error)")
break
}
}) { (nowPlaying, popular, upComing, topActor) in
self.sectionMoviesBundle[.NowPlaying] = .movies(nowPlaying.results.map(MovieViewModel.init))
self.sectionMoviesBundle[.Popular] = .movies(popular.results.map(MovieViewModel.init))
self.sectionMoviesBundle[.Upcoming] = .movies(upComing.results.map(MovieViewModel.init))
self.sectionMoviesBundle[.TopActor] = .actors(topActor.results.map(ActorViewModel.init))
}.store(in: &self.cancellableSet)
}

can not convert json to struct object

I want to parse JSON data into a struct object but i can't do it.
Actually the code is in different files but here i'm posting it as a whole.
Here is my code :
import Foundation
struct dataResponse: Decodable {
var results: [userData]
init(from decoder: Decoder) throws {
var results = [userData] ()
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let route = try? container.decode(userData.self) {
results.append(route)
}
else {
_ = try? container.decode(dummyData.self)
}
}
self.results = results
}
}
private struct dummyData: Decodable { }
enum dataError: Error {
case dataUnavailable
case cannotProcessData
}
struct userData: Codable {
var avatar: String
var city: String
var contribution: Int
var country: String
var friendOfCount: Int
var handle: String
var lastOnlineTimeSeconds: Int
var maxRank: String
var maxRating: Int
var organization: String
var rank: String
var rating: Int
var registrationTimeSeconds: Int
var titlePhoto: String
}
struct dataRequest {
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<[userData], dataError>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(.dataUnavailable))
print("-------bye-bye--------")
return
}
do {
print("-------entered--------")
// let dataresponse = try JSONDecoder().decode([userData].self, from: data)
// print(type(of: dataresponse))
// completionHandler(.success(dataresponse))
let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(jsonResult)
completionHandler(.success(jsonResult as! [userData]))
}
catch {
completionHandler(.failure(.cannotProcessData))
}
}.resume()
}
}
here userData is my struct
the error says : Could not cast value of type '__NSDictionaryM' (0x7fff8fe2dab0) to 'NSArray' (0x7fff8fe2dd30).
I would appreciate if anyone helps, thanks.
You are making a very common mistake.
You are ignoring the root object which is a dictionary and causes the error.
struct Root: Decodable {
let status : String
let result: [UserData]
}
struct UserData: Decodable {
let avatar: String
let city: String
let contribution: Int
let country: String
let friendOfCount: Int
let handle: String
let lastOnlineTimeSeconds: Int
let maxRank: String
let maxRating: Int
let organization: String
let rank: String
let rating: Int
let registrationTimeSeconds: Int
let titlePhoto: String
}
Forget JSONSerialization and use only JSONDecoder
And it's not a good idea to return meaningless enumerated errors. Use Error and return the real error.
You get the array with dataresponse.result
struct DataRequest { // name structs always with starting capital letter
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<Root, Error>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(error!))
print("-------bye-bye--------")
return
}
do {
print("-------entered--------")
let dataresponse = try JSONDecoder().decode(Root.self, from: data)
completionHandler(.success(dataresponse))
}
catch {
completionHandler(.failure(error))
}
}.resume()
}
}
And consider that if status is not "OK" the JSON response could be different.
Your problem is you are using the wrong struct. Create and use a Response struct for decoding. You need to keep your Types to have the first letter in capital to avoid confusion. You could use the JSONDecoder() maybe you are using something that doesn't have the correct format. Try the below code.
struct Response: Codable {
var result: [UserData]
}
enum DataError: Error {
case dataUnavailable, cannotProcessData
}
struct DataRequest {
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<Response, DataError>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(.dataUnavailable))
return
}
do {
let dataresponse = try JSONDecoder().decode(Response.self, from: data)
completionHandler(.success(dataresponse))
} catch {
completionHandler(.failure(.cannotProcessData))
}
}.resume()
}
}
Note: Parameters in UserData always needs to right. Try with and Empty struct to check if everything else is working then proceed to adding the variable one-by-one.