I made a ToDo list app where I enter a task in one view controller and it saves the data in array of another view controller and then adds it to a table. My only problem is that when I close the app, all my new task data is gone. Can anyone help?
I tried using UserDefaults but it didn't work quite well or I just didn't use it right.
This is the code from the view controller where I type in the new task:
#IBAction func addTaskButton(_ sender: Any)
{
if (textField.text != "") {
Task.append(textField.text!)
let task = UserDefaults.standard.set(Task, forKey: "XX")
textField.text = ""
textLabel.text = "SAVED!"
timer = Timer.scheduledTimer(timeInterval: 1.6, target: self, selector: #selector(AddTaskViewController.Hide), userInfo: nil, repeats: true)
textLabel.isHidden = false
}
}
This is the code from the view controller where I try to save the Data and place it in a table
var Task = ["Pray", "Code", "Work-Out"]
var School = ["Email Collin College","Email PQC","Create file for all my doccumensts"]
var ShoppingList = ["KD9 Black","RoshRun Black","Nike Huarache Black","White Shirt","Black Shirt", "Black Work Out pnats", "White Socks"]
var Prayer = ["School","Family","Drive & Grace"]
var WorkOut = ["Legs","Caffs","Push Ups","Arms","Chest Press","Squats", "Back"]
class add_Task_TableViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var TaskTableView: UITableView!
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return (Task.count)
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")
cell.textLabel?.text = Task[indexPath.row]
return(cell)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
if editingStyle == UITableViewCellEditingStyle.delete
{
Task.remove(at: indexPath.row)
TaskTableView.reloadData()
}
}
override func viewDidAppear(_ animated: Bool) {
TaskTableView.reloadData()
}
You can use SQLITE as a database where you maintain your draft messages or values that can be used later when you open app again.
You can use userDefaults to save your todo List. But what you are missing is you are storing correctly but you are not retriving stored array from the userDefaults.
To solve this just change your viewDidAppear method as below code snippet.
override func viewDidAppear(_ animated: Bool) {
Task = UserDefaults.standard.stringArray(forKey: "XX") ?? [String]()
TaskTableView.reloadData()
}
Related
I have been at this for a few days now, what I am trying to do is to be able to select 5 cells (previous populated by a realm database). Any help on this would be really appreciated.
Thanks Mark
The following is my current code as is relates to the Segue to pass the data. I think I have edited the code down to the key elements. Let me know if you need any other information.
Edits Made - based on the suggestion made below. I took a step back and approached the problem differently, and developed a solution. I have updated the code below to reflect my working solution. Hope this helps someone else in the future. Cheers Mark
Primary VC
import UIKit
import RealmSwift
class ExercisesSelectionTimed: UITableViewController{
var realm = try! Realm()
var exercises : Results<ExercisesModel>?
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//populates the Realm Data with the default list
exercises = realm.objects(ExercisesModel.self)
tableView.allowsMultipleSelection = true
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return exercises?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExercisesAddedTableViewCell", for: indexPath) as! ExercisesAddedTableViewCell
// get the specific exercise in the array
let exercise = exercises?[indexPath.row]
cell.exercisenameLabel.text = exercise?.name
cell.equipmentnameLabel.text = exercise?.equipment
return cell
}
private func registerTableViewCells() {
let textFieldCell = UINib(nibName: "ExercisesAddedTableViewCell",bundle: nil)
self.tableview.register(textFieldCell,forCellReuseIdentifier: "ExercisesAddedTableViewCell")
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch (timerSelected) {
case "FGB":
//User needs to select 5 exercises to populate the FGB timer on the next VC
if let selectedRows = tableView.indexPathsForSelectedRows {
//lets the user select 5 cells
if selectedRows.count == 5 {
performSegue(withIdentifier: K.FGB, sender: self)
}
}
default : print ("error")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch (timerSelected) {
case "FGB":
if let selectedRows = tableView.indexPathsForSelectedRows {
let selected = selectedRows.map{exercises?[$0.row]}
let destinationVC = segue.destination as! FBGTimerVCTable
destinationVC.selectedExercise = selected
}
default : print ("error")
}
}
}//last bracket in Class
The target for the data
import UIKit
import RealmSwift
class FBGTimerVCTable: HeartRateMonitorBrain{
var realm = try! Realm()
var selectedExercise = [ExercisesModel?]()
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
}//last bracket in Class
extension FBGTimerVCTable: UITableViewDataSource{
//setup tableview
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExercisesAddedTableViewCell", for: indexPath) as! ExercisesAddedTableViewCell
//get the specific 5 exercises selected in the ExercisesSelectionTimed VC
cell.exercisenameLabel.text = selectedExercise[indexPath.row]?.name
cell.equipmentnameLabel.text = selectedExercise[indexPath.row]?.equipment
return cell
}
private func registerTableViewCells() {
let textFieldCell = UINib(nibName: "ExercisesAddedTableViewCell",bundle: nil)
self.tableview.register(textFieldCell,forCellReuseIdentifier: "ExercisesAddedTableViewCell")
}
}//last bracket in Class Extension
Realm
import RealmSwift
class ExercisesModel : Object {
#objc dynamic var name : String = ""
#objc dynamic var equipment : String = ""
}
Iv been trying to add userDefaults to my code and trying to save for example a task so the next time i open the app i can still see the task .
didnt quit understood how userDefaults work.Maybe someone can help me?
taskList VC file:
#IBAction func doneButton_Tapped(_ sender: Any) {
var newTask = taskList()
newTask.name = taskNameField.text!
newTask.dueDate = dueDatePicker.date
newTask.dateCreated = Date()
taskArr.append(newTask)
didCreateTask = true
self.navigationController?.popViewController(animated: true)
}
mainVC file:
struct taskList {
var name: String!
var dateCreated: Date!
var dueDate:Date!
}
private let listIdentifier = "listIdentifier"
var taskArr: [taskList] = []
let defauls = UserDefaults.standard
class ViewController: UIViewController {
#IBOutlet var tableView:UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Tasks List"
tableView.delegate = self
tableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
#IBAction func newTaskButton_Tapped(_ sender: Any){
self.performSegue(withIdentifier: "newTaskSague", sender: nil)
}
}
extension ViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
extension ViewController:UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return taskArr.count}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: listIdentifier, for: indexPath) as! listCell
let row = indexPath.row
let formatter = DateFormatter()
cell.title?.text = taskArr[row].name
formatter.dateStyle = .medium
formatter.timeStyle = .short
cell.subTitle?.text = "Due: \(formatter.string(from: taskArr[row].dueDate))"
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete{
taskArr.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
}
With user defaults you can pretty much whatever you need but mind that is an internal database to small stuff as checking a boolean, an integer minimal data that you need as extra logic not as storage place, i you are planning to save a lot of entries of your Task model, you should consider to user CoreData.
What I suggest, is save an array of tasks in your UserDefaults (again if is not so much) and when you come from background, you need to check if your UserDefaults is empty, something like
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToForeground), name: Notification.Name.UIApplicationWillEnterForeground, object: nil)
}
func appMovedToForeground() {
print("come from background")
}
For objects array you will need to use archiver, you will need to add to that method of appMoveToForeground something like:
guard let dataTasks = UserDefaults.standard.object(forKey: "task") as? Data else { return nil }
if let tasks = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [Task.self], from: dataTasks){
if tasks.isNotEmpty() {
}
}
}
i still didnot get it how to implment this in my code .and im not trying to save big size of data just couple of them i dont need to use coreData
I'm currently working on a calendar within my application I'm writing using Swift. Although it works correctly in that data (event logs) can be entered and added to an array which will show up within a table view, once the application is closed down and removed from the background, when I re-open it, the event logs are gone.
Have I made any mistakes in my code?
import UIKit
import JTAppleCalendar
var event = [String]()
var userData = false
class CalendarViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var calendarView: JTAppleCalendarView!
#IBOutlet weak var monthAndYear: UILabel!
#IBOutlet weak var eventTable: UITableView!
override func viewDidAppear(_ animated: Bool) {
eventTable.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
configureCalendarView()
userData = UserDefaults.standard.bool(forKey: "userData")
if userData == true {
UserDefaults.standard.object(forKey: "event") //gets
}
else
{
event.append("NO USER DATA")
UserDefaults.standard.set(event, forKey: "event")
if event[0] == "NO USER DATA" {
event.remove(at: 0)
UserDefaults.standard.set(event, forKey: "event")
}
}
eventTable.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return event.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: nil)
cell.textLabel?.text = event[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete
{
event.remove(at: indexPath.row)
UserDefaults.standard.set(event, forKey: "event")
}
eventTable.reloadData()
}
Incase it's necessary here's the code which allows data to be entered in the array:
class AddEventViewController: UIViewController {
#IBOutlet weak var eventText: UITextField!
#IBAction func addEvent(_ sender: Any) {
userData = true
UserDefaults.standard.set(userData, forKey: "userData")
if eventText.text == ""
{
}
else
{
event.append(eventText.text!)
UserDefaults.standard.set(event, forKey: "event")
}
}
I've tried using the simulator and a real iOS devise along with re-installing Xcode, because I've read that it may be a problem with Xcode itself. I've also tried adding UserDefaults.standard.synchronize() all over the place, however, nothing seems to work.
The only line in your code that gets the value out of the UserDefaults is...
UserDefaults.standard.object(forKey: "event")
This doesn't actually do anything with that value. It gets it... and then throws it away.
This will make it look like it isn't being saved.
Change it to ...
event = UserDefaults.standard.object(forKey: "event")
You need to call synchronize() after you call set, in order to save the changes
UserDefaults.standard.synchronize()
hi i am new to this swift and coding in general, i have created a tiny app that the user can take a photo form the photo library and type in a title and a location of the image and upload it to the tableview, but i have some problem and i dont really know what is the problem because iam new to this but there is no error in the code or warnings there is only problem really when the user press Done/Uploadbutton nothing happens, my tableview dont update. Iam not sure if my tableview is not updating or if i even am sending the data over to my array...
so how can i see what data is in my array and if iam even sending the data the right way?
Here is how iam inserting the datato the array
#IBAction func doneEditing(_ sender: Any) {
insertNewActy()
}
func insertNewActy(){
addTitles = addTitle.text!
addLocations = addLocation.text!
newImages = newImage.image!
let element = MyCellRows(image: newImages,
title: addTitles,
location: addLocations)
myCellRows.insert(element, at: 0)
}
and here is the code of my array: is my Array even set up the right way?
import UIKit
var myCellRows: [MyCellRows] = []
class ActyViewController: UIViewController {
#IBOutlet weak var myTableView: UITableView!
func createMyCellArray() -> [MyCellRows] {
var myCells: [MyCellRows] = []
let dog = MyCellRows(image: #imageLiteral(resourceName: "Dog"),
title: "Dog",
location: "America")
let cat = MyCellRows(image: #imageLiteral(resourceName: "Cat"),
title: "Cat",
location: "Sweden")
let rabbit = MyCellRows(image: #imageLiteral(resourceName: "Rabbit"),
title: "Rabbit",
location: "Germany")
let tiger = MyCellRows(image: #imageLiteral(resourceName: "Tiger"),
title: "Tiger",
location: "Africa")
myCells.append(dog)
myCells.append(cat)
myCells.append(rabbit)
myCells.append(tiger)
return myCells
}
override func viewDidLoad() {
super.viewDidLoad()
myCellRows = createMyCellArray()
// Do any additional setup after loading the view.
}
}
this is my tableView code
extension ActyViewController: UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myCellRows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myActy = myCellRows[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! ActyCellTableViewCell
cell.setActy(actys: myActy)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200.0
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete{
myCellRows.remove(at: indexPath.row)
myTableView.reloadData()
}
}
override func viewDidAppear(_ animated: Bool){
super.viewDidAppear(true)
myTableView.reloadData()
}
}
I think you have confused your concepts a bit but you explained your problem well so you're off to a good start. I've rewritten parts of your code and left out the image part so you have a working table view before you continue:
struct Animal {
var title: String
var location: String
}
class ActyViewController: UIViewController, UITableViewDataSource {
var animals: [Animal] = [
Animal(title: "Dog", location: "America"),
Animal(title: "Cat", location: "Sweden"),
Animal(title: "Rabbit", location: "Germany"),
Animal(title: "Tiger", location: "Africa"),
]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return animals.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "AnimalCell"
let cell: UITableViewCell = {
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) else {
return UITableViewCell(style:.subtitle, reuseIdentifier: cellIdentifier)
}
return cell
}()
cell.textLabel?.text = animals[indexPath.row].title
cell.detailTextLabel?.text = animals[indexPath.row].location
return cell
}
}
As you can see above, you should separate your data from how you present your data, so I've made an array of Animal that keeps your data. Your tableview should have a UITableViewDataSource and will ask this data source for data using the two functions you can see.
Now you can add an animal to your list of animals and refresh the table
animals.append(Animal(title: "Elephant", location: "India"))
tableView.reloadData()
Hope that helps to get you going.
If your only problem is that your tableView isn't refreshing after you add the new data, make sure you call reloadData() after you update your array. If your insertNewActy() function is in the same ViewController as the rest of your code you should be able to change it to:
func insertNewActy(){
addTitles = addTitle.text!
addLocations = addLocation.text!
newImages = newImage.image!
let element = MyCellRows(image: newImages,
title: addTitles,
location: addLocations)
myCellRows.insert(element, at: 0)
myTableView.reloadData()
}
I have the following class:
import UIKit
import CloudKit
class FirstViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var listTableView: UITableView!
var list: TLListModel!
var specificList: CKRecord!
override func viewDidLoad()
{
super.viewDidLoad()
let myContainer = CKContainer.default()
list = TLListModel(container: myContainer, viewController: self)
if(listTableView != nil){
listTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("number of items: %i", list.lists.count)
return list.lists.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = listTableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
let list: CKRecord = self.list.lists[(indexPath as NSIndexPath).row]
cell.textLabel?.text = list.value(forKey: "ListName") as? String
cell.textLabel?.font = UIFont (name: "Slim Joe", size: 20)
cell.accessoryType = .disclosureIndicator
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("This object has been selected")
print(self.list.lists[(indexPath as NSIndexPath).row])
specificList = self.list.lists[(indexPath as NSIndexPath).row]
performSegue(withIdentifier: "TLSpecificListSegue", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TLSpecificListSegue"{
if let destinationVC = segue.destination as? TLSpecificListViewController{
destinationVC.listObject = specificList
}
}
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
{
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
if editingStyle == .delete
{
let cloudkit = TLCloudKitHelper()
cloudkit.deleteListItem(self.list.lists[(indexPath as NSIndexPath).row], callback: { (listName) in
TLAlertHelper.notifyUser("List Deleted", message: NSString(format: "List for %# successfully deleted", listName) as String, sender: self)
let myContainer = CKContainer.default()
self.list = TLListModel(container: myContainer, viewController: self)
DispatchQueue.main.async {
self.listTableView.reloadData()
}
})
}
}
}
When I call it from another view controller using the following method:
#IBAction func createListAction(_ sender: AnyObject) {
let cloudkit = TLCloudKitHelper()
let listArray = createListFromTextField(textInputArea.text)
if(!(listNameTextField.text?.isEmpty)!){
cloudkit.createList(listNameTextField.text!) { (response) in
let listId = response
if (!listArray.isEmpty){
for item in listArray{
cloudkit.saveItemRecord(item, listId: listId, recordName: response)
}
}
let fvc: FirstViewController = FirstViewController()
DispatchQueue.main.async {
self.present(fvc, animated: true, completion: nil)
}
}
}else{
TLAlertHelper.notifyUser("Give the list a name", message: "You need to give you list a name...", sender:self)
}
}
I get an error saying fatal error: unexpectedly found nil while unwrapping an Optional value
I don't understand why I am getting this error. I've tried looking at the answers here: Simple UITableView in Swift - unexpectedly found nil but I none of those answers helped. Can someone tell me why this this crashing and how I can fix it?
The problem is this line:
let fvc: FirstViewController = FirstViewController()
This creates a blank FirstViewController instance — one completely unconnected with the interface you designed in the storyboard. Its view is empty. It has no table view in it. Therefore, since there is no table view, there is no outlet connection from any table view, and listTableView remains nil.
What you want to do is get the FirstViewController instance from the storyboard, the one whose interface you have already designed in the storyboard. You can do that by talking to the storyboard and using the FirstViewController's identifier, i.e., call instantiateViewController(withIdentifier:). (You might have to give the FirstViewController in the storyboard an identifier for this purpose.)
EDIT This is such a common mistake that I've written a blog post about it: http://www.programmingios.net/dont-make-a-new-instance-by-mistake/