I am trying to create a ToDoList in swift UI and my deleteTask function is not working properly. I tried a lot of things and none of them worked.
import UIKit
class ViewController: UIViewController {
#IBOutlet var tableView: UITableView!
var toDoTasks = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = "SAVED TASKS"
tableView.delegate = self
tableView.dataSource = self
// setup
if !UserDefaults().bool(forKey: "setup")
{
UserDefaults().set(true, forKey: "setup")
UserDefaults().set(0, forKey: "count")
}
updateTasks()
}
func updateTasks()
{
toDoTasks.removeAll()
guard let count = UserDefaults().value(forKey: "count") as? Int else
{
return
}
for x in 0..<count
{
if let task = UserDefaults().value(forKey: "task_\(x+1)") as? String
{
toDoTasks.append(task)
}
}
tableView.reloadData()
}
#IBAction func didTapAdd()
{
let viewController = storyboard?.instantiateViewController(withIdentifier: "entry") as! EntryViewController
viewController.title = "NEW TASK"
viewController.updated =
{
DispatchQueue.main.async {
self.updateTasks()
}
}
navigationController?.pushViewController(viewController, animated: true)
}
}
extension ViewController: UITableViewDelegate
{
// function to select the rows
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let viewController = storyboard?.instantiateViewController(withIdentifier: "task") as! TasksViewController
viewController.title = "NEW TASK"
viewController.task = toDoTasks[indexPath.row]
navigationController?.pushViewController(viewController, animated: true)
}
}
extension ViewController: UITableViewDataSource
{
// function which returns number of tasks
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return toDoTasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = toDoTasks[indexPath.row]
return cell
}
}
import UIKit
class TasksViewController: UIViewController {
#IBOutlet var label : UILabel!
var task : String?
var currentPosition: Int?
override func viewDidLoad() {
super.viewDidLoad()
label.text = task
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Delete", style: .done, target: self, action: #selector(deleteTask))
}
#objc func deleteTask(_ sender: UIBarButtonItem) {
guard let currentPosition = self.currentPosition else {
return
}
let count = UserDefaults.standard.integer(forKey: "count")
UserDefaults.standard.removeObject(forKey: "task_\(currentPosition+1)")
for i in currentPosition+1..<count {
let task = UserDefaults.standard.string(forKey: "task_\(i+1)")
UserDefaults.standard.setValue(task, forKey: "task_\(i)")
}
UserDefaults.standard.setValue(count-1, forKey: "count")
navigationController?.popViewController(animated: true)
}
}
When I press the Delete button, nothing happens. Could you please help me??
var list = [String]()
#IBOutlet weak var TableView: UITableView!
override func viewDidLoad() {
self.title = "Routines"
TableView.delegate = self
TableView.dataSource = self
super.viewDidLoad()
}
//refresh view when going back to this viewcontroller
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Test Worked")
TableView.reloadData()
}
//generating rows
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return (list.count)
}
//returning text in UITableViewCell
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = UITableViewCell(style:
UITableViewCell.CellStyle.default, reuseIdentifier:
"prototype1")
print("printed")
cell.textLabel?.text = list[indexPath.row]
return cell
}
//deleting rows
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
{
if editingStyle == UITableViewCell.EditingStyle.delete{
deleteAllData("ToDo")
self.list.remove(at: indexPath.row)
TableView.reloadData()
}
}
#IBAction func didAdd() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "addRoutinePage")as! addRoutinePage
self.navigationController?.pushViewController(vc, animated: true)
}
//function to get data from core data
func getData()
{
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "ToDo")
request.returnsObjectsAsFaults = false
do{
//fetching data from coredata
let result = try context.fetch(request)
for data in result as! [NSManagedObject]
{
//appending the list from the value in coredata (attribute) or entity
self.list.append(data.value(forKey: "title")as! String)
print("append success")
}
}catch {
print("failed")
}
}
What is wrong with my code? Everything seems to work except for the UITableViewCell, the print command I entered just to check if the function is executed didn't even work. I tried TableView.reloadData() but it still didn't work. Logically if the problem is with the public function or data source or delegate it won't even generate any rows, but rows are generated. I tried resizing the cell height size too but it still won't work. Please help!
There are a few errors with the code:
You need to reload once the data fetching from CoreData is complete.
func getData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "ToDo")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
self.list.append(data.value(forKey: "title")as! String)
}
self.TableView.reloadData()
} catch {
print(error)
}
}
Also, don't forget to call the getData function.
override func viewDidLoad() {
super.viewDidLoad()
title = "Routines"
TableView.delegate = self
TableView.dataSource = self
getData()
}
Another issue with learning.
I found this in Apple Dev documentation: THIS
My target is to handle one tap on my list of items. When I click I need to open edit window and handle which row I selected. I trying to put that solution into my code but I have no idea what is TrailViewController (I getting Chinese links at first Google search page). So I decided to put my code there. I getting error:
Use of undeclared type 'TrailViewController'.
They appear after I adding this into my code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedTrail = elements[indexPath.row]
if let viewController = storyboard?.instantiateViewController(identifier: "TrailViewController") as? TrailViewController {
viewController.trail = selectedTrail
navigationController?.pushViewController(viewController, animated: true)
}
}
Full code from file below:
import UIKit
import Firebase
import FirebaseFirestore
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var listOfItemsTableView: UITableView!
var elements: [Element] = []
override func viewDidLoad() {
super.viewDidLoad()
let db = Firestore.firestore()
db.collection("recipes").whereField("uid", isEqualTo: Auth.auth().currentUser!.uid).addSnapshotListener({ (snapshot, error) in
if let snapshot = snapshot {
var elementsTemp = [Element]()
for doc in snapshot.documents {
if let itemName = doc.get("name") as? String {
elementsTemp.append(Element(name: itemName))
}
}
self.elements = elementsTemp
self.listOfItemsTableView.reloadData()
} else {
if let error = error {
print(error)
}
}
})
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.listOfItemsTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return elements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "elementCell", for: indexPath) as! ElementCell
cell.elementNameLabel.text = elements[indexPath.row].name
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedTrail = elements[indexPath.row]
if let viewController = storyboard?.instantiateViewController(identifier: "TrailViewController") as? TrailViewController {
viewController.trail = selectedTrail
navigationController?.pushViewController(viewController, animated: true)
}
}
#IBAction func addItemButtonClicked(_ sender: Any) {
self.performSegue(withIdentifier: "toAddItemView", sender: self)
}
}
class Element {
var name = ""
convenience init(name: String) {
self.init()
self.name = name
}
}
Update 1
Ok, I made some progress, but at this moment I can't navigate to EditItemViewController. This is how my code looks now:
import UIKit
import Firebase
import FirebaseFirestore
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var listOfItemsTableView: UITableView!
var elements: [Element] = []
var element: Element?
override func viewDidLoad() {
super.viewDidLoad()
let db = Firestore.firestore()
db.collection("recipes").whereField("uid", isEqualTo: Auth.auth().currentUser!.uid).addSnapshotListener({ (snapshot, error) in
if let snapshot = snapshot {
var elementsTemp = [Element]()
for doc in snapshot.documents {
if let itemName = doc.get("name") as? String {
elementsTemp.append(Element(name: itemName))
}
}
self.elements = elementsTemp
self.listOfItemsTableView.reloadData()
} else {
if let error = error {
print(error)
}
}
})
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.listOfItemsTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return elements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "elementCell", for: indexPath) as! ElementCell
cell.elementNameLabel.text = elements[indexPath.row].name
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedElement = elements[indexPath.row]
if let viewController = storyboard?.instantiateViewController(identifier: "EditItemViewControllerID") as? MainViewController {
viewController.element = selectedElement
self.navigationController?.pushViewController(viewController, animated: true)
}
}
#IBAction func addItemButtonClicked(_ sender: Any) {
self.performSegue(withIdentifier: "toAddItemView", sender: self)
}
}
class Element {
var name = ""
convenience init(name: String) {
self.init()
self.name = name
}
}
And how my storyboard looks at this moment:
Maybe I missed something?
Make a UIViewController named TrailViewController and add it as the class for a new UIViewController in your storyboard and you'll no longer get this error:
class TrailViewController: UIViewController {
var trail: Element?
// ...
}
Note: Also, don't forget to add the identifier for the new TrailViewController added in the storyboard as "TrailViewController" to get the UIViewController when calling instantiateViewController(identifier: in didSelectRow and perform navigation successfully.
My project is a reminder list and has only two view controllers(vc).
The first displays the tableview using Subtile style on the tableview cell. The second vc has two text labels, the date picker and save button.
My questions;
1) How do I send the time only to the label I have created in the tableview cell?
2) When adding the date picker to xcdatamodeld (core data), under Attribute what is the Type? I have tried Double, but that didn't work.
Greatly appreciate your support.
import UIKit
class AddEventViewController: UIViewController {
#IBOutlet weak var addReminderTextField: UITextField!
#IBOutlet weak var addInformationTextField: UITextField!
var datePicker: UIDatePicker!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.isTranslucent = false
}
override func viewDidLoad() {
super.viewDidLoad()
datePicker = UIDatePicker()
datePicker.center = view.center
view.addSubview(datePicker)
}
#IBAction func cancelButtonTapped(_ sender: Any) {
navigationController!.popViewController(animated: true)
}
#IBAction func saveButtonTapped(_ sender: Any) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let task = Task(context: context)
task.title = addReminderTextField.text!
task.subtitle = addInformationTextField.text!
(UIApplication.shared.delegate as! AppDelegate).saveContext()
navigationController!.popViewController(animated: true)
}
}
And here is my tableview code...
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath)
let task = tasks[indexPath.row]
cell.textLabel?.text = task.title!
cell.detailTextLabel?.text = task.subtitle
return cell
}
func getTasks() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
tasks = try context.fetch(Task.fetchRequest())
}
catch {
print("Fetching Failed")
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete {
let task = tasks[indexPath.row]
context.delete(task)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
tasks = try context.fetch(Task.fetchRequest())
}
catch {
print("Fetching Failed")
}
}
tableView.reloadData()
}
}
I am currently experimenting with persistent data in swift, but I am having trouble saving this data and retrieving it back again. Basically I have two text fields, and when the user presses the submit button then the entry will be saved to a UITable, here the user will be able to move the entries in the table around or delete them if they so wish. My main problem is saving and loading this data.
Taskmanager.swift -- Here I have my basic types stored
import Foundation
import UIKit
var taskMgr: TaskManager = TaskManager()
struct task {
var name = "Name"
var year = "Year"
}
//setting data
let defaults = UserDefaults.standard
//defaults.synchronize()
//getting data
class TaskManager: NSObject {
var tasks = [task]()
func addTask(name: String, year: String){
tasks.append(task(name: name, year: year))
}
}
ThirdViewController.swift -- Here I have stored my table and its functions, I also have a rough sketch of save and load data functions.
import Foundation
import UIKit
class ThirdViewController:UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
#IBAction func deleteT(_ sender: Any) {
if(tableView.isEditing == true){
tableView.setEditing(false, animated: true)
}else{
tableView.setEditing(true, animated: true)
}
}
func saveData() {
let data = NSMutableData()
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let path = paths[0]
let file = (path as NSString).appendingPathComponent("Persistent.plist")
//2
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.encode(G, forKey: "name")
archiver.endode(year, forKey: "year")
archiver.finishEncoding()
data.write(toFile: file, atomically: true)
}
func loadData() {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let path = paths[0]
let file = (path as NSString).appendingPathComponent("Persistent.plist")
// 1
if FileManager.default.fileExists(atPath: file) {
if let data = NSData(contentsOfFile: file) {
let unarchiver = NSKeyedUnarchiver(forReadingWith: data as Data)
name = unarchiver.decodeObjectForKey("name") as! [String]
year = unarchiver.decodeObjectForKey("year") as! [String]
unarchiver.finishDecoding()
}
}
}
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
loadData()
}
override func viewWillAppear(_ animated: Bool) {
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return taskMgr.tasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "TableView")
//Assign the contents of our var "items" to the textLabel of each cell
cell.textLabel!.text = taskMgr.tasks[indexPath.row].name
cell.detailTextLabel!.text = taskMgr.tasks[indexPath.row].year
//cell.editing = tableView(tableView, canMoveRowAtIndexPath: indexPath)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath){
if (editingStyle == UITableViewCellEditingStyle.delete){
taskMgr.tasks.remove(at: indexPath.row)
tableView.reloadData()
}
}
}
FourthViewController.swift -- Here I have my textfields and buttons and how I am adding my entries to the table.
import Foundation
import UIKit
class FourthViewController: UIViewController, UITextFieldDelegate{
#IBOutlet var addT: UITextField!
#IBOutlet var addY: UITextField!
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.
}
#IBAction func confTask(_ sender:UIButton){
if (addT.text == ""){
}else{
//add record
let name: String = addT.text!
let Year: String = addY.text!
//taskMgr.addTask(name:name)
taskMgr.addTask(name:name, year:Year)
}
//dismiss keyboard and reset fields
self.view.endEditing(true)
addT.text = nil
addY.text = nil
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool{
textField.resignFirstResponder()
return true
}
}
I have created some sample code using NSUserDefaults for persisting the tasks. It is a fairly simple example, as long as you are just experimenting, and only want to have less than a 100 elements, it should be fine. Using the code below you should be able to display, remove and save tasks.
However, in the future, i would highly recommend you to read more into Core Data. There are many great tutorials out there, like this one.
I have created a Task object model, and TaskManager for reading, saving and removing tasks.
import Foundation
// Task Data Model
class Task: NSObject, NSCoding {
let name: String
let year: String
required init(name: String, year: String) {
self.name = name
self.year = year
}
required init(coder decoder: NSCoder) {
self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
self.year = decoder.decodeObject(forKey: "year") as? String ?? ""
}
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
coder.encode(year, forKey: "year")
}
}
class TaskManager {
/// UserDefaults instance
private let defaults = UserDefaults.standard
/// Singleton instance, class **should** be accessed by this property
static let shared = TaskManager()
/// Indetifier of tasks container in `defaults`
private let kTasksIdentifier = "tasks"
/// Add a new task to your container and syncronize it into `defaults`
///
/// - Parameters:
/// - name: Name of the task
/// - year: Year of the task
func save(taskName name: String, year: String) {
let task = Task(name: name, year: year)
// Check if there is already saved tasks
guard let data = defaults.value(forKey: kTasksIdentifier) as? Data, var tasks = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Task] else {
// If not, save it as the first one
syncronizeTasks(tasks: [task])
return
}
tasks.append(task)
syncronizeTasks(tasks: tasks)
}
/// Remove a task at an index
///
/// - Parameters:
/// - index: The index of the removeable task
func remove(at index: Int) {
guard let data = defaults.value(forKey: kTasksIdentifier) as? Data, var tasks = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Task] else {
fatalError("Unable to retrive tasks from defaults")
}
tasks.remove(at: index)
syncronizeTasks(tasks: tasks)
}
/// Read all tasks elements
/// If there are tasks in memory, it returns the one from memory
/// Otherwise reads it from `UserDefaults`
///
/// - Returns: all tasks elements available, return empty array if no elements found
func readAllTasks() -> [Task] {
let data = UserDefaults.standard.value(forKey: kTasksIdentifier)
let allTasks = NSKeyedUnarchiver.unarchiveObject(with: data as! Data)
return allTasks as? [Task] ?? [Task]()
}
private func syncronizeTasks(tasks: [Task]) {
let data = NSKeyedArchiver.archivedData(withRootObject: tasks)
defaults.set(data, forKey: kTasksIdentifier)
defaults.synchronize()
}
}
I have modified your already existing ThirdViewController a bit.
import UIKit
import Foundation
class ThirdViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
/// Your tasks being updated in this collection every time `refreshTasks()` is being called
private var tasks = [Task]()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.refreshTasks()
self.tableView.reloadData()
}
func refreshTasks() {
self.tasks = TaskManager.shared.readAllTasks()
}
#IBAction func deleteT(_ sender: Any) {
if(tableView.isEditing == true) {
tableView.setEditing(false, animated: true)
} else {
tableView.setEditing(true, animated: true)
}
}
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return tasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "TableView")
//Assign the contents of our var "items" to the textLabel of each cell
cell.textLabel!.text = tasks[indexPath.row].name
cell.detailTextLabel!.text = tasks[indexPath.row].year
//cell.editing = tableView(tableView, canMoveRowAtIndexPath: indexPath)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath){
if (editingStyle == UITableViewCellEditingStyle.delete) {
self.tableView.beginUpdates()
TaskManager.shared.remove(at: indexPath.row)
refreshTasks()
self.tableView.deleteRows(at: [indexPath], with: .fade)
self.tableView.endUpdates()
}
}
}
And, just in case, edited your FourthViewController too
import Foundation
import UIKit
class FourthViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var addT: UITextField!
#IBOutlet var addY: UITextField!
/// User has pressed `Submit` button
///
/// - Parameter sender: the pressed button
#IBAction func confTask(_ sender: UIButton) {
// Check if textfields are containing text
guard let nameText = addT.text, let yearText = addY.text, !nameText.isEmpty, !yearText.isEmpty else {
print("at least one of the textFields is not filled")
return
}
// Save the tasks
TaskManager.shared.save(taskName: nameText, year: yearText)
//dismiss keyboard and reset fields
self.view.endEditing(true)
addT.text = nil
addY.text = nil
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool{
textField.resignFirstResponder()
return true
}
}