How Can I Use Two URLs Asynchronously to Parse JSON data - swift

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

Related

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

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

Use core data index to fetch a specific item from core data

My swift code below when loaded places 3 items in the core data entity named "UserName". When the user enters a number into textfield enterT I want the label labelName to display it. So when the user enters 1 the label should display jessica biel because Jesical Biel is the first name entered. Someone stated the suggestion below to solve this problem. I dont know exactly how to do this.I have added a gif below.
Convert the entered number to Int. If this succeeds pass the integer to joke and fetch the record matching the idx attribute.
https://github.com/redrock34/index-fetch
import UIKit
import CoreData
class ViewController: UIViewController,UITextFieldDelegate {
#IBOutlet var labelName : UILabel!
#IBOutlet var enterT : UITextField!
lazy var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
openDatabse()
fetchData()
enterT.delegate = self
}
func textFieldDidEndEditing(_ textField: UITextField) {
guard let index = Int(textField.text!) else {
// display an alert about invalid text
return
}
joke(at: index - 1)
}
func joke(at index : Int) {
let fetchRequest = NSFetchRequest<Users>(entityName: "Users")
fetchRequest.predicate = NSPredicate(format: "idx == %d", Int32(index))
do {
if let user = try context.fetch(fetchRequest).first {
labelName.text = user.username
}
} catch {
print("Could not fetch \(error) ")
}
}
func openDatabse()
{
let names = ["kim kardashian", "jessica biel", "Hailey Rienhart"]
for i in 0..<names.count {
let newUser = Users(context: context)
newUser.username = names[i]
newUser.idx = Int32(i + 1)
}
print("Storing Data..")
do {
try context.save()
} catch {
print("Storing data Failed", error)
}
}
func fetchData()
{
print("Fetching Data..")
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
let userName = data.value(forKey: "username") as! String
print("User Name is : "+userName)
}
} catch {
print("Fetching data Failed")
}
}}
Of course you have to assign values to the idx attribute and you have to assign the result of the fetch to the label.
First replace
let appDelegate = UIApplication.shared.delegate as! AppDelegate //Singlton instanc
var context:NSManagedObjectContext!
with
lazy var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
Then replace both openDatabse and saveData with
func openDatabse()
{
let names = ["kim kardashian", "jessica biel", "Hailey Rienhart"]
for i in 0..<names.count {
let newUser = Users(context: context)
newUser.name = names[i]
newUser.idx = Int32(i + 1)
}
print("Storing Data..")
do {
try context.save()
} catch {
print("Storing data Failed", error)
}
}
Finally add a line in joke to display the value
func joke(at index : Int) {
let fetchRequest = NSFetchRequest<Users>(entityName: "Users")
fetchRequest.predicate = NSPredicate(format: "idx == %d", Int32(index))
do {
if let user = try context.fetch(fetchRequest).first {
labelName.text = user.username
}
} catch {
print("Could not fetch \(error) ")
}
}
It creates the records and assigns the proper indexes. Then entering a number in the text field should work.
But – once again – on each launch of the app the 3 records are inserted again with the same names and indexes. Be aware of that!

Firebase data to tableview, crash after appending data

I am trying to download data from Firebase using
firebaseDB.observe(DataEventType.value, with: { (snapshot) in })
which works well. I am able to receive the following data:
let fetchedObject = items.value as? [String: AnyObject]
let dbUrl = fetchedObject?["url"]
let dbTime = fetchedObject?["time"]
let dbStatus = fetchedObject?["status"]
Using print("\(dbUrl!), \(dbTime!), \(dbStatus!)"), I see the following output in the console:
https://google.de, 2019-04-26 07:44:54 +0000, new
However, because I want to show the data in a Tableview, I have created a swift file for a data model, called ItemModel with the following content:
class ItemModel {
var url: String?
var time: String?
var status: String?
init(url: String?, time: String?, status: String?){
self.url = url
self.time = time
self.status = status
}
}
When trying to populate the url, time and status using
let item = ItemModel(url: dbUrl as! String?, time: dbTime as! String?, status: dbStatus as! String?)
appending it with self.itemList.append(item), and reloading the table with self.tableViewOutlet.reloadData(), the app crashes.
If I comment out self.itemList.append(item), it does not crash.
What am I doing wrong? I am clearly missing something viable, but I just can't figure out what. I am really wondering why appending the data does not work... Thanks a lot for any help!
EDIT:
Complete fetching method:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var itemList = [ItemModel]()
override func viewDidLoad() {
super.viewDidLoad()
[...]
// Authenticate user
Auth.auth().signInAnonymously() {(authResult, error) in
if error == nil {
self.user = authResult!.user // Authenticate User
self.uid = self.user.uid // Users unique and anonymous identifier by Firebase
print(">> LOG: Auth success!")
// Download data from Firebase
var itemsDB: DatabaseReference!
itemsDB = Database.database().reference().child("somechild").child("someotherchild")
itemsDB.observe(DataEventType.value, with: { (snapshot) in
// Check if there is any relevant data
if snapshot.childrenCount > 0 {
// Initially clear the list
self.itemList.removeAll()
// Interate through all the values
for items in snapshot.children.allObjects as! [DataSnapshot] {
// Get the values from Firebase
let fetchedObject = items.value as? [String: AnyObject]
let dbUrl = fetchedObject?["url"]
let dbTime = fetchedObject?["time"]
let dbStatus = fetchedObject?["status"]
let item = ItemModel(url: dbUrl as? String, time: dbTime as? String, status: dbStatus as? String)
print("\(item.url!), \(item.time!), \(item.status!)")
self.itemList.append(item)
}
// Reload the Tableview
self.tableViewOutlet.reloadData()
}
})
} else {
print(">> LOG: Error when trying to authenticate: \(error!)")
}
}
}
}

Cannot avoid optional() for string print statements?

I have a data struct which contains some string parameters. The struct is below:
struct pulledMessage{
var convoWithUserID: String
var convoWithUserName: String
}
I have a function which assigns a value to variables based on the values within a particular pulledMessage. For some more complicated, out-of-the-scope-of-the-question, reasons, these values come from [pulledMessage] array. The pulledMessage always changes in the actual function but for illustration purposes I will write it as a constant:
var messageArray = [pulledMessage]()
func assignValues(){
messageArray.append(pulledMessage(convoWithUserID: "abc123", convoWithUserName: "Kevin"))
let convoWithUserID = messageArray[0].convoWithUserID
let convoWithUserName = messageArray[0].convoWithUserName
print(convoWithUserID) //returns optional("abc123")
print(convoWithUserName) // returns optional("Kevin")
}
I have tried adding ! to unwrap the values in different ways:
messageArray[0]!.convoWithUserID
This tells gives me an error that I cannot unwrap a non-optional type of pulledMessage.
messageArray[0].convoWithUserID!
This gives me an error that I cannot unwrap a non-optional type of String.
This stack question suggests utilizing if let to get rid of the optional:
if let convoWithUserIDCheck = messageArray[0].convoWithUserID{
convoWithUserID = convoWithUserIDCheck
}
This gives me a warning that there is no reason to do if let with a non-optional type of string. I have no idea how to get it to stop returning the values wrapped by optional().
Update: The more complicated, complete code
The SQL Database functions:
class FMDBManager: NSObject {
static let shared: FMDBManager = FMDBManager()
let databaseFileName = "messagesBetweenUsers.sqlite"
var pathToDatabase: String!
var database: FMDatabase!
override init() {
super.init()
let documentsDirectory = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString) as String
pathToDatabase = documentsDirectory.appending("/\(databaseFileName)")
}
func loadMessageData(){//will need a struct to load the data into a struct
if openDatabase(){
let query = "select * from messages order by messageNumber asc"
do{
print(database)
let results: FMResultSet = try database.executeQuery(query, values: nil)
while results.next(){
let message = pulledMessage(convoWithUserID: String(describing: results.string(forColumn: "convoWithUserID")), convoWithUserName: String(describing: results.string(forColumn: "convoWithUserName")), messageString: String(describing: results.string(forColumn: "messageString")), senderID: String(describing: results.string(forColumn: "senderID")), timeSent: String(describing: results.string(forColumn: "timeSent")), messageNumber: Int(results.int(forColumn: "messageNumber")))
if messagesPulled == nil{
messagesPulled = [pulledMessage]()
}
messagesPulled.append(message)
print("The message that we have pulled are \(message)")
}
}
catch{
print(error.localizedDescription)
}
database.close()
}
}
}
Running the population of the data at the onset of app launch:
func applicationDidBecomeActive(_ application: UIApplication) {
// if FMDBManager.shared.createDatabase() {
// FMDBManager.shared.insertMessageData()
// }else{
// print("Not a chance, sonny")
// FMDBManager.shared.insertMessageData()
// }
FMDBManager.shared.loadMessageData()
}
Organizing the SQL data in order:
struct pulledMessage{//global struct
var convoWithUserID: String
var convoWithUserName: String
var messageString: String
var senderID: String
var timeSent: String
var messageNumber: Int
}
var messagesPulled: [pulledMessage]!
var messageConvoDictionary = [String: [pulledMessage]]()
//For the individual message convos
var fullUnorderedMessageArray = [[pulledMessage]]()
var fullOrderedMessageArray = [[pulledMessage]]()
//For the message table
var unorderedLastMessageArray = [pulledMessage]()
var orderedLastMessageArray = [pulledMessage]()
//For the table messages... FROM HERE..........................................
func organizeSQLData(messageSet: [pulledMessage]){
var i = 0
var messageUserID = String()
while i < messageSet.count{
if (messageSet[i]).convoWithUserID != messageUserID{
print("It wasn't equal")
print(messageSet[i])
messageUserID = messageSet[i].convoWithUserID
if messageConvoDictionary[messageUserID] != nil{
messageConvoDictionary[messageUserID]?.append(messageSet[i])
}else{
messageConvoDictionary[messageUserID] = []
messageConvoDictionary[messageUserID]?.append(messageSet[i])
}
i = i + 1
}else{
messageConvoDictionary[messageUserID]?.append(messageSet[i])
i = i + 1
}
}
}
func getLastMessages(messageSet: [String:[pulledMessage]]){
for (_, messages) in messageSet{
let orderedMessages = messages.sorted(by:{ $0.timeSent.compare($1.timeSent) == .orderedAscending})
let finalMessage = orderedMessages[0]
unorderedLastMessageArray.append(finalMessage)
}
print(unorderedLastMessageArray)
}
func orderLastMessage(messageSet: [pulledMessage]){
orderedLastMessageArray = messageSet.sorted(by:{ $0.timeSent.compare($1.timeSent) == .orderedDescending})
messagesListTableView.reloadData()
print("It wasn't\(orderedLastMessageArray)")
}
func getMessagesReady(){//for observer type function calls
organizeSQLData(messageSet: messagesPulled)
getLastMessages(messageSet: messageConvoDictionary)
orderLastMessage(messageSet: unorderedLastMessageArray)
//This one is for the individual full convos for if user clicks on a cell... its done last because its not required for the page to show up
orderedFullMessageConvos(messageSet: messageConvoDictionary)
let openedMessageConversation = fullOrderedMessageArray[(indexPath.row)]//not placed in its appropriate location, but it is just used to pass the correct array (actually goes in a prepareforSegue)
}
override func viewDidLoad() {
super.viewDidLoad()
getMessagesReady()
}
Then segue to the new controller (passing openedMessageConversation to messageConvo) and run this process on a button click:
let newMessage = pulledMessage(convoWithUserID: messageConvo[0].convoWithUserID, convoWithUserName: messageConvo[0].convoWithUserName, messageString: commentInputTextfield.text!, senderID: (PFUser.current()?.objectId)!, timeSent: String(describing: Date()), messageNumber: 0)
messageConvo.append(newMessage)
let newMessageSent = PFObject(className: "UserMessages")
newMessageSent["convoWithUserID"] = newMessage.convoWithUserID
newMessageSent["convoWithUserName"] = newMessage.convoWithUserName
newMessageSent["messageString"] = newMessage.messageString
newMessageSent["senderID"] = newMessage.senderID
let acl = PFACL()
acl.getPublicWriteAccess = true
acl.getPublicReadAccess = true
acl.setWriteAccess(true, for: PFUser.current()!)
acl.setReadAccess(true, for: PFUser.current()!)
newMessageSent.acl = acl
newMessageSent.saveInBackground()
It is the newMessageSent["convoWithUserID"] and newMessageSent["convoWithUserName"] that read with the optional() in the database.
So it turns out that the reason for this stems from the function run from loadMessageData. The use of String(describing: results.string(forColumn:) requires an unwrapping of results.String(forColumn:)!. This issue propagated throughout the data modification for the whole app and caused the optional() wrapping for the print statements that I was seeing.

Cannot convert value of type 'String?!' to expected argument type 'Notifications'

I am trying to check the id of a record before I put it into the array, using xcode swift
here is the code. But, i get the following error
Notifications.swift:50:46: Cannot convert value of type 'String?!' to expected argument type 'Notifications'
on this line
*if (readRecordCoreData(result["MessageID"])==false)*
Please can some one help to explain this error
import CoreData
struct Notifications{
var NotifyID = [NSManagedObject]()
let MessageDesc: String
let Messageid: String
init(MessageDesc: String, Messageid:String) {
self.MessageDesc = MessageDesc
self.Messageid = Messageid
// self.MessageDate = MessageDate
}
static func MessagesWithJSON(results: NSArray) -> [Notifications] {
// Create an empty array of Albums to append to from this list
var Notification = [Notifications]()
// Store the results in our table data array
if results.count>0 {
for result in results {
//get fields from json
let Messageid = result["MessageID"] as! String
let MessageDesc = result["MessageDesc"] as? String
let newMessages = Notifications(MessageDesc: MessageDesc!, Messageid:Messageid)
//check with id's from core data
if (readRecordCoreData(result["MessageID"])==false)
{
Notification.append(newMessages)
}
}
}
return Notification
}
//check id
func readRecordCoreData(Jsonid: String) -> Bool {
var idStaus = false
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
//2
let fetchRequest = NSFetchRequest(entityName: "ItemLog")
//3
do {
let resultsCD = try! managedContext.executeFetchRequest(fetchRequest)
if (resultsCD.count > 0) {
for i in 0 ..< resultsCD.count {
let match = resultsCD[i] as! NSManagedObject
let id = match.valueForKey("notificationID") as! String
if (Jsonid as String! == id)
{
idStaus = true
}
else{
idStaus = false
}
}
}
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
return idStaus
}
One of your methods is static and the other one is not :
func readRecordCoreData(Jsonid: String) -> Bool
static func MessagesWithJSON(results: NSArray) -> [Notifications]
Depending on what you want to accomplish you could declare both static, none, or replace
//check with id's from core data
if (readRecordCoreData(result["MessageID"])==false)
{
Notification.append(newMessages)
}
By
//check with id's from core data
if (Notifications.readRecordCoreData(Messageid)==false)
{
Notification.append(newMessages)
}
Not sure if the code will work past compilation however as there are many readability issues