How to update Array from an API Call in Swift 2.0? [closed] - arrays

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I'm looking to update an array called 'events' from an api.
Any ideas? Thanks in advance!
var events = [AnyObject]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url : String = "http://api/tickets"
let request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
NSURLSession.sharedSession().dataTaskWithRequest(request) {
(data : NSData?, response : NSURLResponse?, error : NSError?) in
dispatch_async(dispatch_get_main_queue()) {
// Main thread
self.tableView.reloadData()
}
}
}

So you've got your data back, now you need to convert the data into an object that you can work with. Your event array is an array of AnyObject, which can be cumbersome to work with. Instead, I would recommend that you create a model for these objects.
Since I don't know what your data looks like I'll just make up a model, you'll need to edit this to suit your needs:
struct Ticket {
let id : Int
let description : String
// pass your data object directly into your initializer.
init?(data: [String:AnyObject]) {
guard let itemId = data["id"] as? Int else { return }
guard let itemDesc = data["description"] as? String else { return }
id = itemId
description = itemDesc
}
}
// note: this initializer will fail (by design) if you pass in an
// object that doesn't have the proper requirements. You can exchange the
// guards for if-lets to avoid this behavior
Now, rather than using an array of AnyObject, you work specifically with your modeled item:
// use your model for easy data access
var events = [Ticket]()
Next you update your NSURLSession's closure to create and append your new model object to your array:
NSURLSession.sharedSession().dataTaskWithRequest(request) {
(data : NSData?, response : NSURLResponse?, error : NSError?) in
// make sure you have data
guard let returnedData = data else {
print("no data was returned")
return
}
do {
// convert your object to JSON data... the following code
// may differ depending on how your JSON is formed. However,
// the concept is still the same
// get array of dictionaries
let jsonObject = try NSJSONSerialization.JSONObjectWithData(returnedData, options: .MutableLeaves) as! [[String: AnyObject]]
// loop over your array and create Ticket objects
jsonObject.forEach { item in
var ticket = Ticket(item)
// append your tickets to the array
events.append(ticket)
}
} catch let error {
print(error)
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}

Let's say, that what is returned to you is Array of tickets and only array of tickets. If you want to get this array, as [AnyObject] you could write:
if let arrayOfAnyObjects = data as? [AnyObject] {
events = arrayOfAnyObjects
}
in this way, you will try to cast data from type NSData to type [AnyObject] if possible. And if it succeed, you will assign it.

You have the right idea. What part don't you understand?
How you parse the response from the server depends on the format of the data. Is it JSON? XML? Some other format?
EDIT:
Ok, you said the data from the server is in JSON format. So inside the completion block of your data request, use NSJSONSerialization to convert your data to a collection object. It looks like Dan gave a pretty complete explanation on how to do that while I was away from my computer.

Related

How do I fill an array of data with CoreData data?

I am learning to use SwiftUI with Core Data.
I am trying to fill a Line Chart with saved weight data like below:
LineView(data: [0, 32, 445, 56, 99])
I’ve gotten as far as this but im getting an error on the "var locations = ..." line saying "Type of expression is ambiguous without more context"
var fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "UserWeight")
var locations = mocW.executeFetchRequest(fetchRequest, error: nil) as [UserWeight]
for weight in weights {
print(weights.userWeight)
}
Any help on this and how i would populate the line chart with this data would be greatly appreciated!
For SwiftUI, I suspect that you are attempting to achieve the following...
struct YourView: View {
#FetchRequest(entity: UserWeight.entity(),
sortDescriptors: []
) var weights: FetchedResults<UserWeight>
var body: some View {
ForEach(weights) { weight in
Text(weight.userWeight)
}
}
}
Core Data entities confirm to the Identifiable protocol, so you'e able to drop the id: parameter in the ForEach structure...
ForEach(weights) { weight in
Otherwise you'd need to use...
ForEach(weights, id: \.self) { weight in
Note: As an aside, it would help us if you could provide more detail in your questions in the future. The more information you provide, the easier it is for the community to understand your issue and provide a suitable response. Remember that your question and our answers may not only help you, but also help others in the future as they visit the site looking for answers to their own problems.
How do I ask a good question?
if let appDelegate =
UIApplication.shared.delegate as? AppDelegate {
let managedObjectContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<Memory>(entityName: "Memory")
let sortDescriptor = NSSortDescriptor(key: "rating", ascending: false)
var predicate = NSPredicate(format: "mediaType == %#", "image")
fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = [sortDescriptor]
do {
result = try managedObjectContext.fetch(fetchRequest)
} catch {
}
}
"result" is an array of, in my case, Memory objects which are instances of NSManagedObject. To access properties and populate views I do this:
for memory in result {
let value = memory.entityPropertyName
}
I think this should be enough to get your started, let me know if you have more questions.
If UserWeight is a subclass of NSManagedObject, you should declare your fetch request as
var fetchRequest = NSFetchRequest<UserWeight>(entityName: "UserWeight")
Or else as
let fetchRequest: NSFetchRequest<UserWeight> = UserWeight.fetchRequest()
Then you can use the fetch like this, and the type of locations will be Array<UserWeight>.
let locations = try context.fetch(fetchRequest)
I'm not sure where executeFetchRequest(fetchRequest, error: nil) comes from-- it's not a function defined by NSManagedObjectContext in Swift. It resembles the Objective-C version of the function, but in Swift it's different.

Insert multiple records into database with Vapor3

I want to be able to bulk add records to a nosql database in Vapor 3.
This is my Struct.
struct Country: Content {
let countryName: String
let timezone: String
let defaultPickupLocation: String
}
So I'm trying to pass an array of JSON objects but I'm not sure how to structure the route nor how to access the array to decode each one.
I have tried this route:
let countryGroup = router.grouped("api/country")
countryGroup.post([Country.self], at:"bulk", use: bulkAddCountries)
with this function:
func bulkAddCountries(req: Request, countries:[Country]) throws -> Future<String> {
for country in countries{
return try req.content.decode(Country.self).map(to: String.self) { countries in
//creates a JSON encoder to encode the JSON data
let encoder = JSONEncoder()
let countryData:Data
do{
countryData = try encoder.encode(country) // encode the data
} catch {
return "Error. Data in the wrong format."
}
// code to save data
}
}
}
So how do I structure both the Route and the function to get access to each country?
I'm not sure which NoSQL database you plan on using, but the current beta versions of MongoKitten 5 and Meow 2.0 make this pretty easy.
Please note how we didn't write documentation for these two libraries yet as we pushed to a stable API first. The following code is roughly what you need with MongoKitten 5:
// Register MongoKitten to Vapor's Services
services.register(Future<MongoKitten.Database>.self) { container in
return try MongoKitten.Database.connect(settings: ConnectionSettings("mongodb://localhost/my-db"), on: container.eventLoop)
}
// Globally, add this so that the above code can register MongoKitten to Vapor's Services
extension Future: Service where T == MongoKitten.Database {}
// An adaptation of your function
func bulkAddCountries(req: Request, countries:[Country]) throws -> Future<Response> {
// Get a handle to MongoDB
let database = req.make(Future<MongoKitten.Database>.self)
// Make a `Document` for each Country
let documents = try countries.map { country in
return try BSONEncoder().encode(country)
}
// Insert the countries to the "countries" MongoDB collection
return database["countries"].insert(documents: documents).map { success in
return // Return a successful response
}
}
I had a similar need and want to share my solution for bulk processing in Vapor 3. I’d love to have another experienced developer help refine my solution.
I’m going to try my best to explain what I did. And I’m probably wrong.
First, nothing special in the router. Here, I’m handling a POST to items/batch for a JSON array of Items.
router.post("items", "batch", use: itemsController.handleBatch)
Then the controller’s handler.
func createBatch(_ req: Request) throws -> Future<HTTPStatus> {
// Decode request to [Item]
return try req.content.decode([Item].self)
// flatMap will collapse Future<Future<HTTPStatus>> to [Future<HTTPStatus>]
.flatMap(to: HTTPStatus.self) { items in
// Handle each item as 'itm'. Transforming itm to Future<HTTPStatus>
return items.map { itm -> Future<HTTPStatus> in
// Process itm. Here, I save, producing a Future<Item> called savedItem
let savedItem = itm.save(on: req)
// transform the Future<Item> to Future<HTTPStatus>
return savedItem.transform(to: HTTPStatus.ok)
}
// flatten() : “Flattens an array of futures into a future with an array of results”
// e.g. [Future<HTTPStatus>] -> Future<[HTTPStatus]>
.flatten(on: req)
// transform() : Maps the current future to contain the new type. Errors are carried over, successful (expected) results are transformed into the given instance.
// e.g. Future<[.ok]> -> Future<.ok>
.transform(to: HTTPStatus.ok)
}
}

How to order PFObjects based on its creation date?

I have some user comments stored in a database (parse-server) that I would like to would like to display on my viewController's viewDidLoad(). I can easily pull the comment objects as follows:
super.viewDidLoad()
func query(){
let commentsQuery = PFQuery(className: "Comments")
commentsQuery.whereKey("objectId", equalTo: detailDisclosureKey)
commentsQuery.findObjectsInBackground { (objectss, error) in
if let objects = objectss{
if objects.count == 1{
for object in objects{
self.unOrderedComments.append(object)
}
}
}
}
}
This query dumps all of the of the comments in the unOrederedComments array. Each comment is added to the database with a createdAt property automatically being added relating the exact time of its creation. This property is a string with (as an example) the form: "2017-08-13T19:31:47.776Z" (the Z at the end is at the end of every string... not exactly sure why its there but its constant). Now, each new comment is added in order to the top of database and thus any queried result should be in order regardless. However, I would like to make sure of this by reordering it if necessary. My general thought process is to use .sorted, but I cannot figure out how to apply this to my situation
func orderComments(unOrderComments: [PFObject]) -> [PFObject]{
let orderedEventComments = unOrderedEventComments.sorted(by: { (<#PFObject#>, <#PFObject#>) -> Bool in
//code
})
}
This is the generic set up but I cannot, despite looking up several examples online figure out what to put in the <#PFObject#>'s and in the //code. I want to order them based on the "createdAt" property but this is not achieved via dot notation and instead requires PFObject["createdAt"] and using this notation keeps leading to error. I feel as so though I may need to set up a custom predicate but I do not know how to do this.
I was in the same situation, what I did was to first create an array of structs with the data I downloaded where I turned the string createdAt into a Date, then used this function:
dataArrayOrdered = unOrderedArray.sorted(by: { $0.date.compare($1.date) == .orderedAscending})
(.date being the stored Date inside my array of strcuts)
Try this code, notice that I assumed you have a variable name called ["Comments"] inside your Parse database, so replace if necessary. Also, I realised that createdAt it's in Date format, so there was no need to change it from String to Date, chek if it works the same for you, if it doesn't refer to this: Swift convert string to date.
struct Comment {
var date = Date()
var comment = String()
}
var unOrderedComments: [Comment] = []
var orderedComments = [Comment]()
override func viewDidLoad() {
super.viewDidLoad()
query()
}
func query(){
let commentsQuery = PFQuery(className: "Comments")
commentsQuery.findObjectsInBackground { (objectss, error) in
if let objects = objectss{
if objects.count >= 1{
for object in objects{
let newElement = Comment(date: object.createdAt!, comment: object["Comments"] as! String)
self.unOrderedComments.append(newElement)
print(self.unOrderedComments)
}
}
self.orderedComments = self.unOrderedComments.sorted(by: { $0.date.compare($1.date) == .orderedAscending})
print(self.orderedComments)
}
}
}

In Swift, how do you execute one query to Parse before another on PFQueryTableViewController?

I am trying to do two queries to Parse in my PFQueryTableViewController. I am trying to do one query to obtain all of the objects relating to the currentUser and then place those objects into an array. After the array is filled with its objects, I would like to do another query from
override func queryForTable() -> PFQuery { }.
This second query will use the objects from the array that was created from the first query, in order to retrieve objects of its own.
I don't know how to make the first query execute before the second. As of right now, the second query will execute before the first one before it has a chance to fill the array and as a result messes up the query for the second one. The following is what i have for my code at this point.
class PFNewsFeedTableViewController: PFQueryTableViewController {
var leadersArray : NSMutableArray = NSMutableArray()
override func viewDidAppear(animated: Bool) {
leadersArray.removeAllObjects()
var findLeaders = PFQuery(className: "Follow")
findLeaders.whereKey("follower", equalTo: PFUser.currentUser()!)
findLeaders.orderByDescending("createdAt")
findLeaders.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
//If no error
if error == nil {
println("Successfully retrieved \(objects!.count) leaders.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
self.leadersArray.addObject(object.objectForKey("leader")!)
}
}
}
self.tableView.reloadData()
self.queryForTable()
}
}
override func queryForTable() -> PFQuery {
var findContent = PFQuery(className: "Content")
findContent.whereKey("user", containedIn: self.leadersArray as [AnyObject])
findContent.orderByDescending("createdAt")
return findContent
}
}
Is there a way to determine the order in which these queries get executed? Any help to this problem would be appreciated.
Yes, you can.
Let me first start by saying that it does sound like you should try to maybe rethink your data model, as executing two queries in succession like this is often a sign of focusing too much on traditional data modeling rather than the more pragmatic nosql-style of modeling where you (especially for mobile apps) should model for queries rather than normalisation.
If you were to solve your case like you said, you can employ the Bolts framework. No need to install this, as this is already installed as part of Parse (which uses Bolts behind the scenes).
It could then look something like this (just a quick write-up. Will probably not work out of the box, but you should be able to make it fit):
var findLeaders = PFQuery(className: "Follow")
findLeaders.whereKey("follower", equalTo: PFUser.currentUser()!)
findLeaders.orderByDescending("createdAt")
findLeaders.findObjectsInBackground().continueWithBlock {
(task: BFTask!) -> AnyObject! in
//If no error
if task.error != nil {
print("Something went wrong...")
} else {
// Do something with the found objects
if let objects = task.result as? [PFObject] {
print("Successfully retrieved \(objects.count) leaders.")
for object in objects {
self.leadersArray.addObject(object.objectForKey("leader")!)
}
}
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
self.queryForTable()
return nil
}
I figured out the problem and it looks like this
override func queryForTable() -> PFQuery {
var findLeaders = PFQuery(className: "Follow")
findLeaders.whereKey("follower", equalTo: PFUser.currentUser()!)
findLeaders.orderByDescending("createdAt")
var findContent = PFQuery(className: "Content")
findContent.whereKey("user", matchesKey: "leader", inQuery: findLeaders)
findContent.orderByDescending("createdAt")
return findContent
}

How to put Parse data in an array

I need to put data in the array using a query, but it only works in the block. I searched here and found out that it's because the findObjectsInBackgroundWithBlock is asynchronous, but how can I make it synchronous then?
var cities = [String]()
func loadCityArray() {
let citiesVisited = PFQuery(className: "Trips")
citiesVisited.whereKey("userId", equalTo: (PFUser.currentUser()?.objectId)!)
citiesVisited.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let objects = objects {
for object in objects {
let city = object["cityId"] as! String
let query = PFQuery(className: "Cities")
query.whereKey("objectId", equalTo: city)
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if let objects = objects {
for object in objects {
self.cities.append(object["cityName"] as! String)
}
}
})
}
}
You do not want to make that synchonous on the main thread. That is strongly discouraged.
You rather want to store the things you need from the request in instance variables and tell the corresponding View Controller new values are present in the block.
edit:
Suppose you have an object waiting to use the data:
var chart : DataConsumer?
In the block where you get the data,
chart.useData(data)
edit 2:
the useData function should keep track of changes in the data set and make use of the information that new data arrived. For example, by displaying it.

Resources