How to update parsed JSON data using delegates (no table) - swift

I have a file which is parsing the JSON data and another file which calls the function and tries to retrieve the data. However, it seems that it can't update into the variable I want it too. I used this format (using tables) in another project when parsing data and it used tableView.reloadData(). Is there something like this but not for tables?
Here is the code for the file that calls the function to parse the JSON and tries to set it as member. However when I print(member.first_name), it just appears as hi
class ProfileVC: UIViewController {
#IBOutlet weak var NameText: UILabel!
#IBOutlet weak var IDText: UILabel!
#IBOutlet weak var GenderText: UILabel!
var MemberManager = GetMember()
var member: MemberModel = MemberModel(id: " ", first_name: "hi ", last_name: " ", gender: " ")
override func viewDidLoad() {
super.viewDidLoad()
MemberManager.delegate = self
MemberManager.fetchMember()
print(member.first_name)
NameText.text = "member?.first_name"
}
}
//MARK: - GetMemberDelegate
extension ProfileVC: GetMemberDelegate {
func didUpdateStatus(_ getMember: GetMember, member: MemberModel) {
DispatchQueue.main.async {
self.member = member
}
}
func didFailWithError(_ error: Error) {
print("ei")
print(error)
}
}
Here is the code I used to parse the JSON.
import Foundation
protocol GetMemberDelegate {
func didUpdateStatus(_ getMember: GetMember, member: MemberModel)
func didFailWithError(_ error: Error)
}
struct GetMember {
// GET https://api.propublica.org/congress/v1/members/{member-id}.json
let getMemberString = "https://api.propublica.org/congress/v1/members/M001157.json"
var delegate: GetMemberDelegate?
var urlUpcomingFinalString: String?
// Forms URL
func fetchMember() {
let urlMember = NSURL(string: getMemberString)!
let request = NSMutableURLRequest(url: urlMember as URL)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(Constant.APIKEY, forHTTPHeaderField: "X-API-Key")
performRequest(request)
}
// Performs request
func performRequest (_ urlRequest: NSURLRequest) {
let session = URLSession.shared.dataTask(with: urlRequest as URLRequest) {data, response, error in
if error != nil {
self.delegate?.didFailWithError(error!)
print("No")
return
}
if let safeData = data {
if let mem = self.parseJSONMember(safeData) {
self.delegate?.didUpdateStatus(self, member: mem)
}
}
}
session.resume()
}
// Parsing JSON
func parseJSONMember(_ memberData: Data) -> MemberModel?{
// var member: [MemberModel] = []
let decoder = JSONDecoder()
do {
let decodedDataGetMember = try decoder.decode(MemberJSONFormat.self, from: memberData)
let id = decodedDataGetMember.results[0].id
let first_name = decodedDataGetMember.results[0].first_name
// let middle_name = decodedDataGetMember.results[0].middle_name
let last_name = decodedDataGetMember.results[0].last_name
let gender = decodedDataGetMember.results[0].gender
print(first_name)
let memberItem = MemberModel(id: id, first_name: first_name, last_name: last_name, gender: gender)
return memberItem
} catch {
self.delegate?.didFailWithError(error)
print("error")
return nil
}
}
}

Related

How Can I Use Two URLs Asynchronously to Parse JSON data

So I am using a URL in the bolded text to parse JSON data retrieved remotely from that URL. My issue is that I want to parse data remotely AND asynchronously from TWO URLs not just one. The following code works great for 1 URL but I haven't the slightest idea how to do the same thing for 2. I am fairly new to Swift to any tips or pointers would be appreciated.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var customerNameLabel: UILabel!
#IBOutlet weak var cardNumberLabel: UILabel!
#IBOutlet weak var dateNTimeLabel: UILabel!
#IBOutlet weak var amountLabel: UILabel!
var customers = [Customer]()
var currentCustomerIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Retrieve JSON data from a remote server
let config = URLSessionConfiguration.default
// Create a session
let session = URLSession(configuration: config)
// Validate the URL to ensure that it is not a broken link
if let validURL = URL(string: "**THISISMYJSONURLHERE(removedforsecurity)**") {
//Create a task that will download whatever is found at validURL as a Data object
let task = session.dataTask(with: validURL, completionHandler: { (data, response, error) in
// If there is an error, we are going to bail out of this entire method (hence return)
if let error = error {
print("Data task failed with error: " + error.localizedDescription)
return
}
// If we get here that means we have received the info at the URL as a Data Object nd we can now ue it
print("Success")
//Check the response status
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let validData = data
else {print("JSON Object Creation Failed"); return}
do {
let jsonObj = try JSONSerialization.jsonObject(with: validData, options: .mutableContainers) as? [Any]
// Call our Parse method
self.ParseData(jsonObject: jsonObj)
self.displayData()
}
catch {
print(error.localizedDescription)
}
task.resume()
}
}
func ParseData(jsonObject: [Any]?) {
guard let json = jsonObject
else { print("Parse failed to unwrap the optional."); return }
for firstLevelItems in json {
guard let object = firstLevelItems as? [String: Any],
let fname = object["first_name"] as? String,
let lname = object["last_name"] as? String,
let fullName = fname + " " + lname as? String,
let customerNumber = object["customer_number"] as? Int,
let purchase = object["purchase"] as? [String: Any],
let time = purchase["time"] as? String,
let date = purchase["date"] as? String,
let amount = purchase["amount"] as? String
else { continue }
// See Note: Nested Functions
func addTransaction(_customer: Customer) {
if let cardNumber = purchase["card_number"] as? String? {
_customer.transactions.append(Transaction(firstName: fname, lastName: lname, time: time, date: date, amount: amount, cardNumber: cardNumber))
}
else {
_customer.transactions.append(Transaction(firstName: fname, lastName: lname, time: time, date: date, amount: amount))
}
}
let filteredCustomers = customers.filter({ (customer) -> Bool in
return customer.transactions[currentCustomerIndex].customerName == fullName
})
if filteredCustomers.count == 0 {
customers.append(Customer(customerNumber: customerNumber))
//Forced unwrapping here is ok because we know for a fact that customers wont be empty
addTransaction(_customer: customers.last!)
}
// If filtered array.count is 1 then that means we already have a customer object for this number
// In that case we just want to modify the existing customer object instead of creating a new one
else if filteredCustomers.count == 1 {
// filteredCustomer[0].customerNote = "This has been counted and Modified"
addTransaction(_customer: filteredCustomers[0])
}
else {
//See Note: Assertion
// Assertion Failure so that as we are building if this ever happens we know we have messed up
assertionFailure("No customers should exist twice in our customers array")
}
// print("Customer Number: \(customerNumber) has \(filteredCustomers.count) Orccurance in Customer's Array")
}
}
func displayData() {
DispatchQueue.main.async {
self.customerNameLabel.text = self.customers[self.currentCustomerIndex].customerName
self.cardNumberLabel.text = self.customers[self.currentCustomerIndex].cardNum
self.dateNTimeLabel.text = self.customers[self.currentCustomerIndex].dateNTime
self.amountLabel.text = "$" + self.customers[self.currentCustomerIndex].customerAmount.description
}
}
#IBAction func changeCustomer(_ sender: UIButton) {
currentCustomerIndex += sender.tag
if currentCustomerIndex < 0 {
currentCustomerIndex = customers.count - 1
}
else if currentCustomerIndex >= customers.count {
currentCustomerIndex = 0
}
displayData()
}
}

Use of unresolved identifier 'self' (CoreData)

I am using this line below :
self.present(activityViewController, animated: true, completion: nil)
And I am getting an error of - Use of unresolved identifier 'self'. Any ideas about how to resolve this? To me it looks as it it is subordinate to the class, but clearly doing something wrong. Any help would be appreciated.
import UIKit
import CoreData
class CoreDataViewController: UIViewController {
#IBOutlet weak var CoreDataView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var items:[Checkins]?
var btnnames = [""]
override func viewDidLoad() {
super.viewDidLoad()
// CoreDataView.dataSource = self
// CoreDataView.delegate = self
storeTranscription()
// Loads the current data
getTranscriptions()
// fetchCheckins()
let btn1name = btnnames[0]
let btn2name = btnnames[1]
let btn3name = btnnames[2]
let btn4name = btnnames[3]
let btn5name = btnnames[4]
let btn6name = btnnames[5]
// print(btnnames)
print(btn1name, btn2name, btn3name, btn4name, btn5name, btn6name)
}
#IBAction func export(_ sender: Any) {
exportDatabase()
}
#IBOutlet weak var Table_label: UILabel!
}
var CheckinDate: Date? = Date()
var fetchedStatsArray: [NSManagedObject] = []
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func storeTranscription() {
//retrieve the entity that we just created
let entity = NSEntityDescription.entity(forEntityName: "Checkins", in: context)
let transc = NSManagedObject(entity: entity!, insertInto: context) as! Checkins
//set the entity values
transc.who = "Who"
transc.reason = "Reason for visit"
transc.date = CheckinDate
//save the object
do {
try context.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
func getTranscriptions () {
//create a fetch request, telling it about the entity
let fetchRequest: NSFetchRequest<Checkins> = Checkins.fetchRequest()
do {
//go get the results
let searchResults = try context.fetch(fetchRequest)
fetchedStatsArray = searchResults as [NSManagedObject]
//I like to check the size of the returned results!
print ("num of results = \(searchResults.count)")
//You need to convert to NSManagedObject to use 'for' loops
for trans in searchResults as [NSManagedObject] {
//get the Key Value pairs (although there may be a better way to do that...
print("\(trans.value(forKey: "who")!)")
let mdate = trans.value(forKey: "CheckinDate") as! Date
print(mdate)
}
} catch {
print("Error with request: \(error)")
}
}
func exportDatabase() {
let exportString = createExportString()
saveAndExport(exportString: exportString)
}
func saveAndExport(exportString: String) {
let exportFilePath = NSTemporaryDirectory() + "Checkins.csv"
let exportFileURL = NSURL(fileURLWithPath: exportFilePath)
FileManager.default.createFile(atPath: exportFilePath, contents: NSData() as Data, attributes: nil)
//var fileHandleError: NSError? = nil
var fileHandle: FileHandle? = nil
do {
fileHandle = try FileHandle(forWritingTo: exportFileURL as URL)
} catch {
print("Error with fileHandle")
}
if fileHandle != nil {
fileHandle!.seekToEndOfFile()
let csvData = exportString.data(using: String.Encoding.utf8, allowLossyConversion: false)
fileHandle!.write(csvData!)
fileHandle!.closeFile()
let firstActivityItem = NSURL(fileURLWithPath: exportFilePath)
let activityViewController : UIActivityViewController = UIActivityViewController(
activityItems: [firstActivityItem], applicationActivities: nil)
activityViewController.excludedActivityTypes = [
UIActivity.ActivityType.assignToContact,
UIActivity.ActivityType.saveToCameraRoll,
UIActivity.ActivityType.postToFlickr,
UIActivity.ActivityType.postToVimeo,
UIActivity.ActivityType.postToTencentWeibo
]
self.present(activityViewController, animated: true, completion: nil)
}
}
func createExportString() -> String {
var checkinwho: String?
var checkinreason: String?
var export: String = NSLocalizedString("who, reason, date \n", comment: "")
for (index, itemList) in fetchedStatsArray.enumerated() {
if index <= fetchedStatsArray.count - 1 {
checkinwho = Checkins.value(forKey: "who") as! String?
checkinreason = itemList.value(forKey: "reason") as! String?
let Datevar = Checkins.value(forKey: "date") as! Date
let whostring = checkinwho
let reasonstring = checkinreason
let DateSting = "\(Datevar)"
export += "\(whostring!),\(reasonstring!),\(DateSting) \n"
}
}
print("This is what the app will export: \(export)")
return export
}
Remove the } on this line
#IBOutlet weak var Table_label: UILabel!
}
and put another } at the end of this file.

Adding items to NSArrayController causes EXC_BAD_INSTRUCTION

I've had the same problem for a few days now and it's been killing me. Whenever I run my script, I get a EXC_BAD_INSTRUCTION. It happens when I add an array of objects to an NSArrayController, bount to an NSTableView. Below is my code:
AppDelegate.swift:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var mainTabView: NSTabView!
#IBOutlet weak var tracksAC: NSArrayController!
#IBOutlet weak var albumsAC: NSArrayController!
func checkIfFileExists(atPath path: String, isDirectory: Bool = false, createIfNeeded createNew: Bool = false) -> Int {
var isDir: ObjCBool = ObjCBool(isDirectory)
if !FileManager.default.fileExists(atPath: path, isDirectory: &isDir) {
if createNew {
do {
if isDirectory {
try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true)
}else{
FileManager.default.createFile(atPath: path, contents: nil)
}
return 2
}catch{
return 3
}
}else{
return 0
}
}
return 1
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
//Create application support folder if it's not existent and populate with all needed files
let paths = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)
let appSupportDir = paths[0] + "/Project Alpha"
//Create application folder if needed
let folderStatus = checkIfFileExists(atPath: appSupportDir, isDirectory: true, createIfNeeded: true)
if folderStatus == 1 || folderStatus == 2 {
_ = checkIfFileExists(atPath: appSupportDir + "/Songs.txt", createIfNeeded: true)
_ = checkIfFileExists(atPath: appSupportDir + "/Albums.txt", createIfNeeded: true)
_ = checkIfFileExists(atPath: appSupportDir + "/Genres.txt", createIfNeeded: true)
}
//Populate tracks table view
let tracks = getTracks()
print(tracks[0].name)
tracksAC.add(contentsOf: tracks) //This is where the error occures every time
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
#IBAction func openAlbumsTab(_ sender: NSToolbarItem) {
mainTabView.selectTabViewItem(at: 1)
}
}
Classes.swift:
import Cocoa
class Track: NSObject {
func getLine() -> String? {
let songsFilePath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)[0] + "/Project Alpha/Songs.txt"
do {
let content = try String(contentsOfFile: songsFilePath)
let lines = content.components(separatedBy: "---===---")
let line = lines.first(where: {$0.hasPrefix(String(self.identifier) + ";")})
return line
}catch{
return nil
}
}
var identifier: Int
var name: String
var artist: String
var album: String
var genre: String
var vocals: String
var trackNumber: Int
init(line: String) {
let items = line.components(separatedBy: ";")
print(items)
self.identifier = Int(items[0])!
self.name = String(items[1])
self.artist = String(items[2])
self.genre = String(items[3])
self.vocals = String(items[4])
self.album = String(items[5])
self.trackNumber = Int(items[6])!
}
}
I have checked the getTracks function and normally it does return a list of Track objects, so I really don't know what's causing the error. The columns are bound to the array controller as shown via the link below:
screenshot
Any help would be greatly appreciated. Thanks in advance.

How can I reuse a variable later on in Swift

I'm trying to capture a user input (textfield + button) and reuse the result later in the program but I don't know how to do that.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var resultLabel: UILabel!
#IBOutlet weak var moneyTextField: UITextField!
#IBAction func convert(_ sender: Any) {
let convertion:Double = Double(moneyTextField.text!)!
print(convertion)
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://www.x-rates.com/calculator/?from=EUR&to=USD&amount=1")!
let request = NSMutableURLRequest(url : url)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
var rateValue:Double = 0.0;
if let error = error {
print(error)
} else {
if let unwrappedData = data {
let dataString = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue)
var stringSeperator = "<span class=\"ccOutputRslt\">"
if let contentArray = dataString?.components(separatedBy: stringSeperator){
if contentArray.count > 0 {
stringSeperator = "<span"
let newContentArray = contentArray[1].components(separatedBy: stringSeperator)
if newContentArray.count > 0 {
rateValue = Double(newContentArray[0])!
print(newContentArray[0])
}
}
}
}
}
//
print("Rate is \(rateValue)");
DispatchQueue.main.sync(execute: {
self.resultLabel.text = "the value of the dollar is " + String(rateValue)
}
)}
task.resume()
}
}
What I want to do is take the let convertion and multiply it by rateValue at the end of the code. I tried different thing but without any results.
after the advice from Joakim Danielson
I did that :
import UIKit
class ViewController: UIViewController {
var fxRate: Double?
#IBOutlet weak var resultLabel: UILabel!
#IBOutlet weak var moneyTextField: UITextField!
#IBAction func convert(_ sender: Any) {
let convertion:Double = Double(moneyTextField.text!)!
print(convertion)
var convertedAmount = 0.0
if let rate = fxRate, let money = Double(moneyTextField.text) {
convertedAmount = rate * money
}
print(convertedAmount)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = URL(string: "https://www.x-rates.com/calculator/?from=EUR&to=USD&amount=1")!
let request = NSMutableURLRequest(url : url)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
var rateValue:Double = 0.0;
if let error = error {
print(error)
} else {
if let unwrappedData = data {
let dataString = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue)
var stringSeperator = "<span class=\"ccOutputRslt\">"
if let contentArray = dataString?.components(separatedBy: stringSeperator){
if contentArray.count > 0 {
stringSeperator = "<span"
let newContentArray = contentArray[1].components(separatedBy: stringSeperator)
if newContentArray.count > 0 {
rateValue = Double(newContentArray[0])!
print(newContentArray[0])
rateValue = Double(newContentArray[0])!
self.fxRate = rateValue
}
}
}
}
}
//
print("Rate is \(rateValue)");
DispatchQueue.main.sync(execute: {
self.resultLabel.text = "the value of the dollar is " + String(rateValue)
}
)}
task.resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
but I have the error : Cannot invoke initializer for type 'Double' with an argument list of type '(String?)' on line 26. Can you please help me? thx!
create a variable outside of your function
var anyVariableYouWantToAccessLater: Double?
And use this variable anywhere you want.
Since you're downloading the rate during viewDidLoad I am assuming this is what you want to keep.
Add a new property to the class
class ViewController: UIViewController {
var fxRate: Double?
...
In viewDidLoad update this property with the downloaded value
rateValue = Double(newContentArray[0])!
fxRate = rateValue
In the convert func (or wherever you want to use the rate)
#IBAction func convert(_ sender: Any) {
var convertedAmount = 0.0
if let rate = fxRate, let money = Double(moneyTextField.text ?? "0") {
convertedAmount = rate * money
}
print(convertedAmount)
}

Swift - Why is my JSON object element only adding the last array element?

I have a problem with my JSON object. Everything is working fine creating and printing out my JSON object, apart from the idQty part. It only prints the last key value result. I assume I have a problem with my for loop. If anybody can point out where I've went wrong, it would be of huge help.
Code below:
struct Order: Codable {
let idQty: [IdQty]
let collection: String
let name: String
let phone: Int
let doorNum: Int
let street: String
let postcode: String
}
struct IdQty: Codable {
let itemId: Int
let qty: Int
}
class CheckoutServer: NSObject, URLSessionDataDelegate {
var inputVals = [Int:Int]()
var idQty = [IdQty]()
var collection = String()
var name = String()
var phone = Int()
var doorNum = Int()
var street = String()
var postcode = String()
var request = URLRequest(url: NSURL(string: "http://192.168.1.100/api/AddOrder.php")! as URL)
func downloadItems() {
for(key,value) in inputVals {
idQty = [IdQty(itemId: key,qty: value)]
}
let order = Order(idQty: idQty,collection: collection,name: name,phone: phone,doorNum: doorNum,street: street,postcode: postcode)
let encodedOrder = try? JSONEncoder().encode(order)
var json: Any?
request.httpMethod = "POST"
if let data = encodedOrder {
json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
if let json = json {
}
}
let postParameters = "json="+String(describing: json!)
request.httpBody = postParameters.data(using: .utf8)
print(String(describing: json!))
let defaultSession = URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: request) { (data, response, error) in
if error != nil {
print("Failed to upload data at Menu Type Items")
} else {
print("Data uploaded")
}
}
task.resume()
}
}
Below is the output. the 'idQty' part only ever returns the last entry in the [Int:Int] dictionary:
{
collection = Delivery;
doorNum = 4;
idQty = (
{
itemId = 14;
qty = 2;
}
);
name = James;
phone = 4355345;
postcode = Test;
street = TestStreet;
}
You should append new value to your array instead of recreating it on each iteration
for(key,value) in inputVals
{
idQty.append(IdQty(itemId: key,qty: value))
}