How to convert .plist xml string to NSDictionary - ios

I downloaded .plist file with Swift and Alamofire and I want to read values of .plist file.
if let url = URL(string: urlString) {
Alamofire.download(url).responseData(completionHandler: { response in
if response.result.isSuccess {
if let plistData = response.result.value {
if let plistXml = String(data: data, encoding: .utf8) {
// plistXml contains the actual plist contents as String object.
}
}
}
})
}
I have to objects containing my downloaded .plist file:
plistData: the downloaded Data object
plistXml: String object from plistData
Using any of these objects, I want to convert the plist fie to NSDictionary or Dictionary.

You can use PropertyListSerialization from the Foundation framework to create a NSDictionary
from the given property list data. Swift 3 Example:
do {
if let plist = try PropertyListSerialization.propertyList(from: plistData, format: nil)
as? NSDictionary {
// Successfully read property list.
print(plist)
} else {
print("not a dictionary")
}
} catch let error {
print("not a plist:", error.localizedDescription)
}
And with
if let plist = try PropertyListSerialization.propertyList(from: plistData, format: nil)
as? [String: Any] { ... }
you'll get the result as a Swift Dictionary.

Use SWXMLHash
Read plistXml and parse xml step by step using that documentation.
Save the element that you want to inside plistXml into Dictionary
Good Luck!

Here is the objective-C version of code. You can convert it very easily.
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"register.plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
plistPath = [[NSBundle mainBundle] pathForResource:#"register" ofType:#"plist"];
}
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:plistPath];

Related

How to get a value from dictionary which is inside an array?

I want to store a message - "hi this is John KL", in some string, how to parse following example.
[
{
"message": "hi this is John KL"
}
]
Swift:
guard let anArray = input as? [[String:String]],
let message = anArray.first["message"] else {
print("unable to fetch data"
}
Objective-C:
- (void) readJSON {
NSError *result;
NSURL *url = [[NSBundle mainBundle] URLForResource: #"sample"
withExtension: #"JSON"];
NSData *data = [NSData dataWithContentsOfURL: url
options: 0
error: &result];
if (result != nil) {
NSLog(#"Error reading file: %#", result);
return;
}
NSArray<NSDictionary<NSString*, NSString*> *> *array = [NSJSONSerialization JSONObjectWithData: data options: 0 error: &result];
if (result != nil) {
NSLog(#"Error converting JSON: %#", result);
return;
}
else {
NSLog(#"\nJSON data = \n%#", array);
if (array.count < 1) {
NSLog(#"Not enough elements in array");
return;
}
NSString *message = array[0][#"message"];
if (message == nil) {
NSLog(#"Unable to fetch message");
} else {
NSLog(#"Message = \"%#\"", message);
}
}
}
The above Objective-C code does not test to make sure the object read from the JSON file is the correct type. It will crash if it is not an array containing a dictionary with a string key and string value. For a production app you'll want to add code to type-check the data.
Swift
Assign your json array to a variable type of [String : String] or [String : Any] dictionary array. [String : Any] is most commonly used dictionary but according to your data it suites with [String : String]
if let array = [
{
“message” : “hi this is John KL”
}
] as [Any]
Now, get your json/dictionary from array using index value and then get string from json using json key.
if let dictionary = array.first as? [String : Any] {
if let stringMessage = dictionary["message"] as? String {
print("stringMessage - \(stringMessage)")
}
}
Objective-C
NSArray * array = [
{
“message” : “hi this is John KL”
}
];
NSDictionary * dictionary = (NSDictionary *)[array objectAtIndex: 0];
NSString * stringMessage = (NSString *)[dictionary valueForKey: "message"];
NSLog(#"stringMessage - %#",stringMessage);
You can try this answer.
NSArray *result = [json objectForKey:#"result"];
for(NSString *currenObject in result){
NSLog(#"%#",currenObject);
NSString *currentValue = [currenObject valueForKey:#"message"];
}
NSLog(#"%#",currentValue);

ZipArchive skipping over files 49-100

I have a function that is supposed to create a batch of files, write them to a directory, zip that directory, and attach it to an email.
When I run it, it tells me that it has written 299 files, but when I open up the zip folder precisely 50 are missing
Here is the code. I thought about trimming it down, but I was afraid of removing something that might help someone spot the problem:
func exportAllData() {
var totalExports = 0
// fetch all the data first
// 1 Describe what you want
let fetchRequest = NSFetchRequest(entityName: "Fish")
let sortDescriptor = NSSortDescriptor(key: "time", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
// 2 Get it!
do {
let allCatches = try managedContext!.executeFetchRequest(fetchRequest) as? [NSManagedObject]
totalExports = allCatches!.count
// Create a folder to store the catches in
// path to where data will be written
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let applicationDocumentsDirectory:NSURL = urls[urls.count-1]
let destinationURL = applicationDocumentsDirectory.URLByAppendingPathComponent("filesToExport", isDirectory: true)
do { try NSFileManager.defaultManager().createDirectoryAtURL(destinationURL, withIntermediateDirectories: false, attributes: nil)
print("created directory")
var currentFile = 0
// Write each fish to file
for fish in allCatches! {
currentFile+= 1
// convert NSManagedObject into a Dictionary, serialize it to NSData, and write it to file
let keys = fish.entity.propertiesByName.keys.array
let dictionary = fish.dictionaryWithValuesForKeys(keys)
let data = NSKeyedArchiver.archivedDataWithRootObject(dictionary)
let fileName = "catchNumber\(currentFile)"
let fileURL = destinationURL.URLByAppendingPathComponent(fileName)
let success = data.writeToFile(fileURL.path!, atomically: false)
if success { print("successfully exported: "+fileName) } else { print("FAILED to export: "+fileName) }
}
// DEBUG
do {
let contents = try NSFileManager.defaultManager().contentsOfDirectoryAtPath(destinationURL.path!)
print(contents)
} catch {
print(error)
}
// After all the fish have been written to file, zip the file
let zipURL = applicationDocumentsDirectory.URLByAppendingPathComponent("exported.zip")
Main.createZipFileAtPath(zipURL.path!, withContentsOfDirectory: destinationURL.path!)
// Load the zip file up as data and send it out in an email
let zipData = NSData(contentsOfURL: zipURL)
let picker = MFMailComposeViewController()
picker.mailComposeDelegate = self
picker.setSubject("Shared Catch Log! (\(totalExports) entries)")
picker.setMessageBody("Open the attachment to this email on an iOS device with Catch Stats installed on it to import all catch into your Catch Log!", isHTML: false)
picker.addAttachmentData(zipData!, mimeType: "applcation/CatchStats", fileName: "CatchStatsLog.csl")
presentViewController(picker, animated: true, completion: nil)
// If failed to created directory
} catch {
print("directory already existed")
}
// If failed to execute fetch request
} catch {
print(error)
self.navigationController?.popViewControllerAnimated(true)
}
}
Now, at the //Debug when I check the contents of the folder I'm about to zip, it has 299 files numbered from 1 to 299 in it.
But when I email the file out and open it up, 50-99 are all missing. Can anybody offer me some advice?
I plucked this code out of ZipArchive's Main.m file. This is what it's doing.
+ (BOOL)createZipFileAtPath:(NSString *)path
withContentsOfDirectory:(NSString *)directoryPath {
return [self createZipFileAtPath:path withContentsOfDirectory:directoryPath keepParentDirectory:NO];
}
+ (BOOL)createZipFileAtPath:(NSString *)path
withContentsOfDirectory:(NSString *)directoryPath
keepParentDirectory:(BOOL)keepParentDirectory {
BOOL success = NO;
NSFileManager *fileManager = nil;
Main *zipArchive = [[Main alloc] initWithPath:path];
if ([zipArchive open]) {
// Use a local file manager (queue/thread compatibility)
fileManager = [[NSFileManager alloc] init];
NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtPath:directoryPath];
NSString *fileName;
while ((fileName = [directoryEnumerator nextObject])) {
BOOL isDirectory;
NSString *fullFilePath = [directoryPath stringByAppendingPathComponent:fileName];
[fileManager fileExistsAtPath:fullFilePath isDirectory:&isDirectory];
if (!isDirectory) {
if (keepParentDirectory) {
fileName = [[directoryPath lastPathComponent] stringByAppendingPathComponent:fileName];
}
[zipArchive writeFileAtPath:fullFilePath withFileName:fileName];
} else {
if (0 == [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:fullFilePath error:nil].count) {
NSString *temporaryName = [fullFilePath stringByAppendingPathComponent:#".DS_Store"];
[#"" writeToFile:temporaryName atomically:YES encoding:NSUTF8StringEncoding error:nil];
[zipArchive writeFileAtPath:temporaryName withFileName:[fileName stringByAppendingPathComponent:#".DS_Store"]];
[[NSFileManager defaultManager] removeItemAtPath:temporaryName error:nil];
}
}
}
success = [zipArchive close];
}
return success;
}
I found a solution, but it is more of a work around. After a lot of testing, I realized that ZipArchive would fail to zip a folder with an more than 250 files in it. It didn't matter what the files were named or the order they were in.
Instead of serializing each NSDictionary and writing it to file, I created an array of NSDictionaries, and serialized that instead.That way ZipArchive only had to zip one file, and it seems to be working fine now.

Share Plist between iOS App and WatchKit Extension

I have an app that saves and uses data from a plist file. I'm working on a WatchKit extension that needs to access the same plist file to display data and save to the file. I know I need to be using app groups but I don't know how to share the plist between the iOS app and the WatchKit extension.
Here is how I'm saving to the plist in the iOS app currently.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = [[paths objectAtIndex:0]stringByAppendingPathComponent:#"locations.plist"];
BOOL fileExists = [fileManager fileExistsAtPath:docPath];
NSError *error = nil;
if (!fileExists) {
NSString *strSourcePath = [[NSBundle mainBundle]pathForResource:#"locations" ofType:#"plist"];
[fileManager copyItemAtPath:strSourcePath toPath:docPath error:&error];
}
NSString *path = docPath;
NSMutableArray *plistArray = [[NSMutableArray alloc]initWithContentsOfFile:path];
NSDictionary *locationDictionary = [NSDictionary dictionaryWithObjectsAndKeys:self.locationNameTextField.text, #"locationName", latString, #"latitude", longString, #"longitude", nil];
[plistArray insertObject:locationDictionary atIndex:0];
[plistArray writeToFile:docPath atomically:YES];
Once you've setup your app group (in both your primary iPhone app and the Watch Extension), you can get the path to the shared folder:
NSURL *groupContainerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"YourAppGroupSuiteName"];
NSString *groupContainerPath = [groupContainerURL path];
You can then use the groupContainerPath to build your docPath. Otherwise, your code should work as-is.
I was able to successfully use plist data from my main existing iPhone app in my WatchKit app using Swift.
There are two steps:
Enable WatchKit App Extension Target Membership for each plist you want to use. Click on the plist, then:
Here's the Swift code I used to read the plist, which has "id" and "name" fields.
func valueFromPlist(value: Int, file: String) -> String? {
if let plistpath = NSBundle.mainBundle().pathForResource(file as String, ofType: "plist") {
if let entries = NSArray(contentsOfFile: plistpath) as Array? {
var entry = Dictionary<String, Int>()
for entry in entries {
if let id = entry.objectForKey("id") as? Int {
if id == value {
if let name = entry.objectForKey("name") as? String {
return name
}
}
}
}
}
}
return nil
}

List of Fonts Provided by Application (iOS)

does anyone know how to get a list of custom fonts from the 'Fonts provided by application' key in the info.plist file in Xcode?
Thanks
The following code reads the list of custom font files from the Info.plist,
and extracts the full font name from the font file.
(Parts of the code is copied from https://stackoverflow.com/a/17519740/1187415
with small modifications and ARC adjustments).
Objective-C
NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary];
NSArray* fontFiles = [infoDict objectForKey:#"UIAppFonts"];
for (NSString *fontFile in fontFiles) {
NSLog(#"file name: %#", fontFile);
NSURL *url = [[NSBundle mainBundle] URLForResource:fontFile withExtension:NULL];
NSData *fontData = [NSData dataWithContentsOfURL:url];
CGDataProviderRef fontDataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)fontData);
CGFontRef loadedFont = CGFontCreateWithDataProvider(fontDataProvider);
NSString *fullName = CFBridgingRelease(CGFontCopyFullName(loadedFont));
CGFontRelease(loadedFont);
CGDataProviderRelease(fontDataProvider);
NSLog(#"font name: %#", fullName);
}
Swift 3 equivalent:
if let infoDict = Bundle.main.infoDictionary,
let fontFiles = infoDict["UIAppFonts"] as? [String] {
for fontFile in fontFiles {
print("file name", fontFile)
if let url = Bundle.main.url(forResource: fontFile, withExtension: nil),
let fontData = NSData(contentsOf: url),
let fontDataProvider = CGDataProvider(data: fontData) {
let loadedFont = CGFont(fontDataProvider)
if let fullName = loadedFont.fullName {
print("font name", fullName)
}
}
}
}
You can add your costume font http://www.danielhanly.com/blog/tutorial/including-custom-fonts-in-ios/
But, I don't, know how to get this list, sorry.
But, may be you can look on it in IB, in UILabel attributes.

Retrieve file creation or modification date

I'm using this piece of code to try to retrieve the last modified date of a file:
NSError *error = nil;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath: myFilePath error:&error];
if (attributes != nil) {
NSDate *date = (NSDate*)[attributes objectForKey: NSFileModificationDate];
NSLog(#"Date modiifed: %#", [date description]);
}
else {
NSLog(#"Not found");
}
This works well for files in the main bundle but not if the file is located in a subdirectory of the app's document folder, with myFilePath like this:
/Users/User/Library/Application Support/iPhone Simulator/6.0/Applications/The App ID Number/Documents/mySubdirectory/My Saved File
It keeps returning "not found".
I know the file is there, as I can view it with finder. I also tried removing the spaces in the file name but this had no effect.
The error log says no such file or directory, so it looks like something must've gone wrong when I tried to copy the file to the document directory.
Weird thing is, iterating through the document sub directory with contentsOfDirectoryAtPath shows the file as being present.
I've tried hard-coding the path and retrieving it programmatically, with:
*myFolder = [documentsDirectory stringByAppendingPathComponent:#"myFolder"];
*myFilePath = [myFolder stringByAppendingPathComponent:theFileName];
Can anyone see where I'm going wrong?
Swift 3 solution:
func fileModificationDate(url: URL) -> Date? {
do {
let attr = try FileManager.default.attributesOfItem(atPath: url.path)
return attr[FileAttributeKey.modificationDate] as? Date
} catch {
return nil
}
}
Try this. I had same problem and solved with something like next:
NSURL *fileUrl = [NSURL fileURLWithPath:myFilePath];
NSDate *fileDate;
[fileUrl getResourceValue:&fileDate forKey:NSURLContentModificationDateKey error:&error];
if (!error)
{
//here you should be able to read valid date from fileDate variable
}
hope it helped ;)
Here is a Swift like solution of #zvjerka24 answer:
func lastModified(path: String) -> NSDate? {
let fileUrl = NSURL(fileURLWithPath: path)
var modified: AnyObject?
do {
try fileUrl.getResourceValue(&modified, forKey: NSURLContentModificationDateKey)
return modified as? NSDate
} catch let error as NSError {
print("\(#function) Error: \(error)")
return nil
}
}
If you get the error:
"CFURLCopyResourcePropertyForKey failed because it was passed this URL which has no scheme"
You can try to solve this by appending "file:///" to your NSString file path before converting it to NSURL, it worked in my case.
Can also do:
NSURL* file = ...
NSError* error;`
NSDate *creationDate = [[NSFileManager defaultManager] attributesOfItemAtPath:file.path error:&error].fileCreationDate;
For any file in macOS system we can easily get modification date by using any of below mentioned options:
Way 1:
NSString *path = #"path to file";
NSError *err = nil;
NSDictionary *dic2 = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err];
NSLog(#"File modification Date:%#", dic2[NSFileModificationDate]);
Way 2:
MDItemRef itemRef = MDItemCreate(kCFAllocatorDefault, (__bridge CFStringRef)path);
NSArray *attributeNames = (__bridge NSArray *)MDItemCopyAttributeNames(itemRef);
NSDictionary *attributes = (__bridge NSDictionary *) MDItemCopyAttributes(itemRef, (__bridge CFArrayRef) attributeNames);
CFDateRef modifDate = MDItemCopyAttribute(itemRef, kMDItemContentModificationDate);
NSDate* modificationDate = (__bridge NSDate*) modifDate;
NSLog(#"Modification Date%#", modificationDate);
You can also print various other attributes provided by MDItem :
NSLog(#"All attributes%#", attributes);

Resources