Access nested NSDictionary 10.8 SDK v 10.7 SDK - nsdictionary

I've written a method to access a nested dictionary stored in a plist. Works fine on the 10.8 compiler but I get the following error on the 10.7 compiler:
Expected method to read dictionary element not found on object type 'NSDictionary*'
The plist has the following structure:
<dict>
<key>15.144.15</key>
<dict>
<key>Message</key>
<string>15</string>
<key>X</key>
<real>484.8828125</real>
<key>Y</key>
<real>104</real>
</dict>
<key>15.144.17</key>
<dict>
<key>Message</key>
<string>17</string>
<key>X</key>
<real>612.91796875</real>
<key>Y</key>
<real>190.6484375</real>
</dict>
</dict>
Here's the code I'm using to access the nested values for Message, X and Y:
NSString * value = [dictionaryFromPlist][parentKey][nestedKey];
I'm assuming that the 10.8 SDK handles accessing nested dictionaries in this way but 10.7 doesn't.
Anyone know a method that will be compatible with both SDKs?
Thanks

Compile with the 10.8 SDK and set your deployment target to 10.7. This is backward-deployable to 10.6. Your syntax is wrong, tho. Don't surround the variable name in brackets, just the subscript keys:
NSValue* value = dictionary[#"parentKey"][#"nestedKey"];

Related

Using stringsdict with interpolation

I am attempting to use the stringsdict file with new Swift 5.1 advanced interpolation. After much jumping through hoops, I was actually able to get a very simple example to work:
<key>format.%lld</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%##num_formats#</string>
<key>num_formats</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>lld</string>
<key>one</key>
<string>Format</string>
<key>other</key>
<string>Formats</string>
</dict>
</dict>
Now I can reference it in Text for example like so:
Text("format.\(count)", comment: "formats string")
and I'll properly get "Format" for 1 and "Formats" for everything else. But now I have a more advanced use case. I want a num_formats variable and then a format variable. For 1, it should just print format (no num_formats interpolation). For many, I want "\(numFormats) x \(format)"
I tried setting up these 2 variables in the stringsdict like so:
<key>quantity.%lld x %#</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%##num_quantity# %##format#</string>
<key>format</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>#</string>
<key>other</key>
<string>%#</string>
</dict>
<key>num_quantity</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>lld</string>
<key>one</key>
<string></string>
<key>other</key>
<string>%lld x</string>
</dict>
</dict>
I expected to be able to use this by calling:
Text("quantity.\(formats.count) x \("Vinyl")", comment: "format quantity string")
Instead, I get (null) when formats.count is 1, and 2 x (null) when it's not 1. What's the proper way to do this?
EDIT: I was able to get some of it fixed by removing x from the NSStringLocalizedFormatKey value. It was <string>%##num_quantity# x %##format#</string> previously, and I changed it to <string>%##num_quantity# %##format#</string>.
It turns out I can just use normal format specifiers in the key itself and not run them through the pluralization portion:
<key>quantity.%lld x %#</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%##num_quantity# %#</string>
<key>num_quantity</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>lld</string>
<key>one</key>
<string></string>
<key>other</key>
<string>%lld x</string>
</dict>
</dict>
Leaving this up in case anyone else wants to struggle with SwiftUI + string interpolation.

Cocoa NSTask ouput extraction

I want to run this command system_profiler -xml SPUSBDataType in my Cocoa application. After doing it with the help of NSTask i get the following output by encoding fileHandle output to a NSString. But i can't do the extraction of specific key's value. I tried by converting it into xml using SWXMLHash and converting to JSON using jsonSerializer. But it become more complex.
Is there any way to do this?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>_SPCommandLineArguments</key>
<array>
<string>/usr/sbin/system_profiler</string>
<string>-nospawn</string>
<string>-xml</string>
<string>SPCameraDataType</string>
<string>-detailLevel</string>
<string>full</string>
</array>
<key>_SPCompletionInterval</key>
<real>0.13360595703125</real>
<key>_SPResponseTime</key>
<real>0.22787702083587646</real>
<key>_dataType</key>
<string>SPCameraDataType</string>
<key>_detailLevel</key>
<integer>-1</integer>
<key>_items</key>
<array>
<dict>
<key>_name</key>
<string>FaceTime HD Camera</string>
<key>spcamera_model-id</key>
<string>Apple Camera VendorID_0x106B ProductID_0x1570</string>
<key>spcamera_unique-id</key>
<string>CC89657KQR6GDV4AQ</string>
</dict>
</array>
</dict>
</array>
</plist>
You should be able to parse this output directly using PropertyListSerialization
. Check out the documentation.
I'd recommend maybe taking a look at using IOKit instead. It can be a bit of a daunting API to get a handle on, but it provides a ton of power. I've used it for USB device information and connect/disconnect notifications in the past and it's worked wonderfully. I think you'll find it much more robust than parsing the output of a command line utility.
You can use something like below to get the Array with the containing Dictionary.
let url = Bundle.main.url(forResource: "data", withExtension: "plist")!
let plistData = try! Data(contentsOf: url)
if let array = try! PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [[String:Any]] {
print(array)
}

array can't read data from plist

I have found couple of similar topic about this but none of them solve my problem.
basically I want to read nsstring from my array in plist and then I want to overwrite the nsstring value.
NSArray *directoriespaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentsDir = [directoriespaths objectAtIndex:0];
NSString *fullnamefordir = [documentsDir stringByAppendingPathComponent:#"data.plist"];
NSArray *turns = [NSArray arrayWithContentsOfFile:fullnamefordir];
NSString *resultofarray = [turns objectAtIndex:0];
NSLog(#"%#",turns);
NSLog(#"%#",resultofarray);
nslog always shows null and i'm not trying to save anything so far because i can't even read the value :/
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<string>8</string>
</array>
</plist>
i also tried with nsdictionary but it also doesn't work
thanks in advance :)
First Check whether your plist file exist in Document Directory or not.
NSFileMangaer *objFileManager=[[NSFileManager alloc]init];
BOOL isfileExist=[objFileManager fileExistsAtPath:yourDocumentDirectoryPath];
IF Bool Value is true then check you content of plist.
May be the file doesnt exist in document directory ,it is in resource.
If you didnot copied you plist from resource folder to document directory then first copy it there and then try to access it.

How to display today's event in 'Today' section?

I have a plist with an array of dictionaries describing events, like this:
<array>
<dict>
<key>date</key>
<date>2011-11-19T00:00:00Z</date>
<key>title</key>
<string>Nederland - Zweden</string>
</dict>
<dict>
<key>date</key>
<date>2011-11-20T00:00:00Z</date>
<key>title</key>
<string>Polen - Engeland</string>
</dict>
</array>
Now I have a section in tableview called 'Today' and I want the events that happen today (and only those), to be displayed in that section. I have made strings of today and of the date of the events. Now how can I display only those events that happen today?
Should I start using something like:
if ( [todayString isEqualToString:eventDateString ] ) {
}
Or start in here (Today-section is 0):
if (section == 0) {
}
First off, it is generally bad practice to do string comparisons when it can be avoided. You are best off using the NSDate method – timeIntervalSinceNow to get the time between the current time and 12:00am today. If the the event's date is within that range, it is today. Just off the top of my head...but a better answer can be found here.
Also you may want to reconsider testing against the location strings if you are ever filtering by location and if have your app use localization in which for example, we call Spain, Spain. But a Spanish person calls it Espania, etc.

How do I store a string as an array in a Cocoa property list?

I am trying to save two strings. One string needs to be saved as type ARRAY in the pList and the second string needs to be saved as String in the Array.
I can use the code:
[dictionary setObject:(id)anObject forKey:(id)aKey>]
but it doesn't save it correctly. I can cast one of the strings as an array, but it still doesn't work right.
What is the proper method for saving an array to the pList?
Here is what my pList looks like:
<dict>
<key>Test One</key>
<array>
<string>A13#</string>
</array>
<key>Another Test</key>
<array>
<string>1111111111</string>
</array>
<key>Test Three</key>
<array>
<string>2222222222</string>
</array>
<key>Final Test</key>
<array>
<string>3333333333</string>
</array>
</dict>
here is the method I am using to try to
-(void)writeToFile:(NSString *)s1{
NSBundle *bundle = [NSBundle mainBundle];
NSString *plistPath = [bundle pathForResource:#"saved" ofType:#"plist"];
NSMutableDictionary *dictionary = [[[NSDictionary alloc] initWithContentsOfFile:plistPath] mutableCopy];
NSString *tempString = [NSString stringWithFormat:#"1234567"];
[dictionary setObject:tempString forKey:s1];
[dictionary writeToFile:plistPath atomically:YES];
}
You can't cast or otherwise convert a string into an array; they're separate, distinct objects. It's the same as if in real life you try to turn your dog into a station wagon, it isn't happening.
Instead, put your dog inside the station wagon (or put your string(s) inside an array). You can create the array with [NSArray arrayWithObjects:#"string1", #"string2", nil];. Stick that inside your dictionary for a given key, along with your final string for another key, save it, and you'll have a plist with an array of one or more strings.
Also, in your code example your dictionary is leaking memory. Read up on memory management in Objective-C, you're going to run into lots of crashes and performance issues until you understand it well.
You an convert a string to a single element array with
[NSArray arrayWithObject:str];
So if you want your plist to contain entries as arrays of strings, and you want just a single string as an element, then you do something like:
[dictionary setObject:[NSArray arrayWithObject:tempString] forKey:s1];
I don't actually no why you would want it this way unless you want to allow for multiple strings per key at some other time.
Also, as Marc mentioned, you are leaking the initial (unmutable) dectionary you create. Read the memory management rules at http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html.
Further, you should never write inside your application's bundle. For one thing, your application may be on a write protected volume or the current user may not have permissions to change it. For another, the file would then be shared by all users. And for a third, it would break the code signing. Instead, write the file to either the Preferences folder or the Application Support folder.
And finally, if these are intended to be user preferences of some sort, then you should use the preferences system, which allows configuring defaults and stores the preferences in the preferences folder for you. See http://developer.apple.com/documentation/Cocoa/Conceptual/UserDefaults/UserDefaults.html for more information.
The correct way to save an NSArray (by itself) to a plist file is as follows:
NSArray* anArray = ...;
[anArray writeToFile:#"/path/to/file.plist" atomically:YES];
However, you can't save an NSString as an array. Given the XML plist you provided, if you want to add entries with the same format, you can use this much simpler code:
- (void) writeToFile:(NSString *)string {
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"saved" ofType:#"plist"];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
[dictionary setObject:[NSArray arrayWithObject:#"1234567"] forKey:string];
[dictionary writeToFile:plistPath atomically:YES];
}
This also avoids a memory leak in your code, where the receiver of -mutableCopy escapes with a retain count of 1. (This isn't a problem under GC, but it's still bad practice.) You shouldn't need to use +[NSString stringWithFormat:], just use a string literal. If you want to use a different as the string in the array, you can either pass it in as an additional parameter, grab it from another method, etc.
This method is still brittle in that it only stores one string in the array matched with the given key — also, the method name would be better if it were more indicative of exactly what it does. Also, if there will only ever be one string value for each key, you might consider revising the plist to omit the arrays entirely, since it just chews up space and complicates the code.