Saving/loading files: "warning: dynamic accessors failed to find #property implementation for 'uniqueId'" - swift

I am trying to save/load an object to file, and for this I am just converting the object (self.game) to an array containing dictionaries, and then using write(toFile:atomically:). But the saveGame method doesn't do anything: I am allowed to choose a file, to press OK and the save panel gets dismissed, but the file isn't really saved. When I use the loadGame method I see that a strange error appears:
warning: dynamic accessors failed to find #property implementation for 'uniqueId' for entity ABCDInfo while resolving selector 'uniqueId' on class 'ABCDInfo'. Did you remember to declare it #dynamic or #synthesized in the #implementation ?
#IBAction func saveGame(_ sender: NSButton) {
let savePanel = NSSavePanel()
savePanel.allowedFileTypes = ["com.apple.property-list"]
if savePanel.runModal() == NSFileHandlingPanelOKButton {
let file = savePanel.url!.absoluteString
self.game.write(toFile: file)
}
}
#IBAction func loadGame(_ sender: NSButton) {
let openPanel = NSOpenPanel()
openPanel.allowedFileTypes = ["com.apple.property-list"]
if openPanel.runModal() == NSFileHandlingPanelOKButton {
let file = openPanel.url!.absoluteString
self.game = Game(withFile: file)
self.selectedSlot = nil
self.enemySlot = nil
self.restartButton.isEnabled = false
self.updateTurnLabel()
self.chessboardView.reloadData()
}
}
The self.game.write(toFile:) method just converts the object to a NSArray and writes it to the file passed as argument.

When you save an array with a dictionary, you can simply use writeToFile in Objective-C. But the same is not true for Swift unless the keys are absolutely string objects. You have to first archive it with NSKeyedArchiver. You can then write it to a file. If you don't archive it, you can end up with no file with or without a warning. The following is an example.
let title = recordArray[r1].valueForKey("texttitle") as! String
let text = recordArray[r1].valueForKey("texttext") as! String
let urls = recordArray[r1].valueForKey("urls") as! String
var codeArray = [NSDictionary]()
let dict = ["Title":title,"URLs":urls,"Code":text] as NSDictionary // or Dictionary
codeArray.append(dict)
let data = NSKeyedArchiver.archivedDataWithRootObject(codeArray)
data.writeToFile(path, atomically: true)
When you open a file, use NSKeyedUnarchiver to unarchive data.

Related

iOS: In Swift, how to check if obj.value(forKey: "key") is nil before the test?

I have a piece of code that works,
var loginFieldText = ""
if let wd = UIApplication.shared.delegate?.window
{
let vc = wd!.rootViewController //Check
if(vc is UINavigationController)
{
let viewControllers = (vc as! UINavigationController).viewControllers
for obj in viewControllers {
if let loginField = obj.value(forKey: "loginField")
{
if let loginText = (loginField as AnyObject).value(forKey: "text")
{
loginFieldText = loginText as! String
}
}
}
}
}
but my problem is that I have a error message if I use that line:
if let loginField = obj.value(forKey: "loginField")
Because it checks if there is a key "key" that doesn't exist in the view. How can I check first if that key exists before it crashes?
Error message:
valueForUndefinedKey:]: this class is not key value coding-compliant for the key loginField.'
For info, If I try to use the most logical following code:
let loginVC = obj as! LoginViewController
let loginTF = loginVC.loginTextField
=> I have an error message:
fatal error: file '/Users/OlostA/Desktop/Git/FormBox/formbox/Formbox/Planning/Formbox-Bridging-Header.h' has been modified since the precompiled header '/Users/OlostA/Library/Developer/Xcode/DerivedData/Formbox-fpnftywlyjuvvubjjzpknxxdyhul/Build/Intermediates.noindex/PrecompiledHeaders/Formbox-Bridging-Header-swift_7N984CYB20BK-clang_28VAG4OSP9DZS.pch' was built
note: please rebuild precompiled header '/Users/OlostA/Library/Developer/Xcode/DerivedData/Formbox-fpnftywlyjuvvubjjzpknxxdyhul/Build/Intermediates.noindex/PrecompiledHeaders/Formbox-Bridging-Header-swift_7N984CYB20BK-clang_28VAG4OSP9DZS.pch'
/Users/OlostA/Desktop/Git/FormBox/formbox/Formbox/Planning/Formbox-Bridging-Header.h:36:9: note: in file included from /Users/OlostA/Desktop/Git/FormBox/formbox/Formbox/Planning/Formbox-Bridging-Header.h:36:
#import "LoginViewController.h"
^
/Users/OlostA/Desktop/Git/FormBox/formbox/Formbox/LoginViewController.h:18:9: note: in file included from /Users/OlostA/Desktop/Git/FormBox/formbox/Formbox/LoginViewController.h:18:
#import "MainViewController.h"
^
/Users/OlostA/Desktop/Git/FormBox/formbox/Formbox/MainViewController.h:17:9: error: 'Formbox-Swift.h' file not found
#import "Formbox-Swift.h"
^
1 error generated.
<unknown>:0: error: failed to emit precompiled header '/Users/OlostA/Library/Developer/Xcode/DerivedData/Formbox-fpnftywlyjuvvubjjzpknxxdyhul/Build/Intermediates.noindex/PrecompiledHeaders/Formbox-Bridging-Header-swift_7N984CYB20BK-clang_28VAG4OSP9DZS.pch' for bridging header '/Users/OlostA/Desktop/Git/FormBox/formbox/Formbox/Planning/Formbox-Bridging-Header.h'
First off go to the product menu and press the option key. That will change the menu option Clean to Clean Build Folder… That should solve the build error you've been seeing.
Make sure LoginViewController.h is included in the bridging header (looks like it is but better safe than sorry).
Then let's clean up that pyramid of doom you got going on there (don't worry, we have all written at least a few of them):
var loginFieldText = ""
// Check if the root view controller of the window is a navigation controller and grab a reference if it is.
guard let vc = UIApplication.shared.delegate?.window.rootViewController as? UINavigationController else { return }
let viewControllers = vc.viewControllers
for obj in viewControllers {
// See if the obj can be cast as a LoginViewController and if the loginField can be retrieved
guard let loginField = (obj as? LoginViewController)?.loginField else { continue }
// We want loginFieldText to be an empty string or the contents of loginField so we use the nil-coalescing operator
loginFieldText = loginField.text ?? loginFieldText
}
I wrote this without a compiler so there may be a little cleanup but the idea is there.

In Swift, How to print NSMutableArray in textView

override func viewDidAppear(animated: Bool) {
let userDefaults:NSUserDefaults = NSUserDefaults.standardUserDefaults()
let itemListFromUserDefaults:NSMutableArray? = userDefaults.objectForKey("itemList") as? NSMutableArray
if((itemListFromUserDefaults) != nil){
toDoItems = itemListFromUserDefaults!
}
self.tableView.reloadData()
print("toDoItems")
print(toDoItems)
}
In Swift, I used CoreData to save groceries list, now I want to print toDoItems in a textView, How to do it
Create an NSString out of the NSArray using map, and then assign that string to the textView's text property.
Good question, try this:
textView.text = arr.joinWithSeparator(",")

Core Data - Change NSManagedObject array into array of Strings using valueForKey -OSX

So iv using an NSTokenField to allow data entry, the TokenField will suggest thing when the user starts typing. I want it to suggest things that are already inside core data.
To do this i have this function being called when the cell moves to superview (This is all happening inside a custom table view cell)
var subjectInformation = [NSManagedObject]()
let appDel = NSApplication.sharedApplication().delegate as! AppDelegate
let context = appDel.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "SubjectInformation")
do {
let results = try context.executeFetchRequest(fetchRequest)
subjectInformation = results as! [NSManagedObject]
} catch {
}
this returns an array of NSManagedObjects, now i want for every object in managed object get get the valueForKey("subjectName") as insert it into a array of string so that i can return that inside this token field Function
func tokenField(tokenField: NSTokenField, completionsForSubstring substring: String, indexOfToken tokenIndex: Int, indexOfSelectedItem selectedIndex: UnsafeMutablePointer<Int>) -> [AnyObject]? {
return subjectInformation //this is where is should return an array eg; ["English","Maths","Science"]
How would i do this? Thanks :)
If you properly subclassed your NSManagedObject you can use expressive Swift style filters and maps. You would cast your results array to [SubjectInformation] and
let subjectList = subjectInformation.map { $0.subjectName }
Try this:
(subjectInformation as! NSArray).valueForKeyPath("#unionOfObjects.subjectName")
This should return an array of the subjectNames of all the subjectInformation items.

Swift Data File not being created

I am trying to save an array of a user-created class named "TopTenData". The code I used follows. Running the code indicates the file is created, but the file is NOT created. When I navigate to the path I do not find the desired file. Any help would be appreciated.
func writeArrayToPlist(array: [TopTenData]) {
if let arrayPath: String = createArrayPath() {
(array as NSArray).writeToFile(arrayPath, atomically: false)
print("Array written successfully")
print(arrayPath) // navigating this path shows no files
}
}
func createArrayPath () -> String? {
if let docsPath: String = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last {
return ((docsPath as NSString).stringByAppendingPathComponent("myTopTenData") as NSString).stringByAppendingPathExtension("plist")
}
return nil
}
writeToFile method of NSArray has some limitations. For example it only support these data types:
NSString
NSData
NSDate
NSNumber
NSArray
NSDictionary
For custom data types you can use NSKeyedArchiver and NSKeyedUnarchiver
See this post

Create CSV file in Swift and write to file

I have an app I've made that has a UITableView with todoItems as an array for it. It works flawlessly and I have an export button that creates a CSV file from the UITableView data and emails it out:
// Variables
var toDoItems:[String] = []
var convertMutable: NSMutableString!
var incomingString: String = ""
var datastring: NSString!
// Mail alert if user does not have email setup on device
func showSendMailErrorAlert() {
let sendMailErrorAlert = UIAlertView(title: "Could Not Send Email", message: "Your device could not send e-mail. Please check e-mail configuration and try again.", delegate: self, cancelButtonTitle: "OK")
sendMailErrorAlert.show()
}
// MARK: MFMailComposeViewControllerDelegate Method
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
// CSV Export Button
#IBAction func csvExport(sender: AnyObject) {
// Convert tableView String Data to NSMutableString
convertMutable = NSMutableString();
for item in toDoItems
{
convertMutable.appendFormat("%#\r", item)
}
print("NSMutableString: \(convertMutable)")
// Convert above NSMutableString to NSData
let data = convertMutable.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
if let d = data { // Unwrap since data is optional and print
print("NSData: \(d)")
}
//Email Functions
func configuredMailComposeViewController() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self
mailComposerVC.setSubject("CSV File Export")
mailComposerVC.setMessageBody("", isHTML: false)
mailComposerVC.addAttachmentData(data!, mimeType: "text/csv", fileName: "TodoList.csv")
return mailComposerVC
}
// Compose Email
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.presentViewController(mailComposeViewController, animated: true, completion: nil)
} else {
self.showSendMailErrorAlert() // One of the MAIL functions
}
}
My question is how do I create the same CSV file, but instead of emailing, save it to file? I'm new to programming and still learning Swift 2. I understand that the section of code (data!, mimeType: "text/csv", fileName: "TodoList.csv") creates the file as an attachment. I've looked online for this and trying to understand paths and directories is hard for me. My ultimate goal is to have another UITableView with a list of these 'saved' CSV files listed. Can someone please help? Thank you!
I added the following IBAction to my project:
// Save Item to Memory
#IBAction func saveButton(sender: UIBarButtonItem) {
// Convert tableView String Data to NSMutableString
convertMutable = NSMutableString();
for item in toDoItems
{
convertMutable.appendFormat("%#\r", item)
}
print("NSMutableString: \(convertMutable)")
// Convert above NSMutableString to NSData
let data = convertMutable.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
if let d = data { // Unwrap since data is optional and print
print("NSData: \(d)")
}
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
func writeToFile(_: convertMutable, path: String, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws {
}
}
I was struggling to find a decent simple answer to this for ages.
Here is the best way that I've found to create a csv file and even the directory you want it to be it and write to it.
//First make sure you know your file path, you can get it from user input or whatever
//Keep the path clean of the name for now
var filePath = "/Users/Johnson/Documents/NEW FOLDER/"
//then you need to pick your file name
let fileName = "AwesomeFile.csv"
//You should probably have some data to put in it
//You can even convert your array to a string by appending all of it's elements
let fileData = "This,is,just,some,dummy,data"
// Create a FileManager instance this will help you make a new folder if you don't have it already
let fileManager = FileManager.default
//Create your target directory
do {
try fileManager.createDirectory(atPath: filePath!, withIntermediateDirectories: true, attributes: nil)
//Now it's finally time to complete the path
filePath! += fileName!
}
catch let error as NSError {
print("Ooops! Something went wrong: \(error)")
}
//Then simply write to the file
do {
// Write contents to file
try fileData.write(toFile: filePath!, atomically: true, encoding: String.Encoding.utf8)
print("Writing CSV to: \(filePath!)")
}
catch let error as NSError {
print("Ooops! Something went wrong: \(error)")
}
PS. Just noticed that question is from year ago, but I hope it helps a struggling newbie like myself when they inevitably stumble upon it like I did.
convertMutable can be easily written to disk with either fun writeToFile(_ path: String, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws or func writeToURL(_ url: NSURL, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws. All you have to do is create a path or URL to write the string out to. If you are using iCloud things will be more challenging but for locally stored files you can use let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString to get the root path of the documents directory.
Update: Based on you first comment below here is some added info:
The first issue is that it appears as though you are looking for code you can just paste int your project without really understanding what it does. My appologies if I'm wrong, but if I'm right this is not a good route to take as you will have many issues down the road when things change.
At the bottom of your last code section you are trying to create a function inside a function which is not going to do what you want. The above mentioned functions are the declarations of two NSString functions not functions that you need to create. As NSMutableString is a subclass of NSString you can use those functions on your convertMutable variable.
Another thing you need to deal with is creating a name for the file you want to save, currently you have pasted in the line above that gets the Documents directory but does not have a file name. You will need to devise a way to create a unique filename each time you save a CSV file and add that name to the end of path. Then you can use writeToFile… or writeToURL… to write the string to the desired location.
If you find you don't fully comprehend the code you are adding then consider getting a book or finding classes about Swift (Coursera.org has a class that may be of use). There are plenty of resources out there learn the basics of software development and Swift, it will take effort and time but it will be worth it in the end if this is something you want to pursue.