I have been using this following thing to do my plist and save data and display contents of plist in a Table View. I am using a NSMutableArray to fetch the contents of the array and display it in the table view . VC1 is my table view & VC2 is my EnterDetail VC So I have declared my code as follows -
class ListTableViewController: UITableViewController
{
var arr = NSMutableArray()
var plistfinalpath = String()
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
override func viewDidAppear(animated: Bool)
{
let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, .UserDomainMask, true)[0]
plistfinalpath = path.stringByAppendingString("/login.plist")
print(plistfinalpath)
arr = (NSMutableArray(contentsOfFile: plistfinalpath))!
print("time of appearing\(arr)")
/* if let temparray = NSMutableArray(contentsOfFile: plistfinalpath)
{
arr = temparray
}*/
self.tableView.reloadData()
}
So as you can see that I have declared a 'arr' as a NSMutableArray type and it is not optional. And when I use in my didappear() the compiler forces me to force unwrap it. Why is that so why does it happens evenif I have declared 'arr' as non optional? And here comes the real problem whenever I run my app for the first time the plist is empty implies 'arr' is also empty. And since I have used forced unwrapping my app crashes due to fatal error. And if I uncomment my commented line in the above code, the if let part, it works fine . So any suggestions. Thanks.
there is no trouble with your NSMutableArray.
var arr = NSMutableArray()
create non optional instance of an array. so far, so good ... An expression
NSMutableArray(contentsOfFile: plistfinalpath)
can return nil, so it returns the optional value. Here you try to create another instance of NSMutableArray and assign the result to your var arr.
A mutable array containing the contents of the file specified aPath.
Returns nil if the file can’t be opened or if the contents of the file
can’t be parsed into a mutable array.
Probably the best way for you is something like
import Foundation
var arr = NSMutableArray(contentsOfFile: "somePath") ?? NSMutableArray()
Here your variable is guaranty to have the default value (an empty instance of NSMutableArray) if the file does not exist yet (or other failure happened)
To resolve your fatal error check your file exist or not :-
//Check whether your file exist or not
let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, .UserDomainMask, true)[0]
plistfinalpath = path.stringByAppendingString("/login.plist")
if NSFileManager.defaultManager().fileExistsAtPath(plistfinalpath)
{
arr = (NSMutableArray(contentsOfFile: plistfinalpath))!
}
Declare it like
var arr : NSMutableArray = []
// Or
var arr : NSMutableArray!
Related
I am trying to understand the logic of draggable collection view cells. It works with dummy data however, I couldn't figure out how to make it work with real data.
I couldn't know what title to give to the question, please feel free to edit/improve it
If I use this approach with a dummy array items and call the function
class TableViewController: UITableViewController, KDRearrangeableCollectionViewDelegate, UICollectionViewDataSource {
lazy var data : [[String]] = {
var array = [[String]]()
let images = [
"1.jpg", "2.jpg", "3.jpg"
]
if array.count == 0 {
var index = 0
var section = 0
for image in images {
if array.count <= section {
array.append([String]())
}
array[section].append(image)
index += 1
}
}
return array
}()
func moveDataItem(fromIndexPath : NSIndexPath, toIndexPath: NSIndexPath) {
let name = self.data[fromIndexPath.section][fromIndexPath.item]
self.data[fromIndexPath.section].removeAtIndex(fromIndexPath.item)
self.data[toIndexPath.section].insert(name, atIndex: toIndexPath.item)
print(self.data)
}
At this point print(self.data) prints the new order of the array items after dragging/rearranging.
Example print(self.data) log:
[["2.jpg", "1.jpg", "3.jpg"]]
Now I have my real data as and it gets appended by items after fetching from database..
var realImages = [NSURL]()
// I tried assigning `images` array inside `lazy var data` but received error:
lazy var data : [[String]] = {
var array = [[String]]()
let images = realImages // error here
...
Instance member realImages cannot be used on type TableViewController
What is the proper way of using my real array in that case?
(This explanation and its Git repo was great for understanding it). I simplified it even further with dummy data but I couldn't understand the logic of 'lazy var' and regular 'var' and how to make it work in this scenario
You need to use self explicitly like let images = self.realImages.
lazy var data : [[String]] = { [unonwed self] in
var array = [[String]]()
let images = self.realImages // error gone :)
(Note that we had to say [unowned self] in here to prevent a strong reference cycle)
Updated: My FIX
This is what fixed my problem, and it may help you!
override func viewDidAppear(animated: Bool) {
authors = loadPlist()
tableView.reloadData()
}
Now data loads perfectly:)
I'm having troubles with my code. I have a plist that I am modifying and adding entries to.
Everything is working fine, but I need to restart the iOS simulator to see the newly added plist entries.
To quickly summarize, Everything gets updated in the plist file, but it requires the app to be rebuilt.
I've tried tableView.reloadData() but my understanding is that once the viewDidLoad() runs only once.
Now I tried to bypass this by creating additional Segue connections back and fourth, but I found this counter productive and cumbersome.
Thank You very much!
P.S
I have a feeling I'm not persisting the data properly?
How I retrieve info from plist, while creating copy:
private func loadPlist() -> NSArray {
var data = NSArray()
// attempt to open "authors.plist" from the application's Documents/ directory
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
as NSArray
let documentsDirectory = paths.objectAtIndex(0) as! NSString
let path = documentsDirectory.stringByAppendingPathComponent("authors.plist")
let fileManager = NSFileManager.defaultManager()
// if the file is not available (first access), then open it from the app's
// mainBundle Resources/ folder:
if(!fileManager.fileExistsAtPath(path)) {
let plistPath = NSBundle.mainBundle().pathForResource("authors", ofType: "plist")
data = NSArray(contentsOfFile: plistPath!)!
do {
try fileManager.copyItemAtPath(plistPath!, toPath: path)
} catch let error as NSError {
// failure
print("Error copying plist file: \(error.localizedDescription)")
}
print("First launch... Default plist file copied...")
data.writeToFile(path, atomically: true)
}
else {
data = (NSArray(contentsOfFile: path))!
}
return data
}
To Save Data:
func saveData(){
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
as NSArray
let documentsDirectory = paths.objectAtIndex(0) as! NSString
let path = documentsDirectory.stringByAppendingPathComponent("authors.plist")
if let plistArray = NSMutableArray(contentsOfFile: path) {
//...
//...
plistArray.writeToFile(path, atomically: false)
}
}
Save Button:
#IBAction func saveDataButton(sender: UIBarButtonItem) {
saveData()
navigationController?.popViewControllerAnimated(true)
}
How I'm populating my tableview:
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
if let author = authors[indexPath.row] as? [String: AnyObject], let name = author["Author"] as? String {
// Configure Cell
cell.textLabel?.text = name
}
return cell;
my viewDidLoad() now:
override func viewDidLoad() {
super.viewDidLoad()
title = "Authors"
authors = loadPlist()
tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: cellIdentifier)
tableView.delegate = self;
tableView.dataSource = self;
tableView.reloadData()
}
You seem very confused. Yes, viewDidLoad only gets called once during the life of a view controller.
You should call your loadData method once in viewDidLoad.
You should alter loadData to make a mutable copy of your array before returning it. You should keep the mutable array in a property of your view controller. That will become your model (data store) for your table view.
As the user makes changes to the entries in the table view, apply those changes to your mutable array. Make your table view data source methods fetch data from that mutable array.
Your storeData method is wrong. It first reads the plist from disk (wiping out any changes the user has made) and then writes it back. The end result is that it does nothing. Get read of the read at the beginning of the function and instead save the mutable array that has changes in it back to the plist file.
I was using an UIViewController in Swift but I get it when I try to persist the data and trying to retrieving it to back in application.
import UIKit
class ViewController: UIViewController {
#IBOutlet var linefields:[UITextField]!
func dataFilePath() -> String {
let paths = NSSearchPathForDirectoriesInDomains(
NSSearchPathDirectory.DocumentDirectory,
NSSearchPathDomainMask.UserDomainMask, true)
let documetnDirectory = paths[0] as! NSString
return documetnDirectory.stringByAppendingPathComponent("data.plist") as String
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let filePath = self.dataFilePath()
if (NSFileManager.defaultManager().fileExistsAtPath(filePath)){
let array = NSArray(contentsOfFile: filePath) as! [String]
for (var i=0;i<array.count;i++){
linefields[i].text = array [i]
}
}
let app = UIApplication.sharedApplication()
NSNotificationCenter.defaultCenter().addObserver(self,selector: "applicationWillResignActive:",name:UIApplicationWillResignActiveNotification,
object: app)
}
func applicationWillResignActive(notification:NSNotification){
let filePath = self.dataFilePath()
let array = (self.linefields as NSArray).valueForKey("text") as! NSArray
//-----> Next line: fatal error: unexpectedly found nil while unwrapping an Optional value
array.writeToFile(filePath, atomically:true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Does anyone know about this?
Check your connections from textFeilds (storyboard file ) to Outlets(swift file). Because missing connections would return nil.
This is swift's error for when you tell it there will be a value in a certain variable, but there was not one. Basically, one of the variables you were reading data from had no data, because for some reason it had never been assigned data.
As commenters have recently said, it is probably this line: (self.linefields as NSArray).valueForKey("text") as! NSArray. My advice is to switch the as! to as? or as, because this won't tell swift, "there is definitely a value here." Other comments have suggested trying to substitute this potential problem line with something simpler and use the map() function more carefully elsewhere in your code.
I'm playing around with the Master-Detail iOS project in Xcode6.01 and Swift. I have a simple data entry screen that adds swift dictionary objects to a Swift datasource array for the table.
The data entry and table view work fine. I've run into this problem when tapping on a cell, extracting a dictionary object from the array and trying to pass that dict object along to the detail view.
The configureView() function in the detail view is causing an exception. Here's the code:
func configureView() {
if let dictObject:Dictionary = detailItem as? Dictionary<String,String> {
if var strName = dictObject["name"] {
// prints fine:
println(strName)
// fatal error
self.detailDescriptionLabel.text = strName
// debug output:
// prints the string in strName (no mention of optional)
// assigning to label:
// fatal error: unexpectedly found nil while unwrapping an Optional value
}
}
}
It seems strange that the println() statement has no problem with the strName variable and there is no mention of it being an optional in the output panel. As soon as it tries to assign the string to the label, bang - crash.
Found the answer my self. It was not the dictionary object that was nil, but the labels in the view!
After adjusting the code with an IF LET construct for the labels it works like this:
func configureView() {
if let dictObject:Dictionary = detailItem as? Dictionary<String,String> {
if let dLabel = detailDescriptionLabel {
dLabel.text = dictObject["name"]
}
if let eLabel = emailLabel {
eLabel.text = dictObject["email"]
}
}
}
I must admit though, I don't know why this is needed. I thought the storyboard would init the labels. The labels in question are regular IBOutlets:
#IBOutlet weak var detailDescriptionLabel: UILabel!
#IBOutlet weak var emailLabel: UILabel!
In an attempt to port some small existing code to Swift I'm running into a bit of a problem.
I can do the following in Objective-C
NSMutablArray *myMutableArray = [NSMutableArray array];
myMutablArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error].mutableCopy;
However in my attempt at trying this in swift I get 'Could not find member executeFetchRequest'
class exampleTableViewController: UITableViewController {
var managedObjectContext: NSManagedObjectContext? = nil
var myMutableArray: NSMutableArray = NSMutablArray()
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
var request = NSFetchRequest(entityName: "TestTable")
var error: NSError? = nil
self.myMutableArray = self.managedObjectContext!.executeFetchRequest(request, error: &error)
}
...
}
Could someone please point me in the right direction?
Many thanks
EDIT: Pasted code as it looks in xcode.
The error is kind of misleading.
It is saying in full "I don't know any functions which return a mutable array"
Swift is type safe and the compiler enforces that with enthusiasm.
Change the type of your var to NSArray , which is what the function executeFetchRequest returns.
class exampleTableViewController: UITableViewController {
var managedObjectContext: NSManagedObjectContext? = nil
var myArray: NSArray? = nil;
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
var request = NSFetchRequest(entityName: "TestTable")
var error: NSError? = nil
self.myArray = self.managedObjectContext!.executeFetchRequest(request, error: &error)
}
}
Should you want a mutable array you will need to do an explicit cast
let results = self.managedObjectContext!.executeFetchRequest(request, error: &error)
myMutableArray = NSMutableArray(array: results);
You do not need to initialize the array before you call executeFetchRequest, the call will return an array and destroy the one you created already.
I do not see your creation of error and it appears you have a "local" managedObjectContext variable yet you are attempting to reference self. Try:
self.myMutableArray = managedObjectContext!.executeFetchRequest(request, error: &error)
Assuming there is no code between the two lines you posted in that second code block.