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?
Related
I am using Apple Pay with Stripe and it works fine when there is no shipping is available.
When there is a shipping address selected it always gives invalid address error. (It works with Stripe Sandbox Key)
I am using STKPaymentContext API and follow steps from the below link.
https://stripe.com/docs/mobile/ios/basic
in the configuration, I have written this.
let config = STPPaymentConfiguration.shared()
config.requiredShippingAddressFields = [.postalAddress, .phoneNumber,.name]
Not sure what is wrong here.
Here is how it looks.
HERE IS MY CODE
extension CheckoutTableViewController:STPPaymentContextDelegate{
func paymentContext(_ paymentContext: STPPaymentContext, didUpdateShippingAddress address: STPAddress, completion: #escaping STPShippingMethodsCompletionBlock) {
guard
let buyerPostalCode = address.postalCode,
let buyerCountry = address.country,
let productId = self.productDetailsData?.productId
else{
completion(.invalid, nil, nil, nil)
return
}
guard let phone = address.phone, phone.count > 0 else {
completion(.invalid,RatesError.phoneNumberRequired,[],nil)
return
}
var shipmentItem:[String:Any] = [:]
shipmentItem["order_amount"] = self.productCost
shipmentItem["actual_weight"] = 8
shipmentItem["height"] = 7
shipmentItem["width"] = 10
shipmentItem["length"] = 13
shipmentItem["currency"] = "USD"
shipmentItem["destination_postal_code"] = buyerPostalCode
shipmentItem["destination_country_code"] = buyerCountry
shipmentItem["product_id"] = productId
shipmentItem["category"] = "fashion"
enum RatesError:Error,LocalizedError{
case NoDeliveryOptionsFound
case phoneNumberRequired
public var errorDescription: String? {
switch self {
case .NoDeliveryOptionsFound:
return "No couriers are available at the address.\nPlease try with different address."
case .phoneNumberRequired:
return "Please enter phone number."
}
}
}
fetchShippingOptions(forItem: shipmentItem, completionSuccess: {[weak self] (response) in
guard let self = `self` else {
return
}
if
let responseValue = response as? [String:Any],
let rates = responseValue["rates"] as? [[String:Any]]{
self.shippingRates = []
for rate in rates{
if let fullName = rate["courier_display_name"] as? String,
let identifier = rate["courier_id"] as? String,
let amount = rate["shipment_charge_total"] as? Double,
let detail = rate["full_description"] as? String
{
let method = PKShippingMethod()
method.amount = NSDecimalNumber.init(value: amount.currency)
method.identifier = identifier
method.label = fullName
method.detail = detail.replacingOccurrences(of: fullName, with: "")
self.shippingRates.append(method)
}
}
completion(.valid, nil, self.shippingRates, self.shippingRates.first)
}else{
completion(.invalid,RatesError.NoDeliveryOptionsFound,[],nil)
}
}) { (error) in
completion(.invalid,error,[],nil)
}
}
func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
if let paymentOption = paymentContext.selectedPaymentOption {
self.lblPaymentMethod.text = paymentOption.label
} else {
self.lblPaymentMethod.text = "Select Payment"
}
if let shippingMethod = paymentContext.selectedShippingMethod {
if let selectedRate = self.shippingRates.first(where: { (method) -> Bool in
guard let leftValue = method.identifier, let rightValue = shippingMethod.identifier else{
return false
}
return leftValue == rightValue
}){
self.lblAddress.text = selectedRate.label
self.shippingCharges = Double(truncating: selectedRate.amount).currency
self.lblShippingCharges.text = "$\(shippingCharges)"
self.getStripeFees(forAmount: self.productCost + self.shippingCharges)
}
} else {
self.lblAddress.text = "Select Address"
}
self.updateTotalCost()
}
func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
let alertController = UIAlertController(
title: "Error",
message: error.localizedDescription,
preferredStyle: .alert
)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: { action in
// Need to assign to _ because optional binding loses #discardableResult value
// https://bugs.swift.org/browse/SR-1681
_ = self.navigationController?.popViewController(animated: true)
})
let retry = UIAlertAction(title: "Retry", style: .default, handler: { action in
self.paymentContext?.retryLoading()
})
alertController.addAction(cancel)
alertController.addAction(retry)
self.present(alertController, animated: true, completion: nil)
}
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: #escaping STPPaymentStatusBlock) {
self.callPaymentIntentAPI(paymentContext, didCreatePaymentResult: paymentResult, completion: completion)
}
func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
OperationQueue.main.addOperation {
SVProgressHUD.dismiss()
}
let title: String
let message: String
switch status {
case .error:
title = "Error"
message = error?.localizedDescription ?? ""
UIAlertController.showAlert(withTitle: title, andMessage: message, andButtonTitles: ["Okay"]) {[weak self] (selectedIndex) in
OperationQueue.main.addOperation {
self?.navigationController?.popViewController(animated: true)
}
}
case .success:
title = "Success"
message = "Your purchase was successful!"
UIAlertController.showAlert(withTitle: title, andMessage: message, andButtonTitles: ["Okay"]) {[weak self] (selectedIndex) in
OperationQueue.main.addOperation {
self?.onPaymentCompletion?()
var isControllerFound:Bool = false
for controller in self?.navigationController?.viewControllers ?? []{
if (controller is ProductDetailsViewController) || (controller is ChatVC){
isControllerFound = true
self?.navigationController?.popToViewController(controller, animated: true)
break
}
}
if !isControllerFound{
self?.navigationController?.popViewController(animated: true)
}
}
}
case .userCancellation:
return()
#unknown default:
return()
}
}
}
Finally, I found an error.
Apple calls didUpdateShippingAddress method at the time of payment but it doesn't provide all information for security purposes. So in my case phone number validation was causing that error.
So I removed the below code from that method.
guard let phone = address.phone, phone.count > 0 else {
completion(.invalid,RatesError.phoneNumberRequired,[],nil)
return
}
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 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.
I am trying to return a result from a JSON object but unable to do so. I am new to Swift so kindly explain me how to do so. In the below code I want to return json_level_number in the return of function fetchNumberOfSections () where i have hard coded as 5 right now return 5. If i declare a variable json_level_number just above the reachability code it sort of solves the problem but then it is returning '0' for the first time. The API returns 2 each time.
Code as below:
func fetchNumberOfSections () -> Int {
if Reachability.isConnectedToNetwork() == true {
// Below code to fetch number of sections
var urlAsString = "http://themostplayed.com/rest/fetch_challenge_sections.php"
urlAsString = urlAsString+"?apiKey="+apiKey
print (urlAsString)
let url = NSURL(string: urlAsString)!
let urlSession = NSURLSession.sharedSession()
let jsonQuery = urlSession.dataTaskWithURL(url, completionHandler: { data, response, error -> Void in
if (error != nil) {
print(error!.localizedDescription)
}
do {
let jsonResult = (try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary
let json_level_number: String! = jsonResult["level_number"] as! String
//
dispatch_async(dispatch_get_main_queue(), {
// self.dateLabel.text = jsonDate
// self.timeLabel.text = jsonTime
print(json_level_number)
// self.activityIndicatorStop()
})
}
catch let errorJSON {
print (errorJSON)
// alert box code below
let alert = UIAlertController(title: "JSON Error!", message:"Error processing JSON.", preferredStyle: .Alert)
let action = UIAlertAction(title: "OK", style: .Default) { _ in
// Put here any code that you would like to execute when
self.dismissViewControllerAnimated(true, completion: {})
}
alert.addAction(action)
self.presentViewController(alert, animated: true, completion: nil)
// alert box code end
}
})
jsonQuery.resume()
// End
}
else {
print("Internet connection FAILED")
self.performSegueWithIdentifier("nointernet", sender: nil)
}
return 5
}