Convert Data to String in Swift 3 - string

I am very new to Swift.
I want to create something like API on Swift for my educational app.
I have this code:
static func getFilm(filmID: Int) -> String {
print("getFilm")
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
var request = URLRequest(url: url)
var returnData: String = ""
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if var responseVar = response, var dataVar = data {
print(responseVar)
returnData = String(data: dataVar, encoding: .utf8)
} else {
print(error)
}
}
task.resume()
return returnData
}
And I try to convert Data to String in this line: returnData = String(data: dataVar, encoding: .utf8)
Swift compiler gives me an error, and change this line to
returnData = String(data: dataVar, encoding: .utf8)!
, when I execute this line I get empty returnData variable.
If I use basic example line
print(String(data: data, encoding: .utf8))
everything will be OK and I can see data in XCode console.
So, how I can convert Data to String?

This is an example using a completion handler:
class func getFilm(filmID: Int, completion: #escaping (String) -> ()) {
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
URLSession.shared.dataTask(with:url) { (data, response, error) in
if error != nil {
print(error!)
completion("")
} else {
if let returnData = String(data: data!, encoding: .utf8) {
completion(returnData)
} else {
completion("")
}
}
}.resume()
}
And you call it
MyClass.getFilm(filmID:12345) { result in
print(result)
}
In case of an error the completion handler returns an empty string.
MyClass is the enclosing class of getFilm method. Most likely the web service will return JSON, so you might need to deserialize the JSON to an array or dictionary.
In a more sophisticated version create an enum with two cases and associated values
enum ConnectionResult {
case success(String), failure(Error)
}
With a little more effort demonstrating the subtle power of Swift you can return either the converted string on success of the error on failure in a single object.
class func getFilm(filmID: Int, completion: #escaping (ConnectionResult) -> ()) {
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
URLSession.shared.dataTask(with:url) { (data, response, error) in
if error != nil {
completion(.failure(error!))
} else {
if let returnData = String(data: data!, encoding: .utf8) {
completion(.success(returnData))
} else {
completion(.failure(NSError(domain: "myDomain", code: 9999, userInfo: [NSLocalizedDescriptionKey : "The data is not converible to 'String'"])))
}
}
}.resume()
}
On the caller side a switch statement separates the cases.
MyClass.getFilm(filmID:12345) { result in
switch result {
case .success(let string) : print(string)
case .failure(let error) : print(error)
}
}

I had this problem, you can't use encoding: .utf8 for unpredictable data. It will return nil every time.
Use this instead:
String(decoding: data, as: UTF8.self)

For anyone coming in future (which are probably not interested in OP's film code?!);
Simply, try something like:
extension Data {
public func toString() -> String {
return String(data: self, encoding: .utf8) ?? "";
}
}
See also my toHex related answer

Related

Nesting URLSession.shared.dataTask in Swift 4

I am trying to fetch data from an api where the JSON returned has URLs to other pieces of information that I need, such as
"value1" : "data",
"value2": {
"url": "https://example.com/stuff",
}
My logic is as follows:
func(completion: #escaping ([Data]) -> ()) {
var classArray = [myClass]()
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
guard let resultArray = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { return }
let myObject = myClass(value1: resultArray["value1"]! as! String)
guard let valueUrl = URL(string: resultArray["value2"]! as! String) else { return }
URLSession.shared.dataTask(with: valueUrl) { (data, _, _) in
myObject.value2 = data
classArray.append(myObject)
}.resume()
} catch let error {
print("Failed to create json with error: ", error.localizedDescription)
}
completion(classArray)
}.resume()
}
}
Is this a valid approach or are there better implementations? Trying to avoid a future Pyramid of Doom situation. I have tried putting the inner URLSession call in a separate private function but still receive an empty classArray in the end.

Cast NSString(contentsOfURL:...) to String?

public convenience init(nsurl:NSURL) {
var enc:NSStringEncoding = NSUTF8StringEncoding
var err:NSError?
let str:String? =
NSString(
contentsOfURL:nsurl, usedEncoding:&enc, error:&err
)
if err != nil { self.init(err!) }
else { self.init(string:str!) }
}
Swift is version 1.2, error message is:
NSString? is not convertible to string
With Swift 1.2 automatic bridging between String and NSString has been removed.
So you have to explicitly do the cast :
let str = NSString(contentsOfURL:nsurl, usedEncoding:&enc, error:&err) as? String
Swift's String accepts this same initializer as NSString, so you don't even have to use NSString nor to typecast:
let str = String(contentsOfURL: nsurl, encoding: enc, error: &err)
Update for Swift 2.0
do {
let str = try String(contentsOfURL: nsurl, encoding: enc)
print(str)
} catch {
print(error)
}
You can also use an Optional with try? if you want, in this case no need for do catch:
if let str = try? String(contentsOfURL: nsurl, encoding: enc) {
print(str)
} else {
// didn't succeed
}
In Swift 3 I am using the following
do{
let str = try String(contentsOf:personUrl!)
print(str)
}catch let error{
print(error)
}

Why does localizedDescription of NSError say Optional("description")?

Whenever I do println(error.localizedDescription) I get something that says :
Optional("description of the error here")
Rather than just:
"description of the error here"
How do I get rid of the Optional() part of the description?
I tried the below method which results in a compiler error saying that it's not an optional.
func performLoginRequestWithURL(url: NSURL, email: String, password: String) {
let bodyData = "email=\(email)&password=\(password)"
var request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){
response, data, error in
if let error = error {
let errString = error.localizedDescription
NSNotificationCenter.defaultCenter().postNotificationName(self.lh, object: nil, userInfo: ["Result": errString])
} else if data != nil {
let json = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
if let dictionary = JSON().parseJSON(json) as [String: AnyObject]? {
let accesstoken = dictionary["id"] as! String
let id = dictionary["userId"] as! Int
var results = [String: AnyObject]()
results = ["at": accesstoken, "id": id]
// MARK: - Store UID & AccessToken
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "userLoggedIn")
NSUserDefaults.standardUserDefaults().setInteger(id, forKey: "userId")
NSUserDefaults.standardUserDefaults().setObject(accesstoken, forKey: "accessToken")
NSUserDefaults.standardUserDefaults().synchronize()
NSNotificationCenter.defaultCenter().postNotificationName(self.lh, object: nil, userInfo: ["Result": "Success"])
}
}
}
}
The compiler is correct - error is an optional but error.localizedDescription is not. You either need to unwrap the error first, safely by
if let unwrappedError = error {
println(unwrappedError.localizedDescription)
}
or: Use optional chaining - https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html
let description = error?.localizedDescription
which will either cause 'description' to be the string of localizedDescription, if the error optional can be unwrapped successfully, or possibly 'nil' if not.
Code taken from example added to question. Seems to work as ok, below code can be copied into playground to show error and data string printout - just add valid http:// address into see valid return or try with just 'http:' to get an error.
import UIKit
import XCPlayground
func performLoginRequestWithURL(url: NSURL) {
let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){
response, data, error in
if let error = error {
let errString = error.localizedDescription
print(errString)
} else if data != nil {
let json = NSString(data: data!, encoding: NSUTF8StringEncoding) as! String
print(json)
}
}
}
performLoginRequestWithURL(NSURL(string:"http:")!)
XCPSetExecutionShouldContinueIndefinitely()
You have to unwrap error using ?:
if let errString = error?.localizedDescription {
println(errString)
}
If you already know from an earlier check that error isn't nil, you can force unwrap:
println(error!.localizedDescription)
This will crash if error is nil, so use the ! syntax with caution.

Can't convert NSData read from a local file and output as string in Swift

Well I am trying to creating a simple tool to read an specific offset address from a file that's within the project.
I can read it fine and get the bytes, the problem is that I want to convert the result into a string, but I just can't.
My output is this: <00000100 88000d00 02140dbb 05c3a282> but I want into String.
Found some examples of doing it using an extension for NSData, but still didn't work.
So anyone could help??
Here's my code:
class ViewController: UIViewController {
let filemgr = NSFileManager.defaultManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let pathToFile = NSBundle.mainBundle() .pathForResource("control", ofType: "bin")
let databuffer = filemgr.contentsAtPath(pathToFile!)
let file: NSFileHandle? = NSFileHandle(forReadingAtPath: pathToFile!)
if file == nil {
println("File open failed")
} else {
file?.seekToFileOffset(197584)
let byte = file?.readDataOfLength(16)
println(byte!)
file?.closeFile()
}
}
}
So long as you know the encoding, you can create a string from an NSData object like this
let str = NSString(data: data, encoding: NSUTF8StringEncoding)
By the way, you might want to try using if let to unwrap your optionals rather than force-unwrapping, to account for failure possibilities:
let filemgr = NSFileManager.defaultManager()
if let pathToFile = NSBundle.mainBundle() .pathForResource("control", ofType: "bin"),
databuffer = filemgr.contentsAtPath(pathToFile),
file = NSFileHandle(forReadingAtPath: pathToFile)
{
file.seekToFileOffset(197584)
let bytes = file.readDataOfLength(16)
let str = NSString(data: bytes, encoding: NSUTF8StringEncoding)
println(str)
file.closeFile()
}
else {
println("File open failed")
}
The correct answer following #Martin R suggestion to this link: https://codereview.stackexchange.com/a/86613/35991
Here's the code:
extension NSData {
func hexString() -> String {
// "Array" of all bytes:
let bytes = UnsafeBufferPointer<UInt8>(start: UnsafePointer(self.bytes), count:self.length)
// Array of hex strings, one for each byte:
let hexBytes = map(bytes) { String(format: "%02hhx", $0) }
// Concatenate all hex strings:
return "".join(hexBytes)
}
}
And I used like this:
let token = byte.hexString()

How to convert NSHTTPURLResponse to String in Swift

I would like to convert my response from the NSHTTPURLResponse type to String:
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) -> Void in
println("Response: \(response)")
var responseText: String = String(data: response, encoding: NSUTF8StringEncoding)
})
The line below outputs the response message to the console.
println("Response: \(response)")
But this line renders me an error: Extra argument 'encoding' in Call.
var responseText: String = String(data: response, encoding: NSUTF8StringEncoding)
How can I successfully convert this "response" into a String?
body
grab the data and make it utf string if you want. The response's description is not the response body
let responseData = String(data: data, encoding: NSUTF8StringEncoding)
header field
if you want a HEADER FIELD instead:
let httpResponse = response as NSHTTPURLResponse
let field = httpResponse.allHeaderFields["NAME_OF_FIELD"]
Updated Answer:
As is turns out you want to get a header field's content.
if let httpResponse = response as? NSHTTPURLResponse {
if let sessionID = httpResponse.allHeaderFields["JSESSIONID"] as? String {
// use sessionID
}
}
When you print an object its description method gets called.
This is why when you println() it you get a textual representation.
There are two ways to accomplish what you want.
The easy way
let responseText = response.description
However, this is only good for debugging.
The localized way
let localizedResponse = NSHTTPURLResponse.localizedStringForStatusCode(response.statusCode)
Use the second approach whenever you need to display the error to the user.
For a newer version in swift
let task = session.dataTask(with: url) {(data, response, error) in
let httpResponse = response as! HTTPURLResponse
let type = httpResponse.allHeaderFields["Content-Type"]
print("Content-Type", type)
let l = httpResponse.allHeaderFields["Content-Length"]
print("Content-Length", l)
if let response = response { // Complete response
print(response)
}
}catch {
print(error)
}
}
}.resume()
}
You'll need the code below, because the response data from your data task is stored in data. response is the http response, with status codes etc, for more info about http response go here
var responseString: String = String(data: data, encoding: NSUTF8StringEncoding)
If you want to see the answer json as a string, in Swift 5
let httpResponse = response as? HTTPURLResponse
if let jsonResponse = String(data: data!, encoding: String.Encoding.utf8) {
print("JSON String: \(jsonResponse)")
}
It was as simple as var responseText: String = response.description.

Resources