I wanted to know if it's possible to load the text in a label from a URL.
I was going to try and use NSURL to pull in a .txt file stored online.
Would anyone know how to implement this with swift?
Thanks
If you don't want to use sessions, you can also use the simpler NSURLConnection Class, something like this:
let url = NSURL(string: "https://wordpress.org/plugins/about/readme.txt")
let request = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
println(NSString(data: data, encoding: NSUTF8StringEncoding))
dispatch_async(dispatch_get_main_queue()) {
// Do stuff on the UI thread
self.textField.text = NSString(data: data, encoding: NSUTF8StringEncoding)! as String
return
}
}
Yes it's possible. Yes, I know how to do it. However, you haven't exerted any effort whatsoever to solve your problem yourself.
Search on NSURLConnection. Unless you need https or login, you could use the class method sendAsynchronousRequest:queue:completionHandler:, which is very easy to use. You should be able to find examples of using it on the net.
You could also use a library like Alamofire. That makes it even easier.
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 want to know how you guys handle errors when using a URLRequest in your app. How do you go about notifying your users that an error has occurred? Do you even notify your users at all? Do you try and reload the URLRequest again? Do you tell your users to close the current screen and open it again with an alert box? I have no clue.
Once there's an error, your app stops. So what do you do when this happens and you have a network issue, bad Json data?
What do you do when you get a "Bad Network Connection (The server is down)" or the URLSession comes back with an error and the internet connection is fine?
Please look at the code below and help me figure out what needs to be done when an error occurs.
let url = URL(string:"http://example/jsonFile.php")
var request = URLRequest(url:url!)
request.httpMethod = "POST"
let postingString = "id=\(id)"
request.httpBody = postingString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest){(data, response, error) -> Void in
if error != nil {
print("error \(error)")
// *****
// What do you do here? Do you tell your users anything?
// *****
return
}
// Check for Error
if let urlContent = data {
do{
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: .allowFragments) as! [String: AnyObject]
print("jsonResult \(jsonResult)")
}
catch{
print("JSON serialization failed")
// *****
// What do you do here? Do you tell your users anything?
// *****
}
}
}
task.resume()
It is often a bad idea to hide the errors and do nothing (see Error Hiding Anti-Pattern). Unfortunately, handling errors is not easy and can be tedious some times. Pretty code examples quickly become "uglier" when exceptions and errors have to be handled. It takes time to learn to do it well.
In the case of network connections, you would want to wrap your requests into asynchronous methods that take a completion callback as parameter. This callback will often receive a successful result or an error, sometimes wrapped in an enum to remove ambiguity (see Result as an example).
Once you know a network request has failed, you can present that information to the user in a clean, informational but non-obstructive way. To find out what other applications do in this scenario, turn on Airplane Mode in your phone and poke around.
Here are some examples:
Apple Music
Facebook Messenger
I'm trying to parse csv file from my ios project(swift 2.3) and found this website. In the tutorial code, it has the following section of code :
if let content = String(contentsOfURL: contentsOfURL,
encoding: encoding, error: error) {
...........
}
And I'm not sure what it does. Does it create a String object?
Does it create a String object?
Yes, it creates a string from the contents of the URL given by contentsOfURL and using the character encoding given by encoding. It's analogous to the following Objective-C code:
NSString *content = [NSString stringWithContentsOfURL:contentsOfURL
encoding:encoding
error:&error];
The if let part is a form of conditional statement. let is used to assign a value to an immutable variable. Using it in a conditional as in your example only allows the body of the conditional statement to execute if that assignment succeeds. So, if some error occurs while the data at the given URL is being fetched or if the string cannot be created for some reason, the condition fails and the body isn't executed. The whole snippet might be written like this in Objective-C:
NSString *content = [NSString stringWithContentsOfURL:contentsOfURL
encoding:encoding
error:&error];
if (content != nil) {
// do something with content
}
That code creates a string, but it does it by fetching the contents of a URL. Usually that URL points to a resource on the Internet. In that case it's a very bad way to fetch a string, since it is a synchronous network call that can hang or fail. It's a very bad idea to do synchronous networking calls on the main thread.
You could wrap that code in a GCD call to a background queue, but instead I'd suggest using NSURLSession and submitting a data task. Your search terms would be NSURLSession (or just URLSession in Swift 3) and the function func dataTask(with url: URL). (It might be easier search on it's Objective-C name, dataTaskWithURL since Google searches don't work very well with special characters.)
Take a look at a GitHub project I created called Async_demo. It has a singleton class called DownloadManager that downloads a blob of data from a specified URL. It's written to return the data as a Data object, but it would be a simple matter to convert that result from Data to a String.
The key bit of code is this:
typealias DataClosure = (Data?, Error?) -> Void
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
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.
Technologies: iOS8, SWIFT, XCode 6
Using swift, what is the best way to save an external website's html/css/js, modify that saved data with my own css / js, and then load it in the view. This way, the external page loads with my custom styles/js already implemented.
I'm not sure how complicated your use of the UIWebView is but, the quickest implementation I can think of (aside from the evaluateJS route you've already done):
Create a property to decide if a request has been hijacked yet (by you).
var hijacked = false
provide the UIWebViewDelegate protocol method.
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
//if true it must be our version, reset hijack and allow to load
if hijacked {
hijacked = false
return true
}
//original request. Don't let it load, instead trigger manual loader.
else {
hijacked = true
manuallyLoadPage(request)
return false
}
}
Then you just need a method to fetch the page, get the text, manipulate the text, and then load the page with your version.
func manuallyLoadPage(request: NSURLRequest) {
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
var html = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
html = html.stringByReplacingOccurrencesOfString("</head>", withString: "<script>alert(\"I added this\")</script></head>", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
self.webView.loadHTMLString(html, baseURL: response.URL!)
}
task.resume()
}
This is just a quick and dirty approach, you may want to do a more thorough job of tracking which requests are hijacked requests etc... This one just assumes an even odd kind of approach. You could obviously manipulate the html however you want, I just added the JS alert as a proof of concept.