Swift - Save cell selection in NSUserDefaults - ios

Currently, I have a to-do list app. When selecting a row, the alpha dims, indicating that the task is selected or "completed". I have been searching vigorously on here how to save the selected cell state to NSUserDefaults.
My ViewController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var defaults = NSUserDefaults.standardUserDefaults()
#IBOutlet weak var toDoListTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
if defaults.objectForKey("toDoList") != nil {
toDoList = defaults.objectForKey("toDoList") as [String]
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return toDoList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedCell = tableView.cellForRowAtIndexPath(indexPath)!
selectedCell.contentView.alpha = 0.3
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
var deselectedCell = tableView.cellForRowAtIndexPath(indexPath)!
deselectedCell.contentView.alpha = 1.0
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
toDoList.removeAtIndex(indexPath.row)
toDoListTable.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Left)
defaults.setObject(toDoList, forKey: "toDoList")
}
}
override func viewDidAppear(animated: Bool) {
toDoListTable.reloadData()
}
}
I believe I'm just having an issue calling it/trying to figure out where to call it at. Any help would be greatly appreciated.
EDIT** What I have right now saves the content stored from a UITextField.

Is the problem that it doesn't save?
You can try calling synchronize to save it disk immediately to see if that makes a difference, although it shouldn't be required.
NSUserDefaults.standardUserDefaults().setObject(indexPath.row, forKey: "selection")
NSUserDefaults.standardUserDefaults().synchronize()

NSIndexPath's row/item property is an NSInteger. This is not an object. Use
setInteger(_:forKey:) instead.
#Van Du Tan's answer for defaults.setValue(indexPath.row, forKey: "selection") may not work because you have to encapsulate the row inside an NSValue object.

First off, I'm going to assume you have a line declaring NSUserDefaults:
let defaults = NSUserDefaults.standardUserDefaults()
I believe what your trying to do is store the alpha (assuming state) with respect to the row your selecting. In such a case
let key = "myTableViewWithRowOf\(indexpath.row)"
defaults.setFloat(selectedCell.contentView.alpha, forKey: key)
You can then retrieve this data by using the floatForKey method.
Ideally though you might want to end up with an NSArray associated with your tableView and store that using defaults.setObject(...). That way when your app loads you can call the whole array and loop through it restoring the "states".

Related

How to permanently delete a row from table view in Swift?

I tried to delete the rows by using the delete code but the row reappear everytime. I want to permanently delete any particular row.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var countries = ["India","Canada", "USA","Russia","Dubai"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = countries[indexPath.row]
return cell
}
//to enable delete action by swiping left
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete{
countries.remove(at: indexPath.row)
tableView.reloadData()
}
}
#IBOutlet weak var userTxt: UITextField!
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
let obj = UserDefaults.standard.object(forKey: "userKey") as? String
if let userName = obj {
userTxt.text = userName
}
}
#IBAction func savePressed(_ sender: UIButton) {
//we use this to save data
UserDefaults.standard.set(userTxt.text, forKey: "userKey")
}
#IBAction func deletePressed(_ sender: UIButton) {
UserDefaults.standard.removeObject(forKey: "userKey")
}
}
You have to save the modified array of countries to persist the changes you have made to it. Here's what you need to do:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
//...
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
countries.remove(at: indexPath.row)
UserDefaults.standard.set(countries, forKey: "countries")
tableView.reloadData()
}
}
//...
override func viewDidLoad() {
super.viewDidLoad()
if let array = UserDefaults.standard.array(forKey: "countries") as? [String] {
countries = array
} else {
countries = ["India","Canada", "USA","Russia","Dubai"]
}
//...
}
}
Your implementation of delete does not persist the changes you make from delete as it uses a variable that’s initialized every time with data you hardcoded, hence array restores its elements to ones you specified every time you check back. So instead of hard coding values to an array to fill your tableView, consider using a persistent storage like a database or UserDefaults.

Two table views with data and want to delete a index from one specific tableview. Do I have to delete the other array also?

Ok so I am geting memory errors from this which is annoying ,since it isn't something like a simple syntax error. Well the thing I am doing to understanding how to manipulate data
What I am trying to do is to allow it to delete from another view ,but the thing still crashes. I will share the two table views I got going on.
This is the one that has problems ,since it is in the second view controller
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FeedCommands.commentSection.count
}
//allows us to delete the code
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// this is called from a static variable class function
FeedCommands.RemoveComment(atIndex: indexPath.row)
CommentFeed.deleteRows(at: [indexPath], with: .fade)
}
}
Now this is the one that works perfectly fine
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FeedCommands.feedArray.count
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
FeedCommands.feedArray.remove(at: indexPath.row)
TabView.deleteRows(at: [indexPath], with: .fade)
}
}
If I ran the problem one it exits with a memory error, I am thinking that it might have to deal with the arrays and might be the actual cause. I searched and there doesn't really seem to be anything that covers this case when doing multiple tableviews.
Here is the class I am calling the arrays from
static var commentSection: Array<String> = []
class func AddToComment(newElement: String){
FeedCommands.commentSection.append(newElement)
}
class func RemoveComment (atIndex: Int){
FeedCommands.commentSection.remove(at: atIndex)
}
static var QuestionToComment: Array<String> = []
class func AddQuestionToComment(newElement: String){
FeedCommands.QuestionToComment.append(newElement)
}
class func RemoveQuestionToComment (atIndex: Int){
FeedCommands.QuestionToComment.remove(at: atIndex)
}
static var feedArray: Array<String> = []
class func AddToFeed (newElement: String){
FeedCommands.feedArray.append(newElement)
}
class func Remove (atIndex: Int){
FeedCommands.feedArray.remove(at: atIndex)
}
If it needs more details please let me know.
Edit: Due to a request
this is the comment section file
import UIKit
class Comments: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var Question: UITextView!
#IBOutlet weak var ReplyTextField: UITextField!
#IBOutlet weak var CommentFeed: UITableView!
#IBAction func SubmitReply(_ sender: UIButton) {
CommentFeed.reloadData()
if ReplyTextField.text == nil {
}
else{
FeedCommands.AddToComment(newElement: ReplyTextField.text!)
ReplyTextField.text = ""
}
// dismiss(animated: true, completion: nil)
// ReplyTextField.placeholder = "Comment"
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//this is what we use to get the question to
//appear in the comment section
Question.text = ""
for Section in FeedCommands.QuestionToComment{
Question.text = "\(Section)"
CommentFeed.reloadData()
}
}
//adding the table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FeedCommands.commentSection.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = CommentFeed.dequeueReusableCell(withIdentifier: "Com", for: indexPath)
cell.textLabel?.text = "#\(Question.text!)__ \(FeedCommands.commentSection[indexPath.row])"
return cell
}
//allows us to delete the code
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
FeedCommands.commentSection.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade) // crashes here
}
}
}
You are reloading the table view data before appending a new element to your table view instead of reloading it after appending the reply. Thats why you needed to press your button twice to make it appear. What you should actually do to fix your submit reply action is to insert a new row at the last index (count - 1) of your table view every time the SubmitReply button is pressed:
#IBAction func SubmitReply(_ sender: UIButton) {
if !ReplyTextField.text!.isEmpty{
FeedCommands.AddToComment(newElement: ReplyTextField.text!)
ReplyTextField.text = ""
CommentFeed.insertRows(at: [IndexPath(row: FeedCommands.commentSection.count-1, section: 0)], with: .automatic)
}
}

Swift about view does not confirm to protocol "UITableViewDataSource"

class showPageViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var records : [Record] = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return records.count
}
func tableview(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell()
}
func tableView(_ tableVoew: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete{
let record = records[indexPath.row]
context.delete(record)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do{
records = try context.fetch(Record.fetchRequest())
} catch{
print("Failed")
}
}
}
override func viewWillAppear(_ animated: Bool) {
getData()
tableView.reloadData()
}
func getData(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do{
records = try context.fetch(Record.fetchRequest())
} catch{
print("123")
}
}
}
This is the code of my program, I am trying to make a to-do list app, but the error does not confirm to protocol "UITableViewDataSource" always appear, I try to find the solution, but nothing suitable for me, is anyone can help me? thanks
You have a mix of Swift 2 and Swift 3 methods in your code. Your cellForRow... method is using the newer Swift 3 signature. You need to use the older Swift 2 compatible version.
I believe it is:
func tableview(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
Check all of the other table view data source and delegate methods and make sure they all use the Swift 2 compatible signatures and not the newer Swift 3 signatures.
Yes, You are mixing swift 2 and swift 3. Your methods should be like this:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
// Configure the cell...
return cell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
You have done several spelling mistakes in those methods, Please check above methods with yours

Swift fatal error: array index out of range

I'm making a to-do list app but when I try to delete something from my list, xcode is giving me an error that says "fatal error: array index out of range". Can someone tell me what I'm doing wrong with my array that is causing this to happen?
import UIKit
class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
cell.textLabel?.text = eventList[indexPath.row]
return cell
}
override func viewWillAppear(animated: Bool) {
if var storedEventList : AnyObject = NSUserDefaults.standardUserDefaults().objectForKey("EventList") {
eventList = []
for var i = 0; i < storedEventList.count; ++i {
eventList.append(storedEventList[i] as NSString)
}
}
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if(editingStyle == UITableViewCellEditingStyle.Delete) {
eventList.removeAtIndex(indexPath.row)
NSUserDefaults.standardUserDefaults().setObject(eventList, forKey: "EventList")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
}
A breakpoint saying EXC_BAD_INSTRUCTION is being created at the eventList.removeAtIndex(indexPath.row)as well.
It is not sufficient to remove the item from the data source array.
You also have to tell the table view that the row is deleted:
if editingStyle == .Delete {
eventList.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
// ...
}
Otherwise the table view will call the data source methods for the original
number of rows, causing the out of range error.
Alternatively, you can call tableView.reloadData() when modifying the data source, but the above method
gives a nicer animation.
It means you are trying to access an index,indexPath.row, that exceed the eventList range. To fix this issue try:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if(editingStyle == .Delete && indexPath.row < eventList.count) {
eventList.removeAtIndex(indexPath.row)
tableView.reloadData()
NSUserDefaults.standardUserDefaults().setObject(eventList, forKey: "EventList")
NSUserDefaults.standardUserDefaults().synchronize()
}
}

Connect UITableView cell to another UITableView

I am stucked with my project (written in Swift)
I have a UITableView, with a string which provide ten categories in the table.
The thing that I wanna do, is to select one of these categories touching it, and open a secondTableView with other categories. And, when you press on the secondTableView, open the description of that category.
This is my code, this works to have the first UITableView, but when touching nothing happens.
Now I'd like, for example, when I click "Most important places", it open another tableView with, for example, Empire State Building, Statue of Liberty, and when touching Empire State Building, open a Description page.
import UIKit
class Categories: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var MainCategories: [String] = ["Most important places", "Monuments", "Nature", "Churches", "Museums", "Streets", "Zones", "Weather", "Events", "Favourites"]
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.MainCategories.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel?.text = self.MainCategories[indexPath.row]
return cell
}
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
}
}
What can I do? And in the storyboard?
Thanks you very much!
So far you have done nothing in didSelectRowAtIndexPath for the second UITableView, it would be easy for you to make use of another viewcontroller which contains the second uitableview and then a third viewcontroller for details.
But if you still want to have 2 uitableviews within same viewcontroller then please follow these steps to achieve the required result.
add another tableview and set its datasource and delegate
you have to add an if-else condition to differntiate between 2 tablieview in delegate and datasource methods, e.g. numberOfRowsInSection, cellForRowAtIndexPath, didSelectRowAtIndexPath, below is an example of the code if you use 2 uitableviews in one uiviewcontroller. you have to do this if-else for other delegate and datasource mehtos as well.
func tableView(tableView: UITableView, numberOfRowsInSection section:Int)->Int {
if tableview == self.tableView {
return self.MainCategories.count
}
else if tableView == subCategoryTableView {
return self.SubCategories.count
}
}
Hope it helps!
I have changed your code, hope this helps
import UIKit
class Categories: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
**var selectedCategoryId = -1**
var MainCategories: [String] = ["Most important places", "Monuments", "Nature", "Churches", "Museums", "Streets", "Zones", "Weather", "Events", "Favourites"]
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.MainCategories.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel?.text = self.MainCategories[indexPath.row]
return cell
}
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
**selectedCategoryId = indexPath.row**
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let vc = segue.destinationViewController as YOUR_SECOND_VIEW_CONTROLLER
vc.catId = selectedCategoryId
}
}

Resources