Saving Swift TableView data using NSUSerDefaults - swift

I have a tableview in swift. Each cell has a label with different text. When a cell is selected it passes the text to a label on another view controller. I can save this data using a button (Save Data) and load the saved data with another button (Load Data).
My problem is I would ideally like to not use buttons, but have the data loaded automatically when the view loads. However, when I place the NSUSerDefaults code within viewDidLoad (), it saves the data but I am no longer able to change the selection i.e. it loads the first selection permanently. I have posted my code below for the destination ViewController and for the TableViewController.
ViewController
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var detailLabel: UILabel!
var passedValue: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
detailLabel.text = passedValue
}
#IBAction func saveDataClicked(sender: AnyObject) {
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(self.detailLabel.text, forKey: "optionValue")
}
#IBAction func loadDataClicked(sender: AnyObject) {
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
if let optionValueIsNotNill = defaults.objectForKey("optionValue") as? String {
self.detailLabel.text = defaults.objectForKey("optionValue") as! String
}
}
}
TableViewController
import UIKit
class TableViewController: UITableViewController {
var valueToPass:String!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow;
let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as UITableViewCell!;
valueToPass = currentCell.textLabel!.text
performSegueWithIdentifier("showDetailView", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if segue.identifier == "showDetailView" {
// initialize new view controller and cast it as your view controller
var viewController = segue.destinationViewController as! DetailViewController
// your new view controller should have property that will store passed value
viewController.passedValue = valueToPass
}
}
}

if You want to save selected Data to NSUSerDefaults You should write your saving code in didSelectRowAtIndexPath like this
if you have a function to save the data in NSUserDefaults
save(DataArray[indexPath.row])
then it will be saved , but i think if your data used when the application is running only , i think the best option to use is static instance .

Related

Swift: Update Layout and Content of ViewController when dismissing presented ViewController

I have a UIView which displays some information such as a user's Name and more, including a list of objects that all get pulled from my database. This works fine.
However, I now have a ViewController that gets presented on top of the current ViewController. In this presented ViewController, I am adding Data to my Database. When dismissing that view, I want the original ViewController to update all of its content to be up to date.
Right now, all my views are getting layedout in ViewDidLoad, meaning that they only really get loaded once and don't reload later on. I have managed to update Layout by calling self.view.layoutIfNeeded(), but if I understand correctly, this only updates constraint. Of course, I could call a new init of my original view controller. This would make it reload, but I would like to avoid that.
Another Idea I had was to set up all my content in the ViewWillAppear, which should maybe then update anytime my view controller is about to be visible. However, I don't know how to go about doing this. Can I just move all my setup code to viewWillAppear? Does this have any disadvantages?
TLDR: Is there a way to update a stackview with new elements without having to reload the full ViewController over ViewWillAppear?
The UITableView element works very smoothly with database data. If you fetch the data from your database inside viewDidLoad in your first view controller, and store it in an array, the UITableView (if you set up its dataSource correctly) will automatically populate the table with the new values from the second view controller. With this method, there is no need to use ViewWillAppear at all.
It sounds like as of now, you're using Views (inside a VStack)? to display individual objects from the database. If you want to keep whatever custom style/layout you're using with your views, this can be done by defining a custom subclass of UITableViewCell and selecting the "Also create XIB file" option. The XIB file lets you customize how the cells in your UITableView look.
Here is a simple example to show the database values in the first view controller automatically updating. I didn't include the custom XIB file (these are all default UITableViewCells), to keep it streamlined.
FIRST VIEW CONTROLLER
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var dataTable: UITableView!
var tableRows: [DataItem] = []
func loadData() {
let request: NSFetchRequest<DataItem> = DataItem.fetchRequest()
do {
tableRows = try Global_Context.fetch(request)
} catch {
print("Error loading data: \(error)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
dataTable.dataSource = self
loadData()
}
#IBAction func goForward(_ sender: UIButton) {
self.performSegue(withIdentifier: "toSecond", sender: self)
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableRows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dataTableCell", for: indexPath)
cell.textLabel?.text = tableRows[indexPath.row].name
return cell
}
}
let Global_Context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveContext () {
if Global_Context.hasChanges {
do {
try Global_Context.save()
} catch {
let nserror = error as NSError
print("Error saving database context: \(nserror), \(nserror.userInfo)")
}
}
}
SECOND VIEW CONTROLLER:
import UIKit
import CoreData
class AddViewController: UIViewController {
#IBOutlet weak var itemEntry: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
itemEntry.delegate = self
}
#IBAction func addNewItem(_ sender: UIButton) {
let newDataItem = DataItem(context: Global_Context)
newDataItem.name = itemEntry.text
saveContext()
}
#IBAction func goBack(_ sender: UIButton) {
self.performSegue(withIdentifier: "toFirst", sender: self)
}
}
extension AddViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.endEditing(true)
return true
}
}
Main.storyboard:
Once you set up your view controller as a UITableViewDataSource (as in the example code), the table view should make things simpler by eliminating any need to manually manage individual Views.
Is this the functionality you were looking for? (Note about the example: it was set up in Xcode with "Use Core Data" enabled.)
Here is a link to the official documentation:
https://developer.apple.com/documentation/uikit/uitableview

Trouble with segue/unsegue when making a list app

I'm new to coding, so please bear with me. I was following an online tutorial that worked with plists to make a habit list app. I have a table view controller that shows a list of habits and a segue that presents modally a view controller that has text fields to add a habit.
enter image description here
Every time it runs, nothing happens when I click on the "save" and "cancel" buttons. I realize this is a vague question as it doesn't pinpoint to a specific issue, but I am really struggling with fixing this issue and would really appreciate if someone proofreads the code. The app builds and runs with no warnings.
This is the table view controller that shows the habits:
class TableViewController: UITableViewController {
//MARK: Properties
var habits = [Habit]()
//MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return habits.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
fatalError("The dequeued cell is not an instance of ViewController.")
}
// Fetches the appropriate habit for the data source layout.
let habit = habits[indexPath.row]
cell.textLabel?.text = habit.mainGoal
cell.detailTextLabel?.text = habit.microGoal
return cell
}
#IBAction func unwindToHabitList(sender: UIStoryboardSegue) {
if let source = sender.source as?ViewController, let habit = source.habit {
//add a new habit
let newIndexPath = IndexPath(row: habits.count, section: 0)
habits.append(habit)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
}
This is the view controller that adds a habit:
class ViewController: UIViewController, UITextFieldDelegate, UINavigationControllerDelegate {
#IBOutlet weak var saveButton: UIBarButtonItem!
#IBOutlet weak var mainGoalTextField: UITextField!
#IBOutlet weak var microGoalTextField: UITextField!
var habit: Habit?
//method for configuring controller before presenting
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
//configure this destination view controller only when save button is pressed
guard let button = sender as? UIBarButtonItem, button === saveButton else {
os_log("save button was not pressed, cancelling", log: OSLog.default, type: .debug)
return
}
let mainGoal = mainGoalTextField.text ?? ""
let microGoal = microGoalTextField.text ?? ""
//set the habit to be passed on to tableViewController after the unwind segue
habit = Habit(mainGoal: mainGoal, microGoal: microGoal)
}
#IBAction func cancel(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
let isPresentingInAddHabitMode = presentingViewController is UINavigationController
if isPresentingInAddHabitMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The ViewController is not inside a navigation controller.")
}
}
I appreciate any and all help in advance!
STORYBOARD CONNECTIONS:
TABLEVIEW CONTROLLER CONNECTIONS
ADD HABIT VIEW CONTROLLER CONNECTIONS

Passing TableView Data to DetailViewController in swift

I have a simple tableview loading with array of colours(each cell with different colour).i am trying to pass the background colour of detailViewController when user press the colour array cell from colourTableviewcontroller(simply i want to pass the cell colour as a detailview background colour)
my code as follows.....
import UIKit
class colourTableviewcontroller: UITableViewController {
#IBOutlet weak var colorTableView: UITableView!
let colors = [UIColor.redColor(), UIColor.blueColor(),UIColor.greenColor(), UIColor.orangeColor(),UIColor.purpleColor(),UIColor.yellowColor(),UIColor.cyanColor(),UIColor.darkGrayColor(),UIColor.blackColor(), UIColor.brownColor(),UIColor.grayColor(), UIColor.magentaColor(), UIColor.whiteColor()]
var colourData = UIColor()
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.
}
override func viewDidAppear(animated: Bool) {
colorTableView.separatorColor = UIColor.clearColor()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return colors.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("colourCell", forIndexPath: indexPath) as UITableViewCell
cell.backgroundColor = self.colors[indexPath.row % self.colors.count]
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
colourData = self.colors[indexPath.row]
performSegueWithIdentifier("colourSegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "colourSegue") {
let viewController = segue.destinationViewController as! DetailViewController
viewController.bc?.backgroundColor = colourData }
}}
// My DetailViewController as follows...
import UIKit
class DetailViewController: UIViewController {
#IBOutlet var bc: UIView!
override func viewDidLoad() {
self.bc.backgroundColor = UIView.appearance().backgroundColor
}
when i run the code my DetailViewController background colour changing to dark text colour when i press the colour array.....
i don't know what i am missing or am i doing wrong approach/logic....
Thanks in Advance.....
First of all in ColourTableviewcontroller connect the segue to the table view cell rather than to the view controller and delete the method didSelectRowAtIndexPath
Replace prepareForSegue with
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "colourSegue" {
let viewController = segue.destinationViewController as! DetailViewController
let selectedIndexPath = tableView.indexPathForCell(sender as! UITableViewCell)
viewController.color = colors[selectedIndexPath.row]
}
}
In DetailViewController declare a variable color
var color : UIColor!
And set the color of bc in viewDidLoad()
bc.backgroundColor = color
The reason for the additional variable is that the IBOutlets in DetailViewController don't exist yet while prepareForSegue is executed.
You aren't passing the UIColor and assigning it in the next View Controller
You just need to do few modifications to your existing code
1) Add var recievedColor : UIColor! in the DetailViewController
2) In your prepareForSegue replace viewController.bc?.backgroundColor = colourData with viewController.recievedColor = colourData
3) In your DetailViewController, in viewDidLoad() function, replace self.bc.backgroundColor = UIView.appearance().backgroundColor with self.bc.backgroundColor = recievedColor
And there you go!
You need to pass the color, then assign the color to the view on the viewDidLoad(). You are assigning a variable to a view that hasn't been initialized yet.
in your detail class do as follow:
class DetailViewController: UIViewController {
#IBOutlet var bc: UIView!
var selectedBackgroundColor: UIColor? // this should be set on the prepareForSegue method.
override func viewDidLoad() {
super.viewDidLoad()
if let selectedBackgroundColor = selectedBackgroundColor {
bc.backgroundColor = selectedBackgroundColor
}
}
Hope this helps!

UI TableView cell opening a new view with multiple data using coredata in swift

as a beginner in iOS and Swift, I have a project that has to have a tableview with multiple cells, in which every cell contains several data types. i.e. Strings, dates etc., where in one view controller, there is the table view for viewing the cells, the second view controller is for creating a cell and entering the data, and the third view is for displaying the same data when clicking the cell. I've decided to store all of that using coredata since I was told it's most efficient and simple for beginners. I've used several tutorials on this matter but none of them handle this type of problem I have. Best example is how the Contact list works on iOS.
The code I've done so far is this:
var titleCellList = [NSManagedObject]()
var infoCellList = [NSManagedObject]()
class CellsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var cellsTableView: UITableView!
//MARK: Default Functions
override func viewDidLoad() {
super.viewDidLoad()
title = "\"Lists\""
cellsTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
// Do any additional setup after loading the view.
}
// MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return TitleCellList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
let cellTitle = titleCellList[indexPath.row]
cell.textLabel!.text = cellTitle.valueForKey("title") as? String
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(animated: Bool) {
cellsTableView.reloadData()
}
//MARK: Storing CoreData
func saveName(name: String) {
//1
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let entity = NSEntityDescription.entityForName("Data", inManagedObjectContext: managedContext)
let title = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext)
//3
title.setValue(name, forKey: "title")
//4
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
//5
titleCellList.append(title)
}
//MARK: Fetching CoreData
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//1
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let fetchRequest = NSFetchRequest(entityName:"Data")
//3
var error: NSError?
let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObject]
if let results = fetchedResults {
titleCellList = results
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
// MARK: Table Editing Methods
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
context.deleteObject(titleCellList[indexPath.row] as NSManagedObject)
titleCellList.removeAtIndex(indexPath.row)
context.save(nil)
cellsTableView.reloadData()
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let row = indexPath.row
println("Row: \(row)")
println(titleCellList[row])
performSegueWithIdentifier("checkCellSegue", sender: self)
}
Second View Controller (the one for creating a cell with data)
class AddNewViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var titleTextField: UITextField!
#IBOutlet var shortInfoTextView: UITextView!
//MARK: Default Functions
override func viewDidLoad() {
super.viewDidLoad()
self.titleTextField.delegate = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.view.endEditing(true)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
#IBAction func addDataButtonPressed(sender: UIButton) {
if titleTextField.text != "" {
CellsViewController().saveName(titleTextField.text)
titleTextField.text = ""
shortInfoTextView.text = ""
println("New title Added!")
}else {
println("No empty titles allowed!")
}
}
Now, most of this code is from a tutorial, and when I tried adding other data entity's, it didn't work. In the datamodel I currently have only 1 entity named "Data" which contains 4 models. So, to sum it up, I need to store 4 data models in one entity and load them on a different view controller when clicking on a cell which of course, has a title that the user wrote. And just to note, I've spent hours searching online for an answer so this is my last line so to say.
Any help would be greatly appreciated.
So, here is the one approach I used on this little issue. I basically just pass arguments with the prepareForSegue method, and inside of it I just pass the data I want to use in the other class/VC.
The Code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
// Setter for Second VC, destination path --> var declarations in Second VC
if segue.identifier == "checkCellSegue" {
let destination = segue.destinationViewController as! SecondViewController
if let indexPath = self.tableView?.indexPathForCell(sender as! UITableViewCell) {
let object = fetchedResultsController?.objectAtIndexPath(indexPath) as? Data
destination.cellTitle = object?.cellTitle
destination.textViewInfo = object?.textViewInfo
destination.timerValue = object?.timerValue
}
}
So, first we declare the destination which is the name of our Second VC or whatever you named it. Then, since I am accessing data trough a TableView cell we need to fetch my CoreData Entity with the indexPath. After that the final declaration is the Model Class which has all the data values from the entity, which will work like a singleton.
destination.cellTitle // --> in the 2.nd VC we declared a new var called cellTitle, var cellTitle:String
object?.cellTitle // --> in the model class "Data.swift" the declaration is #NSManaged var cellTitle:String
So, thats it. I am still a little newbie on iOS so if there are any mistakes, just say so.

Passing data thru a Swift segue works every other time

I have a tableview which reads and RSS feed of episodic radio shows. I want the playlist for the selected show to pass to a second controller for viewing in a textview when a cell is selected. I am using a segue and it works when I select the same cell twice (every other time). I have searched everywhere without success and its driving me nuts! Please help. Here is my code
// Only grab the data at the selected cell
//
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Load the variable to hold whats in the row
currentList = feeds.objectAtIndex(indexPath.row).objectForKey("itunes:summary") as NSString
// Load the row number
myRow = (indexPath.row)
}
// Pass the data thru the segue
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!) {
if (segue.identifier == "mySegue") {
// var vc = segue.destinationViewController as secondViewController
// vc.toPass = currentList
// println(vc.toPass)
let vc = segue.destinationViewController as secondViewController
let indexPath = self.tableView.indexPathForSelectedRow
vc.toPass = currentList
}
}
}
Here is the code from my second view controller
import UIKit
class secondViewController: UIViewController {
// Create a property to accept the data
#IBOutlet weak var textPlayList: UITextView!
// Create a variable to store the data
var toPass:String!
override func viewDidLoad() {
super.viewDidLoad()
textPlayList.text = toPass
textPlayList.textColor = UIColor .whiteColor()
textPlayList.font = UIFont .boldSystemFontOfSize(10)
}
}
The problem is that prepareForSegue happens before didSelectRowAtIndexPath so your currentList variable is set up too late for be useful in prepareForSegue.
To fix this, move this code:
// Load the variable to hold whats in the row
currentList = feeds.objectAtIndex(indexPath.row).objectForKey("itunes:summary") as NSString
to prepareForSegue:
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!) {
if (segue.identifier == "mySegue") {
let vc = segue.destinationViewController as secondViewController
if let indexPath = self.tableView.indexPathForSelectedRow() {
let currentList = feeds.objectAtIndex(indexPath.row).objectForKey("itunes:summary") as NSString
vc.toPass = currentList
}
}
}
In general, you don't need didSelectRowAtIndexPath if you are using segues because prepareForSegue is where you set up the transition.