Swift - tableview using plist not reloading after adding new data - ios

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.

Related

Is NSMutableArray instance, optional by default in SWIFT?

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!

Deleting items that have been saved using NSUserDefaults

I have a table view that has its data saved with the following function.
func saveWebsites() {
let websitesData = NSKeyedArchiver.archivedDataWithRootObject(loadedWebsites)
NSUserDefaults.standardUserDefaults().setObject(websitesData, forKey: KEY_WEBSITES)
NSUserDefaults.standardUserDefaults().synchronize()
}
I have tried to allow the user to delete an item from the table view, but what happens is that the user can delete an item from the table view, but when the app reloads the item is still there. The code I am using to try and delete it is this:
func deleteWebsites() {
if let data = NSUserDefaults.standardUserDefaults().objectForKey(KEY_WEBSITES) as? NSData {
let site = NSKeyedUnarchiver.unarchiveObjectWithData(data)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
Anybody know my problem?

Data Persistence

When the app is loaded, I want to know whether I have already saved the data to disk. If it is saved, I just load from disk, if not, I save.
for example, I want to save an array(person) using NSUserDefaults.standardUserDefaults()
Save Method:
let defaults = NSUserDefaults.standardUserDefaults()
if let savedPeople = defaults.objectForKey("people") as? NSData
people = NSKeyedUnarchiver.unarchiveObjectWithData(savedPeople) as! [Person]
But how can I know if I have saved the data or not in viewDidLoad()? I don't want to save every time I open the app, and it will also overwrite the data I modified before.
This is how you will do it:
override func viewDidLoad() {
super.viewDidLoad()
if let savedPeople = NSUserDefaults.standardUserDefaults().objectForKey("people") {
// Use savedPeople based on your need
} else {
NSUserDefaults.standardUserDefaults().setObject(myArray, forKey: "people")
}
}

How to save the users progress in an array Swift with a plist

Hey guys in my app the user clicks a button called the showfunfact() that moves them through an array of strings. When the user removes the app from multitasking or turns off the phone I want the users place to be saved and then when the reload the can pick up where they left off
var TechfactIndex = 0
let TechnologyfactBook = TechFactBook()
override func viewDidLoad() {
super.viewDidLoad()
let defaults = NSUserDefaults.standardUserDefaults()
TechfactIndex = NSUserDefaults.standardUserDefaults().integerForKey("ByteLocation")
defaults.setObject(TechfactIndex, forKey: "ByteLocation")
}
#IBAction func showFunFact() {
if ( UIApplication.sharedApplication().applicationIconBadgeNumber != 0){
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}
if (TechfactIndex >= TechnologyfactBook.TechfactsArray.count) {
self.TechfactIndex = 0
}
TechByteLabel.text = TechnologyfactBook.TechfactsArray[TechfactIndex]
TechfactIndex++
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
NSUserDefaults.standardUserDefaults().setInteger(TechfactIndex, forKey: "ByteLocation")
NSUserDefaults.standardUserDefaults().synchronize()
}
The thing is that viewDidDisappear: is unrelated to your spec. It is not called "When the user removes the app from multitasking or turns off the phone". It is not called when the phone rings. It is not called when the user hits the home button. It's irrelevant. You've put your code in the wrong place.
What you want to do is register to hear when the app is deactivated. That is the moment to write information into the user defaults.
It's not that expensive of an operation to just write to the User Defaults every time the next fun fact is viewed. So for the sake of simplicity, you could just write the new index to the NSUserDefaults every time they view a new fun fact.
#IBAction func showFunFact() {
if (UIApplication.sharedApplication().applicationIconBadgeNumber != 0){
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}
TechfactIndex = NSUserDefaults.standardUserDefaults().integerForKey("ByteLocation")
if (TechfactIndex >= TechnologyfactBook.TechfactsArray.count) {
self.TechfactIndex = 0
}
TechByteLabel.text = TechnologyfactBook.TechfactsArray[TechfactIndex]
TechfactIndex++
NSUserDefaults.standardUserDefaults().setInteger(TechfactIndex, forKey: "ByteLocation")
NSUserDefaults.standardUserDefaults().synchronize()
}
However, the user defaults is usually reserved for preferences... so you may be better off implementing a plist (or similar simple saving option) here to store the current index.
EDIT: Here is a simple example on how you could use a plist to achieve this
First, create a Property List file in your directory, call it Data.plist. Make the root object a dictionary and add an NSNumber object with the key FunFactIndex. This will be a template for your plist on the first time a save occurs.
func showFunFact() {
if (UIApplication.sharedApplication().applicationIconBadgeNumber != 0){
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}
// Load the next index
var factIndex = getCurrentFunFactIndex() as Int
if factIndex < 0 {
println("error")
return
}
if (factIndex >= TechnologyfactBook.TechfactsArray.count) {
factIndex = 0
}
TechByteLabel.text = TechnologyfactBook.TechfactsArray[factIndex]
factIndex++
// Save the index
let saveSuccess = saveFunFactIndex(factIndex);
let successString = (saveSuccess) ? "success" : "failure"
println("Save was a \(successString)")
}
func getCurrentFunFactIndex() -> Int {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as? NSString
let path = documentsDirectory!.stringByAppendingPathComponent("Data.plist")
let fileManager = NSFileManager.defaultManager()
// Check if file exists, copy it over from the bundle if it doesn't
if !fileManager.fileExistsAtPath(path) {
let bundle = NSBundle.mainBundle().pathForResource("Data", ofType: "plist")
fileManager.copyItemAtPath(bundle!, toPath: path, error:nil)
}
if let dataDict = NSDictionary(contentsOfFile: path) {
if let indexNum: AnyObject = dataDict.objectForKey("FunFactIndex") {
return indexNum.integerValue
}
}
return -1 // Something went wrong
}
func saveFunFactIndex(index: Int) -> Bool {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as! NSString
let path = documentsDirectory.stringByAppendingPathComponent("Data.plist")
let fileManager = NSFileManager.defaultManager()
// Check if file exists, copy it over from the bundle if it doesn't
if !fileManager.fileExistsAtPath(path) {
let bundle = NSBundle.mainBundle().pathForResource("Data", ofType: "plist")
fileManager.copyItemAtPath(bundle!, toPath: path, error:nil)
}
if let dataDict = NSMutableDictionary(contentsOfFile: path) {
let indexNum: NSNumber = index
dataDict.setObject(indexNum, forKey: "FunFactIndex")
return dataDict.writeToFile(path, atomically: true)
}
return false
}
There are two methods I added for you; getCurrentFunFactIndex and saveFunFactIndex:. Both will first check in the Documents directory of the sandbox forData.plist. If that file does not exist, it will copy over the template plist that we created in the bundle. All future uses of these methods will use theData.plist` file that is in the Documents directory. This will allow the value to persist on subsequent app launches (removing the app from the background or turning off the device).

Refreshing UITableView Asynchronously after Core Data Loaded Swift

I have a UITableView, tViewNews
I have a refresh function which downloads data from my server, stores it into core data, and then the table view loads this data from core data.
It works perfectly
func refresh(refreshControl: UIRefreshControl) {
self.newsArray = [NewsItem]()
self.newslists = [[NewsItem]]()
self.getNewsFromServer()
self.getNewsFromCoreData()
self.tViewNews.reloadData()
refreshControl.endRefreshing()
}
Now, when the user first opens the news viewDidLoad(), I would like for the table view to first load from the core data, then asynchronously populate my table view array from the server (using precisely the same method as the refresh function) and then reload the table view.
So I have tried the following code in my viewDidLoad() function.
override func viewDidLoad() {
super.viewDidLoad()
// Getting news from Core Data
getNewsFromCoreData()
// Updating core data from server and populating table view from core data
dispatch_async(dispatch_get_main_queue()) {
self.newsArray = [NewsItem]()
self.newslists = [[NewsItem]]()
self.getNewsFromServer()
self.getNewsFromCoreData()
self.tViewNews.reloadData()
}
As you can see the functions being run inside the async request are identical to those inside the refresh function.
But!, the refresh function functions properly, populating and reloading the table view. But the async request does absolutely nothing. The table view remains empty. But the functions are running, as tested with print statements.
Anyone know why this is the case?
EDIT - Added extra fucntions
Get Core Data
func getNewsFromCoreData() {
let temp = StaffCoreData()
temp.getAllNews()
newsDates = Dictionary<Int, [NewsItem]>()
for newsobj in temp.newsArray{
if var curdate = newsDates[toNewsItem(newsobj).age]{
curdate.append(toNewsItem(newsobj))
newsDates[toNewsItem(newsobj).age] = curdate
}else{
newsDates[toNewsItem(newsobj).age] = [toNewsItem(newsobj)]
}
}
for var i = 0; i<50; ++i{if let curdate = newsDates[i]{newslists.append(curdate)}}
Get Server Data
runs instance with following method:
func NewsCallReturnsWithSuccess(data: NSData) {
if let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) {
if let statusesArray = jsonObject as? NSArray{
newsArray.removeAll(keepCapacity: true)
for item in statusesArray {
let datacon : NSData = item.dataUsingEncoding(NSUTF8StringEncoding)!
let jsonObject : NSDictionary! = NSJSONSerialization.JSONObjectWithData(datacon, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary
let title : NSString = jsonObject.objectForKey("title") as! NSString
let content : NSString = jsonObject.objectForKey("content") as! NSString
let publishedby : NSString = jsonObject.objectForKey("publishedby") as! NSString
let modified : NSString = jsonObject.objectForKey("modified") as! NSString
let imageurl : NSString = jsonObject.objectForKey("imageurl") as! NSString
let category : NSString = jsonObject.objectForKey("category") as! NSString
let newsItem = NewsItem(title: title as String, content: content as String, category: category as String, imageURL: imageurl as String, publishedBy: publishedby as String, modified: modified as String)
newsArray.append(newsItem)
}
//add complete array to CoreData
let temp = StaffCoreData()
temp.addArrayOfNew(newsArray)
}
}
}
When you are using a different thread for loading your data, you have to return to the main thread when finished loading to update the UI.
Like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// load data
dispatch_async(dispatch_get_main_queue()) {
// update ui
}
}
UPDATE
Pls show us your getNewsFromCoreData() and getNewsFromServer() methods and maybe your cellForRowAtIndexPath, too.
override func viewDidLoad() {
super.viewDidLoad()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// load data
self.newsArray = [NewsItem]()
self.newslists = [[NewsItem]]()
self.getNewsFromServer()
self.getNewsFromCoreData()
dispatch_async(dispatch_get_main_queue()) {
// update ui
self.tViewNews.reloadData()
}
}
}

Resources