How can I Init a Class with a Web Service Call in Swift? - ios

I have a drug class which I would like to init by passing in a upc: Int. Inside the init I'd like to make a web service call, and populate the values in the class with the returned JSON (NSDictionary).
I have a start, but not a finish. I'm getting errors everywhere and can't seem to figure out how to best accomplish this. Maybe I'm going about this the whole wrong way. Could someone help?
Here's the code ..
init(upc: Int) {
let apiKey = "xx"
let baseURL = NSURL(string: "http://something.com/\(apiKey)/")
let getDrugByUpcURL = NSURL(string: "\(upc).json", relativeToURL: baseURL)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let usernamePasswordString = "user:pass"
let usernamePasswordData = usernamePasswordString.dataUsingEncoding(NSUTF8StringEncoding)
let base64EncodedCredential = usernamePasswordData!.base64EncodedStringWithOptions(nil)
let authString = "Basic \(base64EncodedCredential)"
config.HTTPAdditionalHeaders = ["Authorization": authString]
let session = NSURLSession(configuration: config)
let downloadTask: NSURLSessionDownloadTask = session.downloadTaskWithURL(getDrugByUpcURL!, completionHandler: { (location: NSURL!, response: NSURLResponse!, error: NSError!) -> Void in
if (error == nil) {
let dataObject = NSData(contentsOfURL: location)
println(dataObject)
let drugDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(dataObject!, options: nil, error: nil) as NSDictionary
println(drugDictionary["din"])
drug_id = drugDictionary["drug_id"] as Int
din = drugDictionary["din"] as String
packsize = drugDictionary["packsize"] as Double
brand = drugDictionary["brand"] as String
//generic = drugDictionary["generic"] as String
strength = drugDictionary["strength"] as String
form = drugDictionary["form"] as String
upc = drugDictionary["upc"] as String
//priceGroup = drugDictionary["price_group"] as String
//manufacturer = drugDictionary["manufacturer"] as String
onHandQuantity = drugDictionary["onhand"] as Double
//vendorCost = drugDictionary["vendor_cost"] as Double
//basePrice = drugDictionary["base_price"] as Double
//discount = drugDictionary["discount"] as Double
//price = drugDictionary["price"] as Double
} else {
println(error)
}
})
downloadTask.resume()
}
An error I'm getting is on all property assignment lines: Cannot assign to 'drug_id' in 'self'.

The problem is that you are accessing those instance variables from a closure, the completion handler of downloadTaskWithURL:.
This is easily fixed by prefixing the variables with self.. So drug_id becomes self.drug_id.
Note however that what you are doing in the above code is probably a bad idea. Or at least a very uncommon design: I don't think it is a good idea to do asynchronous network requests in your class' initializer.
Since the call to NSURLSessionDownloadTask is asynchronous, your init() will return immediately with uninitialized data and then at some unspecified moment in time your class will be fully populated with the result from the web service call. You don't know what that moment in time is though, so you really have no good way of knowing when your instance is ready.
This is most likely not what you had in mind.

Related

Issue assigning variable value to open Apple Maps when pressing a UILabel

Essentially I am parsing JSON data and assigning it to a variable called addressPressNow I then have the following function that executes when a user taps on a UILabel:
The goal is to have Apple Maps open provided the variable value it contains.
Because I am assigning an address to a variable it will contain spaces
ex: 3981 Test Drive Cupertino CA 95014
NOTE: The value of the variable is being passed correctly because when I do print(addressPressNow) in func tapFunction it prints correctly.
#objc
func tapFunction(sender:UITapGestureRecognizer) {
let targetURL = NSURL(string: "http://maps.apple.com/?q=" + addressPressNow)!
UIApplication.shared.openURL(targetURL as URL)
}
The issue is I am having trouble applying the variable to the string URL with the following error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
The following is how I am assigning the value to the variable:
struct FacilityInfo: Decodable {
let address: String
class infoViewController: UIViewController {
var addressPressNow : String = ""
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(infoViewController.tapFunction))
addressInfo.isUserInteractionEnabled = true
addressInfo.addGestureRecognizer(tap)
let url = URL(string: "https://test/test/example”)!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// ensure there is no error for this HTTP response
guard error == nil else {
print ("error: \(error!)")
return
}
// ensure there is data returned from this HTTP response
guard let data = data else {
print("No data")
return
}
// Parse JSON into array of Car struct using JSONDecoder
guard let cars = try? JSONDecoder().decode([FacilityInfo].self, from: data), let secondCar = cars.first
else {
print("Error: Couldn't decode data into cars array")
return
}
DispatchQueue.main.async {
self.addressPressNow = secondCar.facility_address
}
}
"I am assigning an address to a variable it will contain spaces"
If the address contains spaces then creating NSURL with the string will crash. You can use addingPercentEncoding to solve the problem
if let encodedAddress = addressPressNow.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
let targetURL = NSURL(string: "http://maps.apple.com/?q=" + encodedAddress)!
UIApplication.shared.openURL(targetURL as URL)
}
And don't use NSURL and force unwrapping. Update it like this
if let encodedAddress = addressPressNow.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let targetURL = URL(string: "http://maps.apple.com/?q=" + encodedAddress) {
UIApplication.shared.openURL(targetURL)
}
As suggested by matt use URLComponents
let addressPressNow = "3981 Test Drive Cupertino CA 95014"
var components = URLComponents(string: "http://maps.apple.com")
components?.queryItems = [URLQueryItem(name: "q", value: addressPressNow)]
print(components?.url)//http://maps.apple.com?q=3981%20Test%20Drive%20Cupertino%20CA%2095014
if let targetURL = components?.url {
UIApplication.shared.open(targetURL, options: [:], completionHandler: nil)
}
You are saying
NSURL(string: "http://maps.apple.com/?q=" + addressPressNow)!
Notice the exclamation mark at the end. That means "if there's a problem, crash me". You can hardly complain if you do in fact crash; that is what you asked to do.
Basically, never use NSURL(string:) if you can avoid it. To form a valid URL, build it up using URLComponents. And form it out of valid components. (It is impossible to say whether facility_address is a valid URL query, because you have not shown what it is.)
Example:
var comp = URLComponents()
comp.scheme = "https"
comp.host = "maps.apple.com"
comp.queryItems = [URLQueryItem(name: "q", value: "1 Infinite Loop, Cupertino, CA")]
if let url = comp.url {
print(url) // https://maps.apple.com?q=1%20Infinite%20Loop,%20Cupertino,%20CA
}
That gives us a valid URL that actually works.

JSON array inside of an array

I am looking to access a string that is located inside of a JSON array that is located inside of another array. I am accessing the JSON API using JSONDecoder. I am receiving errors when trying the various methods that I have used in the past when using JSON arrays.
Here is the code:
var country = [Results]()
struct Rating: Codable {
let results: [Results]
}
struct Results: Codable {
let iso_3166_1: String
let release_dates: [Release_Dates]
}
struct Release_Dates: Codable {
let certification: String
}
func loadRating() {
let id = filmId
let apiKey = ""
let url = URL(string: "https://api.themoviedb.org/3/movie/\(id)/release_dates?api_key=\(apiKey)")
let request = URLRequest(
url: url! as URL,
cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData,
timeoutInterval: 10 )
let session = URLSession (
configuration: URLSessionConfiguration.default,
delegate: nil,
delegateQueue: OperationQueue.main
)
let task = session.dataTask(with: request, completionHandler: { (dataOrNil, response, error) in
if let data = dataOrNil {
do { let rates = try! JSONDecoder().decode(Rating.self, from: data)
self.country = rates.results
let us = self.country.filter({ $0.iso_3166_1.contains("US") })
print(us)
}
}
})
task.resume()
}
us prints to console
[Film.DetailsView.Results(iso_3166_1: "US", release_dates: [Film.DetailsView.Release_Dates(certification: "PG-13")])]
I am trying to access the certification string.
What would be the correct method used to achieve this?
us is an array of Results.
To get the first certification use this:
print(us.first!.release_dates.first!. certification)
I am force unwrapping for brevity, you should properly do it with optional binding or the guard statement.
Pretty straightforward, the result of filter is an array and certification is in the array release_dates
let us = self.country.filter({ $0.iso_3166_1.contains("US") })
for item in us {
for releaseDate in item.release_dates {
print(releaseDate.certification)
}
}
Please name your struct member names lowerCamelCased by mapping the keys with CodingKeys or with the convertFromSnakeCase strategy.
If there is only one US item, use first
if let us = self.country.first({ $0.iso_3166_1.contains("US") }) {
for releaseDate in us.release_dates {
print(releaseDate.certification)
}
}

Swift NSURL nil when running the application

When I run the application Xcode told me that
unexpectedly found nil while unwrapping an Optional value
at the url but the url isn't nil, can someone help?
here is the code
import Foundation
protocol WeatherUndergroundServiceByGeographicalDelegate{
func setWeatherByGeographical(weather:WeatherUnderground)
}
class WeatherUndergoundServiceByGeographical{
var delegate:WeatherUndergroundServiceByGeographicalDelegate?
func getWeatherFromWeatherUnderground(latitude:Double, longitude:Double){
let path = "http://api.wunderground.com/api/48675fd2f5485cff/conditions/geolookup/q/\(latitude,longitude).json"
let url = NSURL(string: path)
//session
let session = NSURLSession.sharedSession()
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Error is at here~~~~~~~~~~~~~~~~~~~~~~~~~
let task = session.dataTaskWithURL(url!) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let json = JSON(data: data!)
//parsing json weather condition from weather api. using swiftyJson
let name = json["current_observation"]["display_location"]["city"].string
let temp = json["current_observation"]["temp_c"].double
let windsp = json["current_observation"]["wind_mph"].double
//prasing the weather data
let weather = WeatherUnderground(cityName: name!, temperature: temp!, windSpeed: windsp!)
if self.delegate != nil{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate?.setWeatherByGeographical(weather)
})
}
}
task.resume()
}
}
You probably have error in your path string, try this:
let path = "http://api.wunderground.com/api/48675fd2f5485cff/conditions/geolookup/q/\(latitude),\(longitude).json"
The reason is that you are interpolating tuple value \(latitude,longitude) in the string, which adds extra space and makes url string invalid because space is not percent-escaped.
Instead you have to interpolate each value with a comma between them: \(latitude),\(longitude)
let path = "http://api.wunderground.com/api/48675fd2f5485cff/conditions/geolookup/q/\(latitude,longitude).json"
I think you mean:
let path = "http://api.wunderground.com/api/48675fd2f5485cff/conditions/geolookup/q/\(latitude),\(longitude).json"

Difficulty Returning A Dictionary From NSURL Session

I'm hoping someone an help me figure out a problem that has me scratching my brain! When I attempt this function using a NSData(contentsOfUrl... structure, this all works fine. However, I am attempting to use a NSURLSession for use on an Apple Watch app, and keep hitting an error;
...
class func fetchData() -> [Complication] {
var task: NSURLSessionDataTask?
let myURL = "http://www.myurl.com/sample.json"
let dataURL = NSURL(string: myURL)
let conf = NSURLSessionConfiguration.defaultSessionConfiguration()
conf.requestCachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let session = NSURLSession(configuration: conf)
task = session.dataTaskWithURL(dataURL!) { (data, res, error) -> Void in
if let e = error {
print("dataTaskWithURL fail: \(e.debugDescription)")
return
}
var dataSet = [Complication]()
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSArray
for item in json {
let name: String? = item["name"] as? String
let percent: Int? = item["percent"] as? Int
let timeFromNow: Int? = item["timeFromNow"] as? Int
let myData = Complication(
name: name!,
percent: percent!,
timeFromNow: timeFromNow!
)
dataSet.append(myData)
}
} catch {
print(error)
}
}
return dataSet
//THIS LINE THROWS THE ERROR
}
...
When attempting to return my dataSet array, I receive the error Instance member 'dataSet' cannot be used on type 'Complication'. As mentioned, however, this does seem to work if I were to use a NSData(contentsOfUrl... instead of a NSURLSession, which is where I am stuck!
The data task is a closure that is executed asynchronously. Its return statements returns from the closure, not from the outer function.
Since the closure is executed asynchronously it makes no sense to return data from it: the return type is Void.
You should organize your code differently, e.g. using a completion handler.
Hint: search for "swift return closure" in SO. You will find plenty of questions similar to yours and a number of good answers and suggestions.

Swift 1.2 Xcode 6.3 NSString? to String gives Thread 1: EXC_BAD_ACCESS (code=1, address=0x20)

Now apple have handily gotten rid of the NSString and String automatic compatibility, I'm having a bit of a nightmare going between the two. I'm getting a couple of NSStrings out of a dictionary and I can't convert them to regular Strings...
I've tried:
let fileNameString: String = String(format: "%#", filename!)
let fileNameString: String = (filename as! String)
let fileNameString = filename as? String
let fileNameString = (filename as? String) ?? ""
if let fileNameString = filename as? String {
println("\(fileNameString)")
}
But all produce the error.
I've broken in at the point of conversion and can see neither NSStrings are nil:
But no joy with either. Getting Thread 1: EXC_BAD_ACCESS (code=1, address=0x20) . Am I missing something obvious here?
Even just trying to print the NSString filename before conversion causes the same error..
Posting the code prior to conversion attempt to see if that has anything to do with it...
// First we create a head request as the info I need is in the headers
var newRequest: NSMutableURLRequest = NSMutableURLRequest(URL: request.URL!)
newRequest.HTTPMethod = "HEAD"
var response: NSURLResponse?
NSURLConnection.sendSynchronousRequest(newRequest, returningResponse: &response, error: nil)
// Unwrap response as httpResponse in order to access allHeaderFields
if let httpResponse = response as? NSHTTPURLResponse {
let headerString = "sfn-Document-Filename"
let headerNSString = headerString as NSString
let filetypeString = "Content-Type"
let filetypeNSString = filetypeString as NSString
// This is a dictionary where the keys are NSCFStrings
// (NSStrings, hence creating the NSStrings above)
var allHeaders = httpResponse.allHeaderFields
// Getting the filename out here only works with as? NSString. as? String creates the same error as converting.
let filename = allHeaders[headerNSString] as? NSString
// This is a string which contains the type as 'application/pdf' for example. We only need the part after the /.
// Again, trying to get this out as a String fails
let typeString = allHeaders[filetypeNSString] as? NSString
var typeArray = typeString?.componentsSeparatedByString("/") as! [NSString]
let filetype = typeArray[1]
}
If this were an NSString, then all you’d need to do is filename as String (no !). But it sounds like the problem is your filename, of optional type NSString?, is nil. (option-click filename to confirm its type)
If there’s a reasonable default (say, an empty string), try
let fileNameString = (filename as? String) ?? ""
Or if you need to handle the nil with specific code:
if let fileNameString = filename as? String {
// use fileNameString, which will be unwrapped and of type String
}
else {
// log error or similar
}
Or, if you want to defer unwrapping, but want to change the type inside the possible value, you can do
let fileNameString = filename as? String
// which is a less long-winded way of saying
let fileNameString = filename.map { $0 as String }
Generally speaking, you should try and cut down on the ! usage, since it leads to problems like this. ! is only for those times when you know from the code that the value absolutely positively cannot be nil.
edit: based on your sample code, try the following:
let url = NSURL(string: "http://www.google.com")
let request = url.map { NSMutableURLRequest(URL: $0) }
request?.HTTPMethod = "HEAD"
let response: NSHTTPURLResponse? = request.flatMap {
var response: NSURLResponse?
NSURLConnection.sendSynchronousRequest($0, returningResponse: &response, error: nil)
return response as? NSHTTPURLResponse
}
let headers = response?.allHeaderFields as? [String:String]
// google.com has no such header but still...
let filename = headers?["sfn-Document-Filename"]
// bear in mind someArray[1] will also crash if there's no such entry,
// first/last are better if that's what you want
let type = headers?["Content-Type"]?
.componentsSeparatedByString(";").first?
.componentsSeparatedByString("/").last
Everything here is optional, but safe, so you can test at various points for nil for logging/error reporting purposes.

Resources