I need some assistance again regarding my codes. Bit confuse on how will the empty data be reflected from APIService going to ViewController.
Here's the JSON
{
"responseMessage": "No record Found",
"data": []
}
As you can see the data is nil.
Here's the APIService
typealias getDoctorPayoutSummaryTaskCompletion = (_ latestPayoutSummary: DoctorPayoutSummary?, _ error: NetworkError?) -> Void
static func getDoctorPayoutSummary(doctorNumber: String, periodId: Int, completion: #escaping getDoctorPayoutSummaryTaskCompletion) {
guard let latestPayoutSummaryURL = URL(string: "\(Endpoint.LatestCreditedAmount.latestPayoutSummary)?periodId=\(periodId)&doctorNumber=\(doctorNumber)") else {
completion(nil, .invalidURL)
return
}
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.getAllTasks { (tasks) in
tasks.forEach({ $0.cancel() })
}
Alamofire.request(latestPayoutSummaryURL, method: .get, encoding: JSONEncoding.default).responseJSON { (response) in
guard HelperMethods.reachability(responseResult: response.result) else {
completion(nil, .noNetwork)
return
}
guard let statusCode = response.response?.statusCode else {
completion(nil, .noStatusCode)
return
}
switch(statusCode) {
case 200:
guard let jsonData = response.data else {
completion(nil, .invalidJSON)
return
}
let decoder = JSONDecoder()
do {
let currentPayoutSummary = try decoder.decode(RootDoctorPayoutSummary.self, from: jsonData)
print(periodId)
print(currentPayoutSummary.data ?? "data is nil")
print(currentPayoutSummary.data ?? "response is nil")
completion(currentPayoutSummary.data, nil)
} catch {
completion(nil, .invalidJSON)
print(error)
}
case 400: completion(nil, .badRequest)
case 404: completion(nil, .noRecordFound)
default:
print("**UNCAPTURED STATUS CODE FROM (getDoctorPayoutSummary)\nSTATUS CODE: \(statusCode)")
completion(nil, .uncapturedStatusCode)
}
}
}
I tried to use breakpoints to track my codes and it does print data is nil if the data is empty in the APIService side. But unfortunately the getDoctorPayoutSummary function in the ViewController side doesn't recognized if the data is empty. It just recognizes if the data is not empty and it runs smoothly.
Here's the getDoctorPayoutSummary()
func getDoctorPayoutSummary(doctorNumber: String) {
SVProgressHUD.setBackgroundColor(.lightGray)
SVProgressHUD.show(withStatus: "Processing...")
APIService.DoctorLatestCreditedAmount.getDoctorPayoutSummary(doctorNumber: doctorNumber, periodId: doctorPayoutWeek[0].periodId!) { (payoutsummary, error) in
guard let payoutSummaryDetails = payoutsummary, error == nil else {
if let networkError = error {
switch networkError {
case .noRecordFound:
self.noRecordView.isHidden = false
self.creditedAmountLabel.isHidden = true
case .noNetwork:
let alertController = UIAlertController(title: "No Network", message: "\(networkError.rawValue)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
default:
let alertController = UIAlertController(title: "Error", message: "There is something went wrong. Please try again", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
}
}
SVProgressHUD.dismiss()
return
}
self.payoutSummary = payoutSummaryDetails
print(payoutSummaryDetails)
if self.payoutSummary == nil {
self.noRecordView.isHidden = false
SVProgressHUD.dismiss()
return
}
self.creditedAmountLabel.text = "₱\(self.payoutSummary.creditedAmount ?? 0.0)"
self.getPatientList()
self.noRecordView.isHidden = true
self.week1TableView.reloadData()
SVProgressHUD.dismiss()
return
}
}
My apology if I ask too much but I really need help to solve this one so I can move on with other tasks. Asking for your little time to help me. Thank you so much.
If data is empty it will return empty array, so check
if currentPayoutSummary.data.isEmpty {
print("Data is empty !!")
completion(nil, .noRecordFound)
}
or
if currentPayoutSummary.data.count == 0 {
print("Data is empty !!")
completion(nil, .noRecordFound)
}
Two options:
Check responseMessage
let currentPayoutSummary = try decoder.decode(RootDoctorPayoutSummary.self, from: jsonData)
if currentPayoutSummary.responseMessage == "No record Found" {
completion(nil, .noRecordFound)
} else {
completion(currentPayoutSummary.data, nil)
}
Check if data is empty
let currentPayoutSummary = try decoder.decode(RootDoctorPayoutSummary.self, from: jsonData)
if currentPayoutSummary.data.isEmpty {
completion(nil, .noRecordFound)
} else {
completion(currentPayoutSummary.data, nil)
}
Use responseData instead of responseJSON
Related
I'm working on my app (iOS) to get AdMob and AdSense earnings information. But I've been having trouble getting specifics from them. I've already created the credentials and client ID from my Google account, but I'm not sure where to put them.
I tried carefully following many methods from this link but was never successful.
My first step: During startup, check to see if you are logged in or out.
import FirebaseAuth
import GoogleSignIn
var currentPID = ""
func checkGoogleAccountStatus() {
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
if error != nil || user == nil {
print("Signed out")
self.loginButton()
} else {
print("Signed in")
let userAccess: String = user!.authentication.accessToken
self.googledSignedInSuccess(googleToken: userAccess, tokenID: user!.authentication.idToken!)
let dateformatter = DateFormatter()
dateformatter.dateFormat = "MMMM d, yyyy h:mm:ss a"
let expiredToken = user?.authentication.accessTokenExpirationDate
print("Token Expired: \(dateformatter.string(from: expiredToken!))")
}
}
}
Successful
My second step: When I tapped the button to log in, an alert controller appeared to see if the log in was successful. It will display the alert controller's profile picture, name, and email address.
#objc func loginTapped() {
let adMobScope = "https://www.googleapis.com/auth/admob.report"
let adSenseScope = "https://www.googleapis.com/auth/adsensehost"
let additionalScopes = [adMobScope,adSenseScope]
let signInConfig = GIDConfiguration.init(clientID: "<My URL Schemes>")
GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self, hint: nil, additionalScopes: additionalScopes) { user, error in
guard error == nil else { return }
guard let user = user else { return }
if let profiledata = user.profile {
let grantedScopes = user.grantedScopes
if grantedScopes == nil || !grantedScopes!.contains(adMobScope) {
print("AdMob not Granted...")
} else {
print("AdMob Granted!")
}
if grantedScopes == nil || !grantedScopes!.contains(adSenseScope) {
print("AdSense not Granted...")
} else {
print("AdSense Granted!")
}
//let userId: String = user.userID ?? ""
let givenName: String = profiledata.givenName ?? ""
let familyName: String = profiledata.familyName ?? ""
let email: String = profiledata.email
let userToken: String = user.authentication.idToken!
let userAccess: String = user.authentication.accessToken
let credential = GoogleAuthProvider.credential(withIDToken: userToken, accessToken: userAccess)
Auth.auth().signIn(with: credential) { result, error in
if let error = error {
print(error.localizedDescription)
let alert = UIAlertController(title: "Error", message: "Something went wrong, please try again.", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .cancel, handler: {_ in return })
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
if let imgurl = user.profile?.imageURL(withDimension: 300) {
let absoluteurl: String = imgurl.absoluteString
let alert = UIAlertController(title: "\(givenName) \(familyName)", message: "\n\n\n\n\n\n\n\(email)\nLogin Successful", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .cancel, handler: {_ in
// MARK: Do something to update
self.checkGoogleAccountStatus()
})
let imgViewTitle = UIImageView()
imgViewTitle.translatesAutoresizingMaskIntoConstraints = false
imgViewTitle.layer.borderColor = UIColor(named: "Font Color")?.cgColor
imgViewTitle.layer.borderWidth = 3
imgViewTitle.layer.cornerRadius = 50
imgViewTitle.clipsToBounds = true
alert.view.addSubview(imgViewTitle)
imgViewTitle.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor, constant: -28).isActive = true
imgViewTitle.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor).isActive = true
imgViewTitle.widthAnchor.constraint(equalToConstant: 100).isActive = true
imgViewTitle.heightAnchor.constraint(equalToConstant: 100).isActive = true
DispatchQueue.global().async {
if let data = try? Data(contentsOf: URL(string: absoluteurl)! ) { if let image = UIImage(data: data) { DispatchQueue.main.async { imgViewTitle.image = image } } }
}
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
} else {
let alert = UIAlertController(title: "\(givenName) \(familyName)", message: "\(email)\nLogin Successful", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .cancel, handler: {_ in
// MARK: Do something to update
self.checkGoogleAccountStatus()
})
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}
}
}
}
When I logged in, it asked for permission to access AdMob and AdSense, followed by a pop-up alert that said I had successfully logged in.
My third step: Getting the PID from Google AdMob / AdSense
import CurlDSL
import Gzip
func googledSignedInSuccess(googleToken: String, tokenID: String) {
guard let url = URL(string: "https://admob.googleapis.com/v1/accounts/") else { return }
do {
try CURL(#"curl -H "Authorization: Bearer \#(googleToken)" "\#(url)""#).run { data, response, error in
if let error = error { print("Error took place \(error)"); return }
if let response = response as? HTTPURLResponse {
if response.statusCode != 200 {
print("Error: \(response)")
} else {
if let data = data {
do {
if let rawJSON = try? JSONDecoder().decode(GetAdMobInfo.self, from: data) {
currentPID = rawJSON.account[0].publisherID
print("Successful: \(currentPID)")
self.adMob_gettingReport(pid: currentPID, token: googleToken)
}
}
}
}
}
}
} catch { print("Failed.") }
}
struct GetAdMobInfo: Codable {
let account: [Account]
}
struct Account: Codable {
let name, publisherID, reportingTimeZone, currencyCode: String
enum CodingKeys: String, CodingKey {
case name
case publisherID = "publisherId"
case reportingTimeZone, currencyCode
}
}
It was success, I was able to get my PID and writed to currentPID as string.
My final step, which failed:
func adMob_gettingReport(pid: String, token: String) {
guard let url = URL(string: "https://admob.googleapis.com/v1/accounts/\(pid)/mediationReport:generate") else { return }
let reportData = "--data #- << EOF {\"report_spec\": {\"date_range\": {\"start_date\": {\"year\": 2020, \"month\": 4, \"day\": 1}, \"end_date\": {\"year\": 2020, \"month\": 4, \"day\": 1} },\"dimensions\": [\"AD_SOURCE\", \"AD_UNIT\", \"PLATFORM\"], \"metrics\": [\"ESTIMATED_EARNINGS\"]}} EOF"
do {
try CURL(#"curl -X POST "\#(url)" -H "Authorization: Bearer \#(token)" -H "Content-Type: application/json" \#(reportData)"#).run { data, response, error in
if let error = error { print("Error took place \(error)"); return }
if let response = response as? HTTPURLResponse {
if response.statusCode != 200 {
print("Error: \(response)")
} else {
if let data = data {
print("Getting AdMob Successful")
let decompressedData: Data
if data.isGzipped { decompressedData = try! data.gunzipped() }
else { decompressedData = data }
var getLineFromString: [String] = []
getLineFromString += String(data: decompressedData, encoding: .utf8)!.components(separatedBy: "\n")
for checkLine in getLineFromString {
print("Line: \(checkLine)")
}
}
}
}
}
} catch { print("Failed.") }
}
Attempting to obtain earnings information from AdMob and AdSense, but it kept saying failing in print. This is where I've been for nearly two months. What did I overlook?
I am able to save VPN configuration. Please tell me how to make the server address variable or get a server address and what is the remote identifier for?
I have used this example for reference: Connect to a VPN with certificate - iOS/Swift
How can I solve this?
func connectVPN(){
do {
if let file = URL(string: "example.com") {
let data = try Data(contentsOf: file)
let json = try JSONSerialization.jsonObject(with: data, options: [])
if let object = json as? [String: String] {
// json is a dictionary
var data_VPN = object["VPN_data"]!
//print("Donebro:\(data_VPN)")
let certificate = data_VPN
let nsdata = certificate.data(using: .utf8)
let base64EncodedData = nsdata!.base64EncodedData()
print("base64StoreData:\(nsdata!)")
print("base64StoreNewData:\(base64EncodedData)")
var vpnManager = NEVPNManager.shared()
vpnManager.loadFromPreferences { error in
if vpnManager.`protocol` == nil{
let newIPSec = NEVPNProtocolIPSec()
newIPSec.serverAddress = ""
newIPSec.localIdentifier = ""
newIPSec.remoteIdentifier = ""
newIPSec.useExtendedAuthentication = true
newIPSec.identityData = base64EncodedData as! Data
newIPSec.authenticationMethod = NEVPNIKEAuthenticationMethod.certificate
print("VPNDATA:\(newIPSec)")
if #available(iOS 9, *) {
vpnManager.protocolConfiguration = newIPSec
} else {
vpnManager.`protocol` = newIPSec
}
vpnManager.isEnabled = true
vpnManager.saveToPreferences(completionHandler: { (error) -> Void in
if ((error) != nil) {
print("VPN Preferences error: 2")
}
else {
vpnManager.loadFromPreferences(completionHandler: { (error) in
if ((error) != nil) {
print("VPN Preferences error: 2")
}
else {
var startError: NSError?
do {
try vpnManager.connection.startVPNTunnel()
}
catch let error as NSError {
startError = error
print(startError)
}
catch {
print("Fatal Error")
fatalError()
}
if ((startError) != nil) {
print("VPN Preferences error: 3")
let alertController = UIAlertController( title: "Oops..", message: "Something went wrong while connecting to the VPN. Please try again.", preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction( UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.default,handler: nil))
self.present(alertController, animated: true, completion: nil)
print(startError)
}
else {
//VPNStatusDidChange(nil)
print("Start VPN")
}
}
})
}
})
}
} else if let object = json as? [Any] {
// json is an array
for anItem in object as! [Dictionary<String, AnyObject>] {
let industryName = anItem["VPN_data"] as! String
}
} else {
print("JSON is invalid")
}
} else {
print("no file")
}
} catch {
print(error.localizedDescription)
}
}
}
I have this json wherein hospitalNumber has value and there is instances that it returns null value. The hospitalNumber has significance since its a part of the parameter needed for the endpoint in API. Please see sample json:
{
"responseMessage": "Request successful",
"data": [
{
"hospitalNumber": null,
"patientName": "Manual Entry",
"totalAmount": 10339.8000,
"manualEntry": true
},
{
"hospitalNumber": "1111111",
"patientName": "test patient",
"totalAmount": 932.5000,
"manualEntry": false
}
]
}
And below is my APIService for the endpoint that will pull the json above.
typealias getPatientDetailsPerPayoutTaskCompletion = (_ patientDetailsPerPayout: [PatientPayoutDetails]?, _ error: NetworkError?) -> Void
//Patient procedure details per patient
//parameterName is .searchByHospitalNumber = "hospitalNumber"
static func getPatientDetailsPerPayout(periodId: Int, doctorNumber: String, parameterName: PatientParameter, hospitalNumber: String, manualEntry: Bool, completion: #escaping getPatientDetailsPerPayoutTaskCompletion) {
guard let patientDetailsPerPayoutURL = URL(string: "\(Endpoint.Patient.patientProcedureDetails)?periodId=\(periodId)&doctorNumber=\(doctorNumber)\(parameterName.rawValue)\(hospitalNumber)&manualEntry=\(manualEntry)") else {
completion(nil, .invalidURL)
return
}
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.getAllTasks { (tasks) in
tasks.forEach({ $0.cancel() })
}
Alamofire.request(patientDetailsPerPayoutURL, method: .get, encoding: JSONEncoding.default).responseJSON { (response) in
print(patientDetailsPerPayoutURL)
guard HelperMethods.reachability(responseResult: response.result) else {
completion(nil, .noNetwork)
return
}
guard let statusCode = response.response?.statusCode else {
completion(nil, .noStatusCode)
return
}
switch(statusCode) {
case 200:
guard let jsonData = response.data else {
completion(nil, .invalidJSON)
return
}
let decoder = JSONDecoder()
do {
let patientDetailsPayout = try decoder.decode(RootPatientPayoutDetails.self, from: jsonData)
if (patientDetailsPayout.data?.isEmpty)! {
completion(nil, .noRecordFound)
} else {
completion(patientDetailsPayout.data, nil)
}
} catch {
completion(nil, .invalidJSON)
}
case 400: completion(nil, .badRequest)
case 404: completion(nil, .noRecordFound)
default:
print("**UNCAPTURED STATUS CODE FROM (getPatientDetailsPayout)\nSTATUS CODE: \(statusCode)")
completion(nil, .uncapturedStatusCode)
}
}
}
getPatientPayoutDetails Function
func getPerPatientPayoutDetails(from: String, manualEntry: Bool) {
//SVProgressHUD.setDefaultMaskType(.black)
//SVProgressHUD.setForegroundColor(.white)
SVProgressHUD.setBackgroundColor(.lightGray)
SVProgressHUD.show(withStatus: "Retrieving Patient Procedures")
APIService.PatientList.getPatientDetailsPerPayout(periodId: doctorPayoutWeek[3].periodId!, doctorNumber: doctorNumber, parameterName: .selectedByHospitalNumber, hospitalNumber: from, manualEntry: manualEntry) { (patientPayout, error) in
guard let patientPerPayoutDetails = patientPayout, error == nil else {
if let networkError = error {
switch networkError {
case .noRecordFound:
let alertController = UIAlertController(title: "No Record Found", message: "You don't have current payment remittance", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
case .noNetwork:
let alertController = UIAlertController(title: "No Network", message: "\(networkError.rawValue)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
default:
let alertController = UIAlertController(title: "Error", message: "There is something went wrong. Please try again", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
}
}
SVProgressHUD.dismiss()
return
}
self.selectedPatientPayment = patientPerPayoutDetails
print(self.selectedPatientPayment)
SVProgressHUD.dismiss()
return
}
}
tableView
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.section {
case 0: break
case 1: break
case 2:
filteredPatient = indexPath.row
let selectedpatient = patientList[filteredPatient].hospitalNumber
let selectedEntry = patientList[filteredPatient].manualEntry
self.isBrowseAll = false
getPerPatientPayoutDetails(from: selectedpatient!, manualEntry: selectedEntry)
default: break
}
}
The endpoint which requires null string in hospitalNumber when it is nil
https://sample.com/openapi/getpatientpayoutdetails?periodId=579&doctorNumber=2866&hospitalNumber=null&manualEntry=true
As you can see hospital number has an important role for the endpoint. My problem is, once the tableView has reloaded it shows the data properly but when I didSelect the cell with null hospitalNumber, my app crashes and show Found nil error since hospitalNumber has null value. Hope you understand what I am trying to explain, please help me. Thank you
Your Codable model is correct, all you need is guard-let/if-let to prevent crash:
if let selectedpatient = patientList[filteredPatient].hospitalNumber, let selectedEntry = patientList[filteredPatient].manualEntry {
self.isBrowseAll = false
getPerPatientPayoutDetails(from: selectedpatient, manualEntry: selectedEntry)
}
Updated:
If you want to create endPoint in case of nil also then use coalescing operator :
getPerPatientPayoutDetails(from: selectedpatient ?? "null", manualEntry: selectedEntry)
In didSelect do like
let selectedpatient:String! //or Int whatever type it is right not this line initializes selected patient with nil
//below line will check for nil and ALSO NULL if NULL or nil it will not reassigne selectedpatient which means selectedpatient will remain nil
if let hsptlNbr = patientList[filteredPatient].hospitalNumber as? yourDataType{
selectedpatient = hsptlNbr
}
after this you can pass this as nil or value if exist in below method
getPerPatientPayoutDetails(from: selectedpatient, manualEntry: selectedEntry)
Change func getPerPatientPayoutDetails(from: String, manualEntry: Bool)
to
func getPerPatientPayoutDetails(from: String?, manualEntry: Bool)
I need some assistance with my codes. The json below was the original response I got from the postman.
OLD Format
{
"totalCreditedAmount": 2898.3000,
"periodId": 566,
"periodDate": "4/26/2019"
}
So I created the API service code below. It runs smoothly.
APIService
struct DoctorLatestCreditedAmount {
typealias getLatestCreditedAmountTaskCompletion = (_ latestCreditedAmount: CurrentRemittance?, _ error: NetworkError?) -> Void
static func getLatestCreditedAmount(doctorNumber: String, completion: #escaping getLatestCreditedAmountTaskCompletion) {
guard let latestCreditedAmountURL = URL(string: "\(Endpoint.LatestCreditedAmount.latestCreditedAmount)/\(doctorNumber)") else {
completion(nil, .invalidURL)
return
}
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.getAllTasks { (tasks) in
tasks.forEach({ $0.cancel() })
}
Alamofire.request(latestCreditedAmountURL, method: .get, encoding: JSONEncoding.default).responseJSON { (response) in
guard HelperMethods.reachability(responseResult: response.result) else {
completion(nil, .noNetwork)
return
}
guard let statusCode = response.response?.statusCode else {
completion(nil, .noStatusCode)
return
}
switch(statusCode) {
case 200: guard let jsonData = response.data else {
completion(nil, .invalidJSON)
return
}
let decoder = JSONDecoder()
do {
let currentCreditedAmount = try decoder.decode(CurrentRemittance.self, from: jsonData)
completion(currentCreditedAmount, nil)
} catch {
completion(nil, .invalidJSON)
}
case 400: completion(nil, .badRequest)
case 404: completion(nil, .noRecordFound)
default:
print("**UNCAPTURED STATUS CODE FROM (getLatestCreditedAmount)\nSTATUS CODE: \(statusCode)")
completion(nil, .uncapturedStatusCode)
}
}
}
But when the json side had some changes regarding the response. The json format changed and now using the APIService above I encountered error. It says invalidjson since the new json format is below.
NEW Format
{
"responseMessage": "Request successful",
"data": {
"totalCreditedAmount": 2898.3000,
"periodId": 566,
"periodDate": "4/26/2019"
}
}
Edited: getTotalCreditedAmount
var currentRemittance: CurrentRemittance!
func getTotalCreditedAmount(doctorNumber: String) {
windlessSetup()
APIService.DoctorLatestCreditedAmount.getLatestCreditedAmount(doctorNumber: doctorNumber) { (remittanceDetails, error) in
guard let creditedAmountDetails = remittanceDetails, error == nil else {
if let networkError = error {
switch networkError {
case .noRecordFound:
let alertController = UIAlertController(title: “No Record Found”, message: “You don’t have current payment remittance”, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: “OK”, style: .default))
self.present(alertController, animated: true, completion: nil)
case .noNetwork:
let alertController = UIAlertController(title: “No Network”, message: “\(networkError.rawValue)“, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: “OK”, style: .default))
self.present(alertController, animated: true, completion: nil)
default:
let alertController = UIAlertController(title: “Error”, message: “There is something went wrong. Please try again”, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: “OK”, style: .default))
self.present(alertController, animated: true, completion: nil)
}
}
self.creditedView.windless.end()
self.remittanceView.windless.end()
return
}
self.currentRemittance = creditedAmountDetails
self.showLatestTotalCreditedAmount()
self.creditedView.windless.end()
self.remittanceView.windless.end()
return
}
}
Encountered Error
My problem is, how can I alter my codes for APIService so it will match to the proper NEW json format I got. I am having a hard time since I get to used to pulling the same "OLD" format. I am really new to swift and I really need assistance. Hope you can give me some of your time.
You need
// MARK: - Welcome
struct Root: Codable {
let responseMessage: String
let data: CurrentRemittance
}
// MARK: - DataClass
struct CurrentRemittance: Codable {
let totalCreditedAmount: Double
let periodId: Int
let periodDate: String
}
Decode
let res = try decoder.decode(Root.self, from: jsonData)
print(res.data)
Try the code below, it works to me in array of data:
Alamofire.request(url, method: .get).responseJSON {
response in
if response.result.isSuccess {
let dataJSON = JSON(response.result.value!)
if let datas = dataJSON["data"].arrayObject {
print(datas)
}
}
}
I am creating an app wherein it pulls PatientList from API Server and it will display to a TableView. Upon checking, it returns 200 status code but falls to invalidJSON error. But when I checked in Postman, it returns 200 status code and pulls the records properly. I am quite confuse which part of my codes causes the error since I am new in swift. I am seeking help to solve the issue. Below are my sample codes for your references. Thank you so much in advance.
Patient.swift
struct Patient: Codable {
let hospitalNumber: Int
let patientName: String
let totalAmount: Double
enum CodingKeys: String, CodingKey {
case hospitalNumber = "hospitalNumber"
case patientName = "patientName"
case totalAmount = "totalAmount"
}
}
APIService.swift
struct PatientList {
typealias getPatientListTaskCompletion = (_ patientListperPayout: [Patient]?, _ error: NetworkError?) -> Void
static func getPatientList(doctorNumber: Int, periodId: Int, completion: #escaping getPatientListTaskCompletion) {
guard let patientPerPayoutURL = URL(string: "\(Endpoint.Patient.patientPerPayout)?periodId=\(periodId)&doctorNumber=\(doctorNumber)") else {
completion(nil, .invalidURL)
return
}
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.getAllTasks { (tasks) in
tasks.forEach({ $0.cancel() })
}
Alamofire.request(patientPerPayoutURL, method: .get, encoding: JSONEncoding.default).responseJSON { (response) in
guard HelperMethods.reachability(responseResult: response.result) else {
completion(nil, .noNetwork)
return
}
guard let statusCode = response.response?.statusCode else {
completion(nil, .noStatusCode)
return
}
switch(statusCode) {
case 200:
guard let jsonData = response.data else{
completion(nil, .invalidJSON)
print(statusCode)
return
}
let decoder = JSONDecoder()
do {
let patientListArray = try decoder.decode([Patient].self, from: jsonData)
let sortedPatientListArray = patientListArray.sorted(by: { $0.patientName < $1.patientName })
completion(sortedPatientListArray, nil)
}catch{
completion(nil, .invalidJSON)
print(statusCode)
}
case 400:
completion(nil, .badRequest)
case 404:
completion(nil, .noRecordFound)
default:
print("UNCAPTURED STATUS CODE FROM getPatientList\nSTATUS CODE: \(statusCode)")
completion(nil, .uncapturedStatusCode)
}
}
}
Controller.swift
var patientList: [Patient]! {
didSet {
performSegue(withIdentifier: patientListIdentifier, sender: self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.latestCreditedAmountTableView.dataSource = self
self.latestCreditedAmountTableView.delegate = self
configureTableViewCell()
showTotalCreditedAmount()
getDoctorPayoutSummary(doctorNumber: doctorNumber)
}
func getDoctorPayoutSummary(doctorNumber: Int) {
self.payoutSummary = payoutSummaryDetails
self.taxRateVatRateLabel.text = "\(self.payoutSummary.taxRate) / \(self.payoutSummary.vatRate)"
self.getPatientList()
self.latestCreditedAmountTableView.reloadData()
return
}
func getPatientList() {
APIService.PatientList.getPatientList(doctorNumber: doctorNumber, periodId: currentRemittance.periodId) { (patientListArray, error) in
guard let patientListPerPayout = patientListArray, error == nil else {
if let networkError = error {
switch networkError {
case .noRecordFound:
let alertController = UIAlertController(title: "No Record Found", message: "You don't have current payment remittance", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
case .noNetwork:
let alertController = UIAlertController(title: "No Network", message: "\(networkError.rawValue)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
default:
let alertController = UIAlertController(title: "Error", message: "There is something went wrong. Please try again", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
}
}
return
}
self.patientList = patientListPerPayout
return
}
}
JSON Response
[
{
"hospitalNumber": null,
"patientName": null,
"totalAmount": 31104
},
{
"hospitalNumber": "",
"patientName": "LastName, FirstName",
"totalAmount": 3439.8
}
]
Your JSON response shows that some of the fields can be null - hospitalNumber and patientName at least. Also hospitalNumber is a string in the JSON - thanks to #Don for pointing out. Your struct should also be able to cope with these being nullable by making the mapped fields nullable also. I.e.
struct Patient: Codable {
let hospitalNumber: String?
let patientName: String?
let totalAmount: Double
enum CodingKeys: String, CodingKey {
case hospitalNumber = "hospitalNumber"
case patientName = "patientName"
case totalAmount = "totalAmount"
}
}
You will need to do the same for totalAmount if that can ever be null also. Whether the API is correct to return null in any circumstance is of course another question - how a null hospital number or name is useful may need to be addressed.
Make sure you do not force-unwrap the fields when you use them.
Just make below changes in your model class. Define your model class variable as optional which is not mandatory from APIs.
struct Patient: Codable {
var hospitalNumber: String?
let patientName: String?
let totalAmount: Double?
enum CodingKeys: String, CodingKey {
case hospitalNumber = "hospitalNumber"
case patientName = "patientName"
case totalAmount = "totalAmount"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let hospitalNumb = try container.decode(Int?.self, forKey: .hospitalNumber) {
hospitalNumber = String(hospitalNumb)
} else {
hospitalNumber = try container.decode(String.self, forKey: .hospitalNumber)
}
patientName = try container.decode(String.self, forKey: .patientName)
totalAmount = try container.decode(Double.self, forKey: .totalAmount)
}
}
Note:
Codable OR Decodable is not working if the type is different for the same key or you can say like that type is different then specified type.