I am fairly new to swift programming language and I am trying to find a way to make a HTTP/API call from swift. I know how to do it exactly in Javascript because I have worked with it before quite a lot. But I can't seem to figure out in swift.
I found this code online and I have been testing this in XCODE Playground. But it is returning "nil"
The Code is below:
//: Playground - noun: a place where people can play
import UIKit
import XCPlayground // add this in
XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true)
var str = "Hello, playground"
// create a session object
let session = NSURLSession(
configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
// make a network request for a URL, in this case our endpoint
session.dataTaskWithURL(NSURL(string: "http://echo.jsontest.com/key/value/one/two")!,
completionHandler: { (taskData, taskResponse, taskError) -> Void in
// create an NSArray with the JSON response data
var jsonReadError:NSError?
let jsonArray = NSJSONSerialization.JSONObjectWithData(
taskData, options: nil, error: &jsonReadError) as! [AnyObject]
}).resume()
Can anyone tell me what am I doing wrong. Thank you.
If I have made a mistake please let me know or if you need further information please let me know.
Try this:
let jsonResult = NSJSONSerialization.JSONObjectWithData(taskData, options: NSJSONReadingOptions.MutableContainers, error: &jsonReadError) as! NSDictionary
Related
One of my view controllers decodes and prints html from a web page. I've done searches on stackoverflow and in example project on github and it seems that people are using Alamofire with Swiftsoup to do this.
I'm a beginner but I am trying to understand why I would need AlamoFire when I can just use URLSessions? Is it better to use Alamofire?
My use case is simple, I think. If I use Alamofire,
let getURL = "https://www.someurl.com/extension"
Alamofire.request(getURL, method: .post, parameters: nil, encoding: URLEncoding.default).validate(contentType: ["application/x-www-form-urlencoded"]).response { (response) in
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
do {
parseHTML()
}
}
}
If I use URLSessions, I think it would like this:
let httpURL = URL(string: "https://www.someurl.com/extension")!
let httpTask = URLSession.shared.dataTask(with: httpURL) {
(data, response, error) in
guard let validData = data, error == nil else {
DispatchQueue.main.async(execute: {
print("Error getting paragraph\n") })
return
}
var results = String(data: data!, encoding: String.Encoding.utf8) ?? "Unable to read Paragraph HTML\n"
DispatchQueue.main.async(execute: {
print("Correctly read from Paragraph HTML\n")
parseHTML()
})
}
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async(execute: {
httpTask.resume()
})
Side question: Is Swiftsoup the go to for decoding HTML? Is there something built in that can be used instead?
Alamofire/AFNetworking (AFNetworking is the objective-c version) gained traction as an alternative to Apple's NSURLConnection class, that was much more low level and involved a lot of boilerplate code. It was not as easy to establish a download task or anything with NSURLConnection, AFNetworking (at the time) made it easier to perform the tasks like in your question without having to write too much code.
Around iOS7, Apple released NSURLSession to replace NSURLConnection, which made it quite similar to how AlamoFire do things. At this point personally, I feel that using NSURLSession/URLSession is fine and straightforward enough. Maybe AlamoFire is a bit easier to use but overall they are similar. The only times I end up moving towards AlamoFire these days is when I face some type of limitation.
So tl;dr, pre iOS7, AFNetworking was a much easier and straightforward way of working with download tasks. Post iOS7 URLSessions became easier to work with and set up.
I'm working with AEXML to write and read xml documents in Swift. I have the writing working no problem. And I have everything setup for the reading, but I can't seem to turn the saved text xml into the document object. It only ever gets the first element and none of the children. I've tried removing all the lines and spaces but still nothing. The content is reading into the String just fine and I've tried converting the data back to a string and it isn't getting messed up in conversion. Is this even possible with AEXML or am I just doing it wrong?
let doc = AEXMLDocument()
let content = try String(contentsOf:NSURL(string:file) as! URL)
let data = content.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
let xml = NSString(data:data, encoding:String.Encoding.utf8.rawValue)
try doc.loadXMLData(data)
So I figured out that I was actually using an outdated version of AEXML which clearly wasn't working anymore. The updated code looks like this.
let content = try String(contentsOf:NSURL(string:file) as! URL)
let data = content.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
let options = AEXMLOptions()
let doc = try AEXMLDocument(xml:data,options:options)
I am relatively new to Swift and programming. I'm developing an app which heavily relies on information downloaded from the server. So in a lot of ViewControllers, I use NSURLSession and NSJSONSerialization to download the JSON into my app.
Every time I wanted to subscript the dictionary, for example timetableDict?["timetable"]["class"]["day"]["lesson"][0]["name"], something like Cannot subscript a value of type [String : AnyObject] with an index type of String shows up as an error.
I understand that I should avoid using AnyObject in my code, but the dictionary from the server is heavily nested with structures like this one:
"timetable": ["class": ({
day = ({
lesson = ({
name = (MATHEMATICS, ENGLISH),
classOrder = 0,
teacher = (Someone)
}),
({
name = FRENCH,
classOrder = 1,
teacher = (Someone)
)}
)}
)}]
The problem with this structure is that it is heavily nested and has different types when it gets to "name", "classOrder" and "teacher". It is very hard for me not to use AnyObject. However, this error has been annoying for me for a very long time. I would greatly appreciate it if someone could help me out on this. Thanks in advance!
I suggest taking a look at SwiftyJSON : https://github.com/SwiftyJSON/SwiftyJSON
It's a framework/library designed to handle JSON in a very much more elegant way than what's build into swift (especially for heavy nested structures like yours). It's easy to use and has an excellent tutorial.
EDIT: (Added sample code)
Example from the swiftyJSON tutorial :
let JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
if let statusesArray = JSONObject as? [AnyObject],
let status = statusesArray[0] as? [String: AnyObject],
let user = status["user"] as? [String: AnyObject],
let username = user["name"] as? String {
// Finally we got the username
}
even with optional chaining quite messy :
let JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
if let username = (((JSONObject as? [AnyObject])?[0] as? [String: AnyObject])?["user"] as? [String: AnyObject])?["name"] as? String {
// What a disaster
}
With swiftyJSON
let json = JSON(data: dataFromNetworking)
if let userName = json[0]["user"]["name"].string {
//Now you got your value
}
SwiftlyJSON that #Glenn mentions is not a bad system (though I find it over-reliant on string lookups, which is fragile). The deeper point, however, is that you want to validate and parse your JSON in one place, and turn it into a non-JSON Swift data structure for use by the rest of your program. SwiftlyJSON can be a decent tool for doing that, but I would use it to unload data into a an array of structs.
Working with JSON throughout your system is extremely error-prone and cumbersome, even wrapped up in SwiftlyJSON. If you unload the data once, then you can check for errors one time and everywhere else you know that the data is correct. If you pass around JSON structures, then you must check every single time and deal with possibly missing or incorrect data. Think through the case where the server sends you JSON in a format you didn't expect. How many times do you want to test for that?
In one of my apps I need to geocode address string. At first I considered using CLGeocoder. However, after I tried it I stumbled upon a problem which I described in this question.
The solution was to use Google's Geocoding APIs instead. I have now switched to them and managed to get them working by having the following functions:
func startConnection(){
self.data = NSMutableData()
let urlString = "https://maps.googleapis.com/maps/api/geocode/json?address=\(searchBar.text!)&key=MYKEY"
let linkUrl:NSURL = NSURL(string:urlString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!)!
let request: NSURLRequest = NSURLRequest(URL: linkUrl)
let connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)!
connection.start()
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!){
self.data.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? [String: AnyObject] {
print(json)
}
}
catch {
print("error1")
}
}
This works great and resolves the problem which I had with CLGeocoder. However, in addition to extracting the coordinates of place, I need to also use Google's Timezone APIs to extract the timezone for each place.
Doing this with the NSURLConnection or NSURLSession seems to me a bit difficult as I would need to keep track of which session/connection returns. So, I would like to have some solution which uses completion handlers.
I have tried using Alamofire framework (using the correct branch for Swift 2.0). However, it seems like request() function is the wrong one to use in this case. I have tried:
let parameters = ["address":searchBar.text!,"key":"MYKEY"]
Alamofire.request(.GET, "https://maps.googleapis.com/maps/api/geocode/json", parameters: parameters).responseJSON(options:.AllowFragments) { _, _, JSON in
print(JSON)
}
And all I am getting printed is "SUCCESS". I hope that I am doing something wrong and it can be fixed because I would really like to be able to use closures instead of delegate calls.
My questions are:
Is it possible to use Alamofire with Google Geocoding APIs?
If so, can you please tell me what am I doing wrong?
If it is not possible, can you please suggest me how to design a system with NSURSessions or NSURLConnections which would allow me to use completion handlers for each call instead of delegates?
P.S. I am aware that I can use synchronous requests but I would really like to avoid using that option
Update
It was suggested that adding .MutableContainers as an option should make responseJSON work. I tried the code below:
let apiKey = "MYKEY"
var parameters = ["key":apiKey,"components":"locality:\(searchBar.text!)"]
Alamofire.request(.GET, "https://maps.googleapis.com/maps/api/geocode/json", parameters: parameters).responseJSON(options:.MutableContainers) { one, two, JSON in
print(JSON)
}
And all I get printed is "SUCCESS".
Ok, I have finally figured this out (with the help from #cnoon). The value which is returned is of type Result. I couldn't find documentation for it, but the source code is available here.
In order to retrieve JSON below implementation can be used:
Alamofire.request(.GET, "https://mapss.googleapis.com/maps/api/geocode/json", parameters: parameters).responseJSON(options:.MutableContainers) { _, _, JSON in
switch JSON {
case .Failure(_, let error):
self.error = error
break
case .Success(let value):
print(value)
break
}
}
The value printed is the correct representation of response from Geocoding APIs.
Hi I am really new to coding in Swift, and am trying to follow the codes in this book: http://www.apress.com/9781484202098. Learn iOS 8 App Development 2nd Edition by James Bucanek
In particular, I am working through Chapter 3 - building a URL shortening app, but despite having copied the code exactly, I am getting an error on the code in Page 76:
if let toShorten = webView.request.URL.absoluteString {
which states 'NSURLRequest?' does not have a member named 'URL'.
I have tried googling an answer, but unfortunately have not come across anything. Any response I can find seems to suggest that my code ought to be working (e.g. How to get url which I hit on UIWebView?). This seems to have the closest answer SWIFT: Why I can't get the current URL loaded in UIWebView? but the solution does not appear to work for me. If I add a ? after the request, it will then at least build it, but I then have a nil variable returned.
I am using Xcode v6.1.1. Here is the piece of code that is coming up with the error in ViewController.swift:
let GoDaddyAccountKey = "0123456789abcdef0123456789abcdef" //this is replaced by my actual account key in my own code
var shortenURLConnection: NSURLConnection?
var shortURLData: NSMutableData?
#IBAction func shortenURL( AnyObject ) {
if let toShorten = webView.request?.URL.absoluteString { // ? now added
let encodedURL = toShorten.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
let urlString = "http://api.x.co/Squeeze.svc/text/\(GoDaddyAccountKey)?url=\(encodedURL)"
shortURLData = NSMutableData()
if let firstrequest = NSURL(string: urlString) //added if here and removed !
let request = NSURLRequest(URL:firstrequest)
shortenURLConnection = NSURLConnection(request:request, delegate:self)
shortenButton.enabled = false
}
}
}
If you have any suggestions on how I can fix this, I would really appreciate it!
Update:
Following suggestions from Ashley below, I have amended my code so that it is no longer bringing up the error (see comments above). However, it is now no longer running. This appears to be because the urlString is being created as http://api.x.co/Squeeze.svc/text/d558979bb9b84eddb76d8c8dd9740ce3?url=Optional("http://www.apple.com/"). The problem is therefore the Optional() that is included and thus makes it an invalid URL. Does anyone have a suggestion on how to remove this please?
request is an optional property on UIWebView:
var request: NSURLRequest? { get }
also stringByAddingPercentEscapesUsingEncoding returns an optional:
func stringByAddingPercentEscapesUsingEncoding(_ encoding: UInt) -> String?
What you need is to make user of optional binding in a few places:
if let toShorten = webView.request?.URL.absoluteString {
if let encodedURL = toShorten.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
let urlString = "http://api.x.co/Squeeze.svc/text/\(GoDaddyAccountKey)?url=\(encodedURL)"
shortURLData = NSMutableData()
if let firstrequest = NSURL(string: urlString) { // If a method can return a nil, don't force unwrap it
let request = NSURLRequest(URL:first request)
shortenURLConnection = NSURLConnection(request:request, delegate:self)
shortenButton.enabled = false
}
}
}
See Apple's docs on optional chaining for details
See Apple's docs for NSURL class