Swift 3 NSDictionary to Dictionary conversion causes NSInvalidArgumentException - objective-c

I have just converted my project from Swift 2.2 to 3.0, and I'm getting a new exception thrown in my tests. I have some Objective C code in one of my tests which reads in some JSON from a file:
+ (NSDictionary *)getJSONDictionaryFromFile:(NSString *)filename {
/* some code which checks the parameter and gets a string of JSON from a file.
* I've checked in the debugger, and jsonString is properly populated. */
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
return jsonDict;
}
I'm calling this from some Swift code:
let expectedResponseJSON = BZTestCase.getJSONDictionary(fromFile: responseFileName)
This works just fine most of the time, but I have one JSON file which causes the error:
failed: caught "NSInvalidArgumentException", "-[__NSSingleObjectArrayI enumerateKeysAndObjectsUsingBlock:]: unrecognized selector sent to instance 0x608000201fa0"
The strange thing about this is that the error is generated after the getJSONDictionaryFromFile method returns and before expectedResponseJSON in the Swift code is populated. To me, this seems to say that it's the conversion from an NSDictionary to Dictionary which is the problem. The offending JSON file is this one:
[
{
"status": "403",
"title": "Authentication Failed",
"userData": {},
"ipRangeError": {
"libraryName": "Name goes here",
"libraryId": 657,
"requestIp": "127.0.0.1"
}
}
]
If I remove the outermost enclosing [], this error goes away. I can't be the only person using an array as the top level entity of a JSON file in Swift 3, am I doing something wrong? What can I do to get around this error?

As is referenced in the comments, the problem is that getJSONDictionaryFromFile returns an NSDictionary * and my JSON input is an array. The only mystery is why this used to work in Swift 2.2! I ended up changing expectedResponseJSON to be an Any?, and rewrote my Objective C code in Swift:
class func getStringFrom(file fileName: String, fileExtension: String) -> String {
let filepath = Bundle(for: BZTestCase.self).path(forResource: fileName, ofType: fileExtension)
return try! NSString(contentsOfFile: filepath!, usedEncoding: nil) as String
}
class func getJSONFrom(file fileName: String) -> Any? {
let json = try! JSONSerialization.jsonObject(with: (getStringFrom(file: fileName, fileExtension: ".json").data(using: .utf8))!, options:.allowFragments)
return json
}
As a note to anyone who might cut and paste this code, I used try! and filepath! instead of try? and if let... because this code is used exclusively in tests, so I want it to crash as soon as possible if my inputs are not what I expect them to be.

Related

call can throw error message Swift 2

I just converted my app to Swift 2, and of course, am getting one error message:
"Call can throw, but it is not marked with 'try' and the eror is not handled"
I did search here for how to fix this, but the answers are even more confusing to me than the error itself. LOL.
My application worked perfectly until I converted it into Swift 2. Wonderful...
var myPlayer = AVAudioPlayer()
var yourSound1 = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("RemSound_01", ofType: "wav")!)
func initYourSound() {
myPlayer = AVAudioPlayer(contentsOfURL: yourSound1, fileTypeHint: nil)
myPlayer.prepareToPlay()
myPlayer.volume = 1.0 // < for setting initial volume, still not perfected.
}
The error throws in the following line:
myPlayer = AVAudioPlayer(contentsOfURL: yourSound1, fileTypeHint: nil)
How am I supposed to fix this? I understand that Swift is trying to "universalize" error handling, but doing so by breaking even the simplest of code seems silly to me.
Thank you for your help.
Here is the pattern you are looking for.
<throwing function> // compiler error in Swift 2
do { try <throwing function> } catch { }
In catch you usually get an error that you can handle further.
This works in Swift 2.0, put it in your viewDidLoad method ...
do {
try AudioPlayer = AVAudioPlayer(contentsOfURL: ButtonAudioURL, fileTypeHint: nil)
} catch {
print("errorin do-try-catch")
}
where ButtonAudioURL is
var ButtonAudioURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Besides", ofType: "mp3")!)

Swift 2(.1) migration error

I updated my Swift 1.2 Project to Swift 2.1 (installing Xcode 7.1). Now I get 2 errors.
1st error:
Downcast from 'NSURL?' to 'NSURL' only unwraps optionals; did you mean to use '!'?
in this line of code:
let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last as! NSURL
2nd error:
Cannot convert value of type 'Set<NSObject>' to expected argument type 'Set<UIUserNotificationCategory>?'
In this line of code:
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: NSSet(array: [todoCategory]) as! Set<NSObject> as Set<NSObject>))
Do you know how to fix it?
The first error is fixed when you remove the casting. However who probably want to check if documentDirectory is not nil so you would put it in a if let:
if let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last {
// do something with the documents directory
}
The second error can be fixed like this:
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: Set<UIUserNotificationCategory>.init(arrayLiteral: todoCategory)))

error handling in Swift 2

i have to fix the follow code for swift2.
if !UIImagePNGRepresentation(img).writeToFile(imagePath, options: nil, error: &error) {
if let actualError = error {
NSLog("Image not saved. \(actualError)")
}
}
To compile it i have this error on if row: Cannot invoke writeToFile with an argument list of type (String, options: _, error: inout NSError?)
How I can fix it.
Try it with
UIImagePNGRepresentation(img)?.writeToFile(imagePath, atomically: true)
instead. Check the Apple Docs.
Edit:
To answer your question more precisely use the error handling in Swift 2.
do {
try UIImagePNGRepresentation(img)?.writeToFile(imagePath, options: .DataWritingAtomic)
} catch let error as NSError {
print("Image not saved. \(error.description)")
}

iap - conversion from objective c to swift

I'm trying to convert my app from objective c to swift, and i have everything except for the in app purchase working.
i have the objective c helper class imported in the project, but i'm having trouble doing the RequestProductsCompletionHandler section
in the old objective c version i have the code
[[MTIAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) {
if (success) {
_products = products;
if([[self appData] isPro] == FALSE)
[[self bUpgrade] setUserInteractionEnabled:TRUE];
}
}];
and i'm trying to convert this section to objective c but so far i've been unable to convert the requestProductsWithCompletionHandler part
can anyone help?
i've tried creating the completion handler the same was as in objective c using
requestProductsWithCompletionHandler(sucess: Bool, products : NSArray)
but i get the compiler errors "Extra argument 'products' in call" and "Expected member name or constructor call after type name".
if i try it without the parameters i get "Missing argument for parameter #1 in call"
In Swift, your completion handler would be a closure. The syntax is semi-similar to blocks in Objective-C but, instead of defining the parameters outside the block:
^(BOOL success, NSArray *products) { /* ... */ }
you define them inside the closure:
{ (success: Bool, products: [AnyObject]!) in /* ... */ }
So, your call to requestProductsWithCompletionHandler should look something like this:
MTIAPHelper.sharedInstance().requestProductsWithCompletionHandler {
(success: Bool, products: [AnyObject]!) in
if success {
// etc.
}
}
You can also let Swift infer the parameter types for you:
MTIAPHelper.sharedInstance().requestProductsWithCompletionHandler {
(success, products) in
if success {
// etc.
}
}

How to handle a NSDictionary as a return type in iOS swift?

I'm trying to convert and existing, working objective c application over to swift and I'm getting a little tripped up with "closures". Here's the old working objective c block that makes returns a value from a web service:
- (IBAction)didTapSayHiButton {
[self.meteor callMethodName:#"sayHelloTo" parameters:#[self.username.text] responseCallback:^(NSDictionary *response, NSError *error) {
NSString *message = response[#"result"];
[[[UIAlertView alloc] initWithTitle:#"Meteor Todos" message:message delegate:nil cancelButtonTitle:#"Great" otherButtonTitles:nil] show];
}];
}
So here I'm getting either getting back a dictionary or response. And it works.
And here's how I'm trying to go about this with swift (the method is slightly different):
#IBAction func sayHi(sender : AnyObject) {
var params = [
"name": "Scotty"
]
meteor.callMethodName("sayHi", parameters: params, responseCallback: {
(response: Dictionary<String, String>, error: NSError) in
println("the name recieved back is: \(response)")
})
}
The error I'm getting in xCode: "NSDictionary is not a subtype of 'Dictionary'"
After looking through the swift book this is the best educated attempt that I can make. I've tried a few other things but each resulted in another type of error.
How do I make this work with swift?
Edit: I've also tried just using Dictionary and Dictionary<String, String>
I should also note that I'm using a bridging header to access objective c code (objectiveDDP). And that callMethodNamed is written in objective c as can be seen here: https://github.com/boundsj/ObjectiveDDP/blob/master/ObjectiveDDP/MeteorClient.h#L47
Update: by changing the method to:
meteor.callMethodName("sayHi", parameters:["scotty"] , responseCallback:nil)
we were able to get it to work. But the second we try to add in the closure, it starts throwing the same original errors.
Try changing from using a Swift dictionary to explicitly using NSDictionary:
#IBAction func sayHi(sender : AnyObject) {
var params: NSDictionary = [
"name": "Scotty"
]
meteor.callMethodName("sayHi", parameters: params, responseCallback: {
(response: NSDictionary!, error: NSError) in
println("the name recieved back is: \(response)")
})
}
The trick, in this particular case, is to completely omit the closure's parameter types, and let the compiler figure it out. After searching around for a while, I found this post which led me to the solution.
meteor.callMethodName("sayHi", parameters:["scotty"] , responseCallback: {
response, error in
let me:String = response["result"] as String!
println("called back \(me)")
})
If your not worried about accessing the closures parameters, apparently you can also using an underscore to ignore them completely:
meteor.callMethodName("sayHi", parameters:["scotty"] , responseCallback: {
_ in
// Do something here
})