I'm struggling with some code I'm playing with at the minute to sort the response alphabetically. Everything I try, it keeps flagging various errors and build issues.
My code currently runs:
class Category {
var name = "Category"
var offences = [Offence]()
required init(name: String) {
self.name = name
}
func addOffence(data: JSON) {
let offence = Offence(data: data)
offences.append(offence)
}
var imageName: String {
return name
}
}
Any ideas where I can slot it in?
I forgot to update this previously...
So in order to sort the API response, I simply amended the API request to sort by Active desc and that achieves the result I was looking for.
Related
Please help! I'm stuck for months now trying to perform a simple Google Search on my Swift App and I'm way past smashing my head through the wall!
I've tried both with ALAMOFIRE and With a Regular URLRequest, but since the result seems to be only in HTML Format, I Can't seem to parse the results correctly. Even when you look into the HTML Format, the code is for a Webpage, and it does NOT Include the Search Results.
I Would LOVE to have the search Results into a Simple Dictionary. Here is my Code:
let googleUrl:String = "https://cse.google.com/cse/publicurl?&output=json&cx=<MyGoogleKey>:<MyGoogleSKey>&q=q=+normal+search"
// Trying with AlamoFire:
Alamofire.request(googleUrl).response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.error)")
}.responseJSON(completionHandler: { response in
print("ResponseJSON: \(response)")
}).responseData(completionHandler: { response in
print("ResponseData: \(response)")
}).responseString(completionHandler: { response in
print("ResponseString: \(response)")
})
As you See I Try the Response in almost ALL Alamofire supported Types and I GEt NOTHING.
Here is Error #1 (.responseJSON):
The data couldn’t be read because it isn’t in the correct format.
ResponseJSON: FAILURE: responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}))
Here is DATA Response (.responseData):
ResponseData: SUCCESS: 4337 bytes
Here is the Response HTML (.responseString)
ResponseString: SUCCESS:
*> Google Custom Search
(function(){var cookie_path='/cse/';var
path_copy='/coop/';window._gaq =
window._ga...._AND_SO_ON_TILL_FULL_HTML_PAGE_IN_A_STRING....*
* I Only wish I Could have the Search Results in a Simple Dictionary...
Anyone? Please?
You seem to be using the Custom Search API for embedding into web pages.
This documentation is for what you want to do.
Here is an example using stack overflow as the search domain.
import UIKit
import Alamofire
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
search(query: "swift") { (results) in
for result in results {
dump(result)
}
}
}
func search(query:String, completion: #escaping (Array<SearchResult>)->()){
let id = "Custom search engine ID"
let key = "API key"
let url = "https://www.googleapis.com/customsearch/v1?key=\(key)&cx=\(id)&q=\(query)"
Alamofire.request(url).responseJSON { (response) in
var results = Array<SearchResult>()
if let dict = response.value as? Dictionary<String,Any> {
if let items = dict["items"] as? Array<Dictionary<String,Any>> {
for item in items {
if let result = SearchResult(dict: item) {
results.append(result)
} else {
print("Incomplete search result data.")
}
}
}
}
completion(results)
}
}
}
I have this struct to store the search results better. It doesn't contain all the values that the JSON results does. I just chose these ones for testing.
struct SearchResult {
var displayLink: String
var formattedUrl: String
var htmlFormattedUrl: String
var htmlSnippet: String
var htmlTitle: String
var link: String
var snippet: String
var title: String
init?(dict:Dictionary<String,Any>) {
guard
let displayLink = dict["displayLink"] as? String,
let formattedUrl = dict["formattedUrl"] as? String,
let htmlFormattedUrl = dict["htmlFormattedUrl"] as? String,
let htmlSnippet = dict["htmlSnippet"] as? String,
let htmlTitle = dict["htmlTitle"] as? String,
let link = dict["link"] as? String,
let snippet = dict["snippet"] as? String,
let title = dict["title"] as? String
else {
return nil
}
self.displayLink = displayLink
self.formattedUrl = formattedUrl
self.htmlFormattedUrl = htmlFormattedUrl
self.htmlSnippet = htmlSnippet
self.htmlTitle = htmlTitle
self.link = link
self.snippet = snippet
self.title = title
}
}
This is a couple of results that dump(result) prints out.
▿ CustomGoogleSearch.SearchResult
- displayLink: "stackoverflow.com"
- formattedUrl: "https://stackoverflow.com/questions/tagged/swift"
- htmlFormattedUrl: "https://stackoverflow.com/questions/tagged/<b>swift</b>"
- htmlSnippet: "<b>Swift</b> is an open-source programming language developed by Apple. Use the tag <br>\nonly for questions about language features, or requiring code in <b>Swift</b>. Use the ..."
- htmlTitle: "Newest '<b>swift</b>' Questions - Stack Overflow"
- link: "https://stackoverflow.com/questions/tagged/swift"
- snippet: "Swift is an open-source programming language developed by Apple. Use the tag \nonly for questions about language features, or requiring code in Swift. Use the ..."
- title: "Newest \'swift\' Questions - Stack Overflow"
▿ CustomGoogleSearch.SearchResult
- displayLink: "stackoverflow.com"
- formattedUrl: "stackoverflow.com/documentation/swift/topics"
- htmlFormattedUrl: "stackoverflow.com/documentation/<b>swift</b>/topics"
- htmlSnippet: "58 example-focused documentation topics for <b>Swift</b> Language."
- htmlTitle: "All <b>Swift</b> Language Topics - Stack Overflow"
- link: "http://stackoverflow.com/documentation/swift/topics"
- snippet: "58 example-focused documentation topics for Swift Language."
- title: "All Swift Language Topics - Stack Overflow"
Getting the keys
On this page, click the Get A Key button.
You then need to select or create a Google project, then you will be generated an API Key. So copy this an put in here let key = "API key"
For the Search Engine ID, go to your console. Now either select a search engine or create a new one. You'll get to a page like this.
Click the Search engine ID button, this will display a screen with your id, then put that here let id = "Custom search engine ID"
Here I'm trying to get currency values of INR for last 30 dates.
I'm fetching last 30 dates values of INR currency using Alamofire.
//strDates contains all 30 days dates
for i in 0..<strDates.count {
Alamofire.request("http://api.fixer.io/\(strDates[i])?base=USD").responseJSON { response in
if let arr = response.result.value as? [String:AnyObject]
{
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
}
}
}
print(sValues) //Print nil
setChart(dataPoints: strDates, values: sValues)
How do I use this sValues array outside the Alamofire block.
Here, Actually I'm sending dates & INR values as a parameter to below method.
func setChart(dataPoints: [String], values: [Double]) {
barChartView.noDataText = "You need to provide data for the chart."
for i in 0..<dataPoints.count {
let dataEntry = BarChartDataEntry(x: Double(i), yValues: [values[i]])
dataEntries.append(dataEntry)
}
let chartDataSet = BarChartDataSet(values: dataEntries, label: "INR Rates(₹)/$")
let chartData = BarChartData(dataSet: chartDataSet)
barChartView.data = chartData
barChartView.xAxis.labelPosition = .bottom
barChartView.rightAxis.enabled = false
barChartView.leftAxis.enabled = true
barChartView.data?.setDrawValues(false)
barChartView.leftAxis.granularityEnabled = true
barChartView.leftAxis.granularity = 1.0
barChartView.xAxis.granularityEnabled = true
barChartView.xAxis.granularity = 1.0
barChartView.leftAxis.axisMinimum = 70//65
barChartView.leftAxis.axisMaximum = 60//70
//chartDataSet.colors = [UIColor.cyan, UIColor.green]
}
Are you looking for the chart to refresh when only when ALL of the calls are done? If so you use a pattern like this:
var completedCalls = 0
for i in 0..<strDates.count {
Alamofire.request("http://api.fixer.io/\(strDates[i])?base=USD").responseJSON { response in
if let arr = response.result.value as? [String:AnyObject]
{
completedCalls += 1
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
if completedCalls = strDates.count {
DispatchQueue.main {
setChart(dataPoints: strDates, values: sValues)
}
}
}
}
}
The idea is to count how many API requests come back and only take action when all of the requests are done (you should check that all of them actually succeeded as well and show an error if any fail).
Your codes cause sValues to print nil because your print(sValues) ran before your request response. This block
if let arr = response.result.value as? [String:AnyObject]
{
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
}
runs only when your request returns with a value or an error. So any usage of values from your request should also be done here. If you want to use the value to update certain UI element or database, you should also call the method from within this block to ensure that you already retrieved the value from its source.
I do suggest you adopt this mentality when programming any asynchronous codes as you can never expect when your codes running in another thread or the server to return. It is always safer to run you methods that requires the return value in a completion block that runs only when the asynchronous codes have completed.
If you're displaying them in a table, create a strong variable of type Array, or some other data provider (e.g. CoreData, SQLite) and store the results there. In the completion block for AF, set the value of the Array variable (or update your local data provider) to the API result data, then call reloadData() on your table. You'll also need to configure the UITableView delegate and datasource methods.
I'm building an API client using Siesta and Swift 3 on Xcode 8. I want to be able to fetch an entity using a Siesta resource, then update some of the data and do a patch to the API.
The issue is that having an entity, if I save the JSON arrays in my entity fields I can't send them back to the server, I get the following error:
▿ Siesta.RequestError
- userMessage: "Cannot send request"
- httpStatusCode: nil
- entity: nil
▿ cause: Optional(Siesta.RequestError.Cause.InvalidJSONObject())
- some: Siesta.RequestError.Cause.InvalidJSONObject
- timestamp: 502652734.40489101
My entity is:
import SwiftyJSON
import Foundation
struct Order {
let id: String?
let sessionId: String?
let userId: Int?
let status: String?
let comment: String?
let price: Float?
let products: Array<JSON>?
init(json: JSON) throws {
id = json["id"].string
sessionId = json["sessionId"].string
userId = json["userId"].int
status = json["status"].string
comment = json["comment"].string
price = json["price"].float
products = json["products"].arrayValue
}
/**
* Helper method to return data as a Dictionary to be able to modify it and do a patch
**/
public func toDictionary() -> Dictionary<String, Any> {
var dictionary: [String:Any] = [
"id": id ?? "",
"sessionId": sessionId ?? "",
"userId": userId ?? 0,
"status": status ?? "",
"comment": comment ?? ""
]
dictionary["products"] = products ?? []
return dictionary
}
}
What I'm doing is:
MyAPI.sessionOrders(sessionId: sessionId).request(.post, json: ["products": [["product": productId, "amount": 2]], "comment": "get Swifty"]).onSuccess() { response in
let createdObject : Order? = response.typedContent()
expect(createdObject?.sessionId).to(equal(sessionId))
expect(createdObject?.comment).to(equal("get Swifty"))
expect(createdObject?.products).to(haveCount(1))
expect(createdObject?.price).to(equal(product.price! * 2))
if let createdId = createdObject?.id {
var data = createdObject?.toDictionary()
data?["comment"] = "edited Swifty" // can set paid because the user is the business owner
MyAPI.order(id: createdId).request(.patch, json: data!).onSuccess() { response in
result = true
}.onFailure() { response in
dump(response) //error is here
}
}
}
Resources:
func sessionOrders( sessionId: String ) -> Resource {
return self
.resource("/sessions")
.child(sessionId)
.child("orders")
}
func order( id: String ) -> Resource {
return self
.resource("/orders")
.child(id)
}
Transformers:
self.configureTransformer("/sessions/*/orders", requestMethods: [.post, .put]) {
try Order(json: ($0.content as JSON)["data"])
}
self.configureTransformer("/orders/*") {
try Order(json: ($0.content as JSON)["data"])
}
I've managed to circle this by creating dictionary structures like:
let products: Array<Dictionary<String, Any>>?
products = json["products"].arrayValue.map({
["product": $0.dictionaryValue["product"]!.stringValue, "amount": $0.dictionaryValue["amount"]!.intValue]
})
But I live in a hell of downcasts if I need to modify anything:
var data = createdObject?.toDictionary()
data?["comment"] = "edited Swifty"
//if I want to modify the products...
var products = data?["products"] as! Array<Dictionary<String, Any>>
products[0]["amount"] = 4
data?["products"] = products
How can I send those original JSON arrays with Siesta? They're really easy to modify and read! I've browsed the siesta docs and github issues with no success...
Your problem is a mismatch between SwiftyJSON and Foundation’s JSONSerialization; Siesta just happens to be in the middle of it.
InvalidJSONObject is Siesta telling you that Foundation doesn’t understand the thing you gave it — which would be the value returned by your toDictionary() method. Most of the things in that dictionary look fine: strings, ints, a float. (Careful about using float for money, BTW.)
The culprit is that products array: it’s [JSON], where JSON is a SwiftyJSON type that Foundation doesn’t know what to do with. You should be in the clear if you turn the JSON values back into simple dictionaries:
dictionary["products"] = (products ?? []).map { $0.dictionaryObject }
If that doesn’t do it, or if you need to diagnose a similar error in the future, remove all the values from the offending dictionary and then add them back in one at a time to see which one is tripping up JSONSerialization.
I've been busy creating a Schedule app for a school project but can't really seem to figure it out. Someone on this site already helped me to filter out data from a JSON link I had, but I can't really make it work. I want to put the Data that I get in my console into a Table view. I get roster info for 5 days, so every cell should have different information. I've watched several youtube videos and looked around SO but its either not really what I need, or I don't understand the way it works.
The code I currently have :
typealias JSONDictionary = [String:Any]
let savedValue = UserDefaults.standard.string(forKey: "LoginUserID")
let url = URL(string: "(url)")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print ("ERROR")
}
else
{
if let content = data
{
do {
if let json = try JSONSerialization.jsonObject(with:data!, options: []) as? JSONDictionary {
if let days = json["Days"] as? [JSONDictionary] {
for day in days {
print(day["DayName"] as! String)
if let lessons = day["Lessons"] as? [JSONDictionary] {
for lesson in lessons {
let classRoom = lesson["Classroom"] as! String
let name = lesson["Name"] as! String
let teacher = lesson["Teacher"] as! String
print(teacher)
}
}
}
}
}
} catch {
print(error)
}
}
}
}
task.resume()
So I need to first of all be able to get info for example just Monday. Then i need to divide a table cell into 7 parts, and then put all the info i get into those parts.
I hope someone can help out because I'm really struggling
You are parsing the data. That's great, but you need a reference to it, i.e. an object. (UPDATE: looking at your data and your parsing, you are going to have some issues. DayName & Lesson are dictionaries inside an array of the dictionary Day. Your parsing isn't quite complete...)
TableViews work best when access an array of objects. To create an array of objects, you'll need to create a class ("Lessons" I'm assuming?). In the class, you'll set up the properties that a Lesson object should have -> Classroom, Name, Teacher. Here is a Class file I made for a simple project.
class DailyWeather: NSObject {
var time : Double = 0.0
var summaryDescription : String = ""
var temperatureMin : Double = 0.0
var temperatureMax : Double = 0.0
var precipProbability : Double = 0.0
override init() {
super.init()
self.time = 0.0
self.summaryDescription = ""
self.temperatureMin = 0.0
self.temperatureMax = 0.0
self.precipProbability = 0.0
}
}
As you can see, I declared all my properties that I'll need in the class. I took an easy route and gave them all values to start, whether they were empty strings or values of 0 if the data type was an Int or Double. Then I initialized them. After that, I was able to create an object, and set it's values when I parsed the JSON data.
if let dailyDict = jsonDictionary["daily"] as? JSONDictionary {
print(dailyDict)
if let dataArray = dailyDict["data"] as? JSONArray {
print(dataArray)
for dataDict in dataArray {
let dailyWeatherData = DailyWeather()
if let summary = dataDict["summary"] as? String {
dailyWeatherData.summaryDescription = summary
} else {
print("I could not parse summary")
} ... <continue to parse and then append the object at the end before the loop begins again>
As you can see, just after I began looping through my Data array, I created an instance of the Class I created above
let dailyWeatherData = DailyWeather()
From there, I could set the properties of that dailyWeatherData object while I parsed it. By placing the object inside the loop, it will create a new object for you every time.
From there, you'll need to append the object at the end of the loop before it starts over again to an array of objects, in my case, an array of DailyWeather objects.
I'm going to assume you're doing everything in the ViewController (which is where most newbies start) so put your object array at the top before ViewDidLoad()
var DailyWeatherArray = [DailyWeather]()
This creates an array that will hold all those objects you're create in the loop while you parse the data. You'll put those objects by appending the object to the array just before the end of the loop.
After that, you can just access the array from the TableViewDelegates and DataSource functions you will be using to populate the tableview. I'll let you figure that part out as I've given you quite a bit to be going on with.
** My code was in an older version of Swift, so there may be a couple of discrepancies...
[
{
"ID": 0,
"Name": "PHI"
},
{
"ID": 0,
"Name": "ATL"
}
]
I'm using SwiftyJSON and Alamofire. This is what is being returned. I want to loop through each of these objects now in my code. I'm having trouble getting this information though.
json[0]["Name"].string seems to return nil, and I'm not sure why. The JSON object is definitely getting the JSON, when I print it to the console it looks exactly like above.
I also tried:
var name = json[0].dictionary?["Name"]
Still nil though.
Any ideas?
EDIT
Here's the code I'm using to retrieve the JSON.
Alamofire.request(.GET, "url", parameters: nil, encoding: .JSON, headers: nil).responseJSON
{
(request, response, data, error) in
var json = JSON(data!)
//var name = json[0].dictionary?["Name"]
}
Your JSON is valid (at least this snippet is), and your first method to retrieve the data from SwiftyJSON is correct.
On the other hand, the Alamofire snippet you showed didn't compile for me, I had to change the signature.
Given that in the comments you say that not only json[0]["Name"] is nil but json[0] is also nil, I think you have a problem with how your data is fetched.
I tested this version of the Alamofire method in a sample app and it worked well:
Alamofire.request(.GET, yourURL).responseJSON(options: nil) { (request, response, data, error) in
let json = JSON(data!)
let name = json[0]["Name"].string
println(name)
}
In the screenshot, the URL is my local server, with your JSON copy pasted in "test.json".
This "answer" is an extension of my comment, I needed room to show more info...
I think you might try this.
first convert JSON(data) using swifyJSON
let json = JSON(data!)
then count the array element in data.
let count: Int? = json.array?.count
after that use for loop to reach array element one.
for index in 0...count-1 {
self.idArray.append(json[index]["ID"].stringValue)
self.nameArray.append(json[index]["Name"].stringValue)
}
you will get sorted data in "idArray" and "nameArray". try to print both the array.
You do not have to use index to parse array. You can get single json object from array and use it to access the data:
for (index: String, subJson: JSON) in json {
println("Current Index = \(index)")
if let _id = subJson["ID"].int {
println("ID = \(_id)")
} else {
println("Error ID")
}
if let _name = subJson["Name"].string {
println("Name = \(_name)")
} else {
println("Error Name")
}
}