EDIT: Just changing the code to show my attempt at the Singleton suggestion.
So I am attempting to create an application which would take the selection of a user from a UITableView and pass that to another UITableView. Then, it would be appended to the array in that view and presented as the new table. The idea being that users can select from multiple lists and create one list made of all their selections.
However, I am extremely new to iOS development and while I can get it to let me take the selected value and show it in the new UITableView, it only sends the one value and does not keep or append it. Meaning I can never show multiple additions to the list.
So, what I'm getting atm is the ability to select a cell, let's say "Kevin Smith", and that value gets sent to the new UITableView and shown to the user. But if I go and select another value, "John Smith", then only John shows up and Kevin is gone.
Here is my three controllers involved:
The first UITAbleView Controller
class PlayerViewController: UITableViewController {
var resultsController: NSFetchedResultsController<Player>!
let CDSPlayer = coreDataStackPlayer()
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelection = true
let request: NSFetchRequest<Player> = Player.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "level", ascending: true)
request.sortDescriptors = [sortDescriptor]
resultsController = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: CDSPlayer.managedContext,
sectionNameKeyPath: nil,
cacheName: nil
)
resultsController.delegate = self
do{
try resultsController.performFetch()
} catch {
print("Perform Fetch Error: \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return resultsController.sections?[section].numberOfObjects ?? 0
}
override func tableView(_ _tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "PlayerCell", for: indexPath)
let selectedIndexPaths = tableView.indexPathsForSelectedRows
let rowIsSelected = selectedIndexPaths != nil && selectedIndexPaths!.contains(indexPath)
cell.accessoryType = rowIsSelected ? .checkmark : .none
let player = resultsController.object(at: indexPath)
cell.textLabel?.text = player.name
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "player2form", sender: tableView.cellForRow(at: indexPath))
}
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style:.destructive, title: "Delete"){(action, view, completion) in
let player = self.resultsController.object(at: indexPath)
self.resultsController.managedObjectContext.delete(player)
do {
try self.resultsController.managedObjectContext.save()
self.errorMessage(message: "Player Character Deleted.");
completion(true)
} catch {
print("Delete Failed: \(error)")
self.errorMessage(message: "Failed to Delete Player Character.");
completion(false)
}
}
action.image = UIImage(named: "trash")
action.backgroundColor = .red
return UISwipeActionsConfiguration(actions: [action])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if let _ = sender as? UIBarButtonItem, let vc = segue.destination as? AddPlayerViewController{
vc.managedContext = resultsController.managedObjectContext
}
if let cell = sender as? UITableViewCell, let vc = segue.destination as? AddPlayerViewController{
vc.managedContext = resultsController.managedObjectContext
if let indexPath = tableView.indexPath(for: cell){
let player = resultsController.object(at: indexPath)
Service.shared.allPlayers.append(player)
vc.player = player
}
}
}
//Error Function
func errorMessage(message:String){
let alert = UIAlertController(title: "Alert", message: message, preferredStyle:UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title:"Ok", style:UIAlertActionStyle.default, handler:nil);
alert.addAction(okAction);
self.present(alert, animated: true, completion:nil);
}
}
extension PlayerViewController: NSFetchedResultsControllerDelegate{
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type{
case .insert:
if let indexPath = newIndexPath{
tableView.insertRows(at: [indexPath], with: .automatic)
}
case .delete:
if let indexPath = indexPath{
tableView.deleteRows(at: [indexPath], with: .automatic)
}
case .update:
if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath){
let player = resultsController.object(at: indexPath)
cell.textLabel?.text = player.name
}
default:
break
}
}
The Selection view that passes the data:
class AddPlayerViewController: UIViewController {
var managedContext: NSManagedObjectContext!
var player: Player?
var selectedItems = [String]()
#IBOutlet weak var done_btn: UIButton!
#IBOutlet weak var playerInput: UITextField!
#IBOutlet weak var cancel_btn: UIButton!
#IBAction func add2combat(_ sender: UIButton) {
self.performSegue(withIdentifier: "player2combat", sender: self)
}
#IBAction func done(_ sender: UIButton) {
guard let name = playerInput.text, !name.isEmpty else {
return //Add notice user cannot save empty items
}
if let player = self.player {
player.name = name
} else {
//Set values from input to the cell
let player = Player(context: managedContext)
player.name = name
}
do {
try managedContext.save()
playerInput.resignFirstResponder()
dismiss(animated: true)
} catch {
print("Error Saving Player Character: \(error)")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if segue.identifier == "player2combat" {
let selectedItems : [String] = [playerInput.text!]
let otherVc = segue.destination as! CombatSceneViewController
otherVc.selectedItems = selectedItems
print(selectedItems)
}
}
#IBAction func cancel(_ sender: UIButton) {
playerInput.resignFirstResponder()
dismiss(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
playerInput.becomeFirstResponder()
if let player = player {
playerInput.text = player.name
playerInput.text = player.name
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
And the UITableView that gets the data and tries to append it to the existing Array.
class CombatSceneViewController: UITableViewController{
var selectedItems = Service.shared.allSelectedItems
override func viewDidLoad() {
super.viewDidLoad()
let otherVC = AddPlayerViewController()
selectedItems.append(contentsOf: otherVC.selectedItems)
saveData()
loadData()
print(selectedItems)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return selectedItems.count
}
override func tableView(_ _tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "CombatCell", for: indexPath as IndexPath)
cell.textLabel?.text = selectedItems[indexPath.item]
return cell
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableView.reloadData()
}
func saveData() {
let data = NSMutableData()
// 1
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(selectedItems, forKey: "Agents")
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)
selectedItems = unarchiver.decodeObject(forKey: "Agents") as! [String]
unarchiver.finishDecoding()
}
}
}
Singleton Class:
class Service {
static let shared = Service()
var allPlayers = [Player]()
var allSelectedItems = [String]()
}
I hope I did the formatting right... this is my first post on here, be gentle^^;.
The problem is that when you go back the old player is gone because you don't keep it , you can try to create a singleton for that
class Service {
static let shared = Service()
var allPlayers = [Player]()
var allSelectedItems = [String]()
}
// keep it here
if let cell = sender as? UITableViewCell, let vc = segue.destination as? AddPlayerViewController{
vc.managedContext = resultsController.managedObjectContext
if let indexPath = tableView.indexPath(for: cell){
let player = resultsController.object(at: indexPath)
Service.shared.allPlayers.append(player) // add this line
vc.player = player
}
}
Do same logic to append selected items and when you reach final tableView access them with
Service.shared.allPlayers
Or
Service.shared.selectedItems
That way you have a persisted container for all the selected players & items accessible anyWhere inside the app
Related
I'm trying to add up all of the amount data and send to a different table view cell. I think I need to convert String to Double before I can do this, but I'm not sure how to do this either. Does anyone know how you can take the sum of data and present it somewhere else? I'm very new to swift and am having trouble figuring out where I need to write this code.
import Foundation
struct Income: Codable {
var name: String
var amount: String
init(name: String, amount: String) {
self.name = name
self.amount = amount
}
static let DocumentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("incomes").appendingPathExtension("plist")
static func loadSampleIncomes() -> [Income] {
return [
Income(name: "Main Income", amount: "0"),
Income(name: "Secondary Income", amount: "0"),
Income(name: "Interest Income", amount: "0")]
}
static func saveToFile(incomes: [Income]) {
let propertyListEncoder = PropertyListEncoder()
let codedIncomes = try? propertyListEncoder.encode(incomes)
try? codedIncomes?.write(to: ArchiveURL, options: .noFileProtection)
}
static func loadFromFile() -> [Income]? {
guard let codedIncomes = try? Data(contentsOf: ArchiveURL) else { return nil }
let propertyListDecoder = PropertyListDecoder()
return try? propertyListDecoder.decode(Array<Income>.self, from: codedIncomes)
}
}
import UIKit
class IncomeTableViewController: UITableViewController {
var incomes: [Income] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: CGRect.zero)
if let savedIncomes = Income.loadFromFile() {
incomes = savedIncomes
} else {
incomes = Income.loadSampleIncomes()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
// table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return incomes.count
} else {
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "incomeCell", for: indexPath) as! IncomeTableViewCell
let income = incomes[indexPath.row]
cell.update(with: income)
cell.showsReorderControl = true
return cell
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
override func tableView(_ tableView: UITableView, commit
editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath:
IndexPath) {
if editingStyle == .delete {
incomes.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: . automatic)
Income.saveToFile(incomes: incomes)
}
}
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
let movedIncome = incomes.remove(at: fromIndexPath.row)
incomes.insert(movedIncome, at: to.row)
tableView.reloadData()
}
#IBAction func editButtonTapped(_ sender: UIBarButtonItem) {
let tableViewEditingMode = tableView.isEditing
tableView.setEditing(!tableViewEditingMode, animated: true)
}
// Navigation
#IBAction func unwindToIncomeTableView(segue:UIStoryboardSegue) {
guard segue.identifier == "saveIncomeUnwind",
let sourceViewController = segue.source as? AddEditIncomeTableViewController,
let income = sourceViewController.income else { return }
if let selectedIndexPath = tableView.indexPathForSelectedRow {
incomes[selectedIndexPath.row] = income
tableView.reloadRows(at: [selectedIndexPath],
with: .none)
} else {
let newIndexPath = IndexPath(row: incomes.count, section: 0)
incomes.append(income)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
Income.saveToFile(incomes: incomes)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EditIncome" {
let indexPath = tableView.indexPathForSelectedRow!
let income = incomes[indexPath.row]
let navController = segue.destination as! UINavigationController
let addEditIncomeTableViewController = navController.topViewController as! AddEditIncomeTableViewController
addEditIncomeTableViewController.income = income
}
}
}
import UIKit
class AddEditIncomeTableViewController: UITableViewController {
#IBOutlet weak var saveButton: UIBarButtonItem!
#IBOutlet weak var incomeNameTextField: UITextField!
#IBOutlet weak var incomeAmountTextField: UITextField!
var income: Income?
override func viewDidLoad() {
super.viewDidLoad()
if let income = income {
incomeNameTextField.text = income.name
incomeAmountTextField.text = income.amount
}
updateSaveButtonState()
}
override func prepare(for segue: UIStoryboardSegue, sender:
Any?) {
super.prepare(for: segue, sender: sender)
guard segue.identifier == "saveIncomeUnwind" else { return }
let name = incomeNameTextField.text ?? ""
let amount = incomeAmountTextField.text ?? ""
income = Income(name: name, amount: amount)
}
func updateSaveButtonState() {
let nameText = incomeNameTextField.text ?? ""
let amountText = incomeAmountTextField.text ?? ""
saveButton.isEnabled = !nameText.isEmpty && !amountText.isEmpty
}
#IBAction func textEditingChanged(_ sender: UITextField) {
updateSaveButtonState()
}
}
this is the table view controller in which I want the new data to be presented.
import UIKit
class BudgetHomeTableViewController: UITableViewController {
var incomes: [Income] = []
var expenses: [Expense] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: CGRect.zero)
if let savedIncomes = Income.loadFromFile() {
incomes = savedIncomes
} else {
incomes = Income.loadSampleIncomes()
}
if let savedExpenses = Expense.loadFromFile() {
expenses = savedExpenses
} else {
expenses = Expense.loadSampleExpenses()
}
}
}
This is the cell where the data will be presented specifically
import UIKit
class BugetBalanceCell: UITableViewCell {
#IBOutlet weak var budgetBalanceText: UILabel!
var incomes: [Income] = []
var expenses: [Expense] = []
override func awakeFromNib() {
super.awakeFromNib()
let incomeTotal = incomes.map({Double($0.amount) ?? 0}).reduce(0, +)
let expenseTotal = expenses.map({Double($0.amount) ?? 0}).reduce(0, +)
let balance = incomeTotal - expenseTotal
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
First define your income,expenses arrays and load data
var incomes: [Income] = []
var expenses: [Income] = []
if let savedIncomes = Income.loadFromFile() {
incomes = savedIncomes
} else {
incomes = Income.loadSampleIncomes()
}
//do the same thing for load data to expenses array
you can get your balance = incomes - expenses using
let incomeTotal = incomes.map({Double($0.amount) ?? 0}).reduce(0, +)
let expenseTotal = expenses.map({Double($0.amount) ?? 0}).reduce(0, +)
let balance = incomeTotal - expenseTotal
use this value where you want to show in next Table View conntroller
(income as NSArray).valueForKeyPath("#sum.self")
I am a beginner to Xcode and Swift and I am currently creating an application where the user adds a person on the application and after that it right the amount of money they owe that person or that person owes him/her.
Note: I have used core data to store all the value
I have ViewController called PeopleTableViewController where the user adds the name of the person they owe. Then I have PersonDetailTableViewController which shows the list of details the user owes that particular person the selected in PeopleTableViewController. The problem I am facing is that if I add three people in PeopleTableViewController and when I select any one of the people then I am directed to same tableview in PersonDetailTableViewController but I want different tableviews for different person the user selects in PeopleTableViewController.
PersonDetailTableViewController:
import UIKit
class PersonDetailTableViewController: UITableViewController {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var totalLabel: UILabel?
var person: People?
var owe: Owe?
#IBOutlet var personTable: UITableView!
var dataInfo: [Owe] = []
var selectedObject: [Owe] = []
var balanceAmount = "Balance: "
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (dataInfo.count)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = personTable
.dequeueReusableCell(withIdentifier: "detailsCell", for: indexPath)
cell.textLabel?.text = dataInfo[indexPath.row].name
cell.detailTextLabel?.text = "₹ \(dataInfo[indexPath.row].amount)"
// if dataInfo[indexPath.row].amount < 0 {
// cell.detailTextLabel?.textColor = UIColor.red
// } else {
// cell.detailTextLabel?.textColor = UIColor.green
// }
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedObject = [dataInfo[indexPath.row]]
performSegue(withIdentifier: "addOweDetails", sender: nil)
tableView.deselectRow(at: indexPath, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
getData()
personTable.dataSource = self
addTotalToNav()
print(dataInfo as Any)
}
// MARK: - Table view data source
func addTotalToNav() -> Void {
if let navigationBar = self.navigationController?.navigationBar {
let totalFrame = CGRect(x: 10, y: 0, width: navigationBar.frame.width/2, height: navigationBar.frame.height)
totalLabel = UILabel(frame: totalFrame)
totalLabel?.text = balanceAmount
totalLabel?.tag = 1
totalLabel?.font = UIFont.boldSystemFont(ofSize: 14)
totalLabel?.textColor = UIColor.red
// navigationBar.large = totalLabel?.text
self.title = totalLabel?.text
}
}
func getData() -> Void {
do{
dataInfo = try context.fetch(Owe.fetchRequest())
var total:Double = 0.00
for i in 0 ..< dataInfo.count {
total += dataInfo[i].amount as! Double
}
balanceAmount = "Balance: ₹" + (NSString(format: "%.2f", total as CVarArg) as String)
}
catch{
print("Fetching Failed")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! NewOweTableViewController
vc.dataInfo = selectedObject
selectedObject.removeAll()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getData()
personTable.reloadData()
if (self.navigationController?.navigationBar.viewWithTag(1)?.isHidden == true){
self.navigationController?.navigationBar.viewWithTag(1)?.removeFromSuperview()
addTotalToNav()
}
}
}
PeopleTableViewController:
import UIKit
import CoreData
class PeopleTableViewController: UITableViewController {
#IBOutlet weak var peopleTableView: UITableView!
var people: [People] = []
override func viewDidLoad() {
super.viewDidLoad()
peopleTableView.separatorStyle = .none
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
//ViewWillAppear allows us to fetch all the data in the backend and help us display to the user
override func viewWillAppear(_ animated: Bool) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest: NSFetchRequest<People> = People.fetchRequest()
do {
people = try managedContext.fetch(fetchRequest)
peopleTableView.reloadData()
} catch {
print("Could not fetch")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
}
//Following function is called right before the user segues from one viewcontroller to another viewcontroller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? PersonDetailTableViewController,
let selectedRow = self.peopleTableView.indexPathForSelectedRow?.row else {
return
}
destination.person = people[selectedRow]
// destination.owe = people[selectedRow]
}
func deletePerson(at indexPath: IndexPath) {
let person = people[indexPath.row]
guard let managedContext = person.managedObjectContext else {
return
}
managedContext.delete(person)
do {
try managedContext.save()
people.remove(at: indexPath.row)
peopleTableView.deleteRows(at: [indexPath], with: .automatic)
} catch {
print("Could not delete")
peopleTableView.reloadRows(at: [indexPath], with: .automatic)
}
}
}
extension PeopleTableViewController{
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = peopleTableView.dequeueReusableCell(withIdentifier: "peopleCell", for: indexPath)
let person = people[indexPath.row]
cell.textLabel?.text = person.title
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
deletePerson(at: indexPath)
}
}
}
The following images shows what I exactly require:
PeopleTableViewController
PeopleTableViewController
On clicking Mike I get following:
PersonDetailTableViewController
On clicking John I get following:
PersonDetailTableViewController
I want that the records for Mike and John should be different that is on PersonDetailTableViewController.
You can try (Both in PeopleTableViewController) , create a segue named shoePersonDetails from PeopleTableViewController to PersonDetailTableViewController
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let person = people[indexPath.row]
performSegue(withIdentifier: "shoePersonDetails", sender: person)
tableView.deselectRow(at: indexPath, animated: true)
}
//
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! PersonDetailTableViewController
vc.dataInfo = sender as! People
}
I am retrieving the data from SQLite to a table view cells, but the data is being retrieved duplicate triple times, means on each data there should be a single cell in which the retrieved data will be. but the current situation is that for each data the three cells are being showed in a table view cell, how to retrieve data without duplication.
Here is code :
import UIKit
import SQLite
import SQLite3
#available(iOS 11.0, *)
class ViewAssetViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var database: Connection!
let assetsTable = Table("AssetTable")
let assetid = Expression<Int>("assetid")
let assetname = Expression<String>("assetname")
let assetdescription = Expression<String>("assetdescription")
let assetform = Expression<String>("assetform")
let assetdate = Expression<String>("assetdate")
var assetIdArray = [Int]()
var assetNameArray = [String]()
var assetDescArray = [String]()
var assetTempArray = [String]()
var assetCreateDateArray = [String]()
var getAssetId = String()
var assetIdIs = Int()
var assetNameIs = String()
var assetDescIs = String()
var assetTempIs = String()
var assetCreateDateIs = String()
var SubmittedList = [String]()
#IBOutlet weak var menu: UIBarButtonItem!
#IBOutlet weak var SubmittedListTable: UITableView!
#IBAction func SubmittedLeftAct(_ sender: Any) {
print("submitted left action")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
self.getDataAssetIs()
return assetNameArray.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = SubmittedListTable.dequeueReusableCell(withIdentifier: "SubmittedCell") as! SubmittedTableViewCell
// self.getDataAssetIs()
cell.submittedLabel.text = assetNameArray[indexPath.row]
cell.submittedOnLabel.text = "Created on: \(assetCreateDateArray[indexPath.row])"
cell.associatedTempLabel.text = "Associated template: \(assetTempArray[indexPath.row])"
cell.assetDescLabel.text = assetDescArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
print("delete")
if editingStyle == .delete {
print("Deleted")
let userId = assetIdArray[indexPath.row]
let user = self.assetsTable.filter(self.assetid == userId)
let deleteUser = user.delete()
do {
try self.database.run(deleteUser)
// assetIdArray.remove(at: [indexPath.row])
assetIdArray.remove(at: indexPath.row)
assetNameArray.remove(at: indexPath.row)
assetDescArray.remove(at: indexPath.row)
assetTempArray.remove(at: indexPath.row)
assetCreateDateArray.remove(at: indexPath.row)
SubmittedListTable.deleteRows(at: [indexPath], with: .automatic)
// assetDescArray.remove(at: [indexPath.row])
// numbers.removeAtIndex(indexPath.row)
//tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
} catch {
print(error)
}
}
SubmittedListTable.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// let indexPath = PDFListTable.indexPathForSelectedRow //optional, to get from any UIButton for example
// let currentCell = PDFListTable.cellForRow(at: indexPath!) as! PDFTableViewCell
// formName = currentCell.titleLabel!.text!
// print(currentCell.titleLabel!.text!)
self.moveToEdit()
}
func createAssetTable() {
print("CREATE TAPPED")
let createTable = self.assetsTable.create(ifNotExists: true){ (table) in
table.column(self.assetid, primaryKey: true)
table.column(self.assetname)
table.column(self.assetdescription)
table.column(self.assetform)
table.column(self.assetdate)
//try self.database?.run(usersTable.addColumn(formcreatedate))
}
do {
try self.database?.run(createTable)
print("Created Table")
} catch {
print(error)
}
}
func getDataAssetIs() {
do {
let users = try self.database?.prepare(self.assetsTable)
for user in users! {
print("userId: \(user[self.assetid]), name: \(user[self.assetname]), email: \(user[self.assetdescription]), formcate: \(user[self.assetform]), mydate: \(user[self.assetdate])")
assetIdIs = user[self.assetid]
assetNameIs = user[self.assetname]
assetDescIs = user[self.assetdescription]
assetTempIs = user[self.assetform]
assetCreateDateIs = user[self.assetdate]
self.assetIdArray.append(assetIdIs)
self.assetNameArray.append(assetNameIs)
self.assetDescArray.append(assetDescIs)
self.assetTempArray.append(assetTempIs)
self.assetCreateDateArray.append(assetCreateDateIs)
//print(formNameArray)
}
} catch {
print(error)
}
}
func moveToEdit() {
let mainStoryBoard: UIStoryboard = UIStoryboard(name: "Main", bundle : nil)
let desController = mainStoryBoard.instantiateViewController(withIdentifier: "ViewFormViewController") as! ViewFormViewController
let newFrontViewController = UINavigationController.init(rootViewController:desController)
revealViewController().pushFrontViewController(newFrontViewController, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
do {
let documentDirectory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileUrl = documentDirectory.appendingPathComponent("Stephencelis").appendingPathExtension("sqlite3")
let database = try Connection(fileUrl.path)
self.database = database
} catch {
print(error)
}
self.createAssetTable()
/*
if sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS AssetTable (assetid INTEGER PRIMARY KEY AUTOINCREMENT, assetname TEXT, assetdescription TEXT, assetform TEXT, assetdate Text)", nil, nil, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error creating table: \(errmsg)")
}
*/
SubmittedListTable.delegate = self
SubmittedListTable.dataSource = self
//SubmittedList = ["Asset title 1", "Asset title 2","Asset title 3","Asset title 4", "Asset title 5", "Asset title 6","Asset title 7", "Asset title 8", "Asset title 9","Asset title 10", "Asset title 11", "Asset title 12"]
// Do any additional setup after loading the view.
menu.target = revealViewController()
menu.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
navigationController?.navigationBar.barTintColor = UIColor(red:0.00, green:0.52, blue:1.00, alpha:1.0)
navigationController?.navigationBar.tintColor = UIColor.white
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
numberOfRowsInSection can be called any number of times. You should not be loading data in that method.
Call getDataAssetIs() once, not over and over. viewDidLoad would be a good place to call it. But definitely not from numberOfRowsInSection.
I'm trying to pass a certain part of an array to another view controller based on which tableview cell you click on. So when I click on the third cell for example, it'll send the array data from the third cell to the next screen. This data is an array called "todos". When I try to receive the data in the next screen, there is no data.
Code:
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EditTodo" {
print("it is")
var vc = segue.destination as! ViewController
// var indexPath = tableView.indexPathForCell(sender as UITableViewCell)
var indexPath = tableView.indexPathForSelectedRow
if let index = indexPath {
vc.todo = todos[index.row]
}
}
}
I'm not sure if it's being called at all, or what. The identifier is correct, and I'm not sure what else to do. (When I run it, the print function is not called, but I'm not even sure if it's supposed to be).
Here is the whole page of code from the 'sending data' page with the table:
import UIKit
var todos: [TodoModel] = []
var filteredTodos: [TodoModel] = []
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchDisplayDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
print("i think it worked...")
let defaults = UserDefaults.standard
if todos.count > 0 {
// Save what we have
let data = NSKeyedArchiver.archivedData(withRootObject: todos)
defaults.set(data, forKey: "TDDATA")
defaults.synchronize()
print("saved \(todos.count)")
} else if let storedTodoData = defaults.data(forKey: "TDDATA"),
let storedTodos = NSKeyedUnarchiver.unarchiveObject(with: storedTodoData) as? [TodoModel] {
// There was stored data! Use it!
todos = storedTodos
print("Used \(todos.count) stored todos")
tableView.reloadData()
self.tableView.tableFooterView = UIView()
}
//print([todos.first])
print("Here?: \(todos.first?.title)")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override var prefersStatusBarHidden: Bool {
return true
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == searchDisplayController?.searchResultsTableView {
return filteredTodos.count
}
else {
return todos.count
}
}
// Display the cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Must use 'self' here because searchResultsTableView needs to reuse the same cell in self.tableView
let cell = self.tableView.dequeueReusableCell(withIdentifier: "todoCell")! as UITableViewCell
var todo : TodoModel
if tableView == searchDisplayController?.searchResultsTableView {
todo = filteredTodos[indexPath.row] as TodoModel
}
else {
todo = todos[indexPath.row] as TodoModel
}
//var image = cell.viewWithTag(101) as! UIImageView
var title = cell.viewWithTag(102) as! UILabel
var date = cell.viewWithTag(103) as! UILabel
//image.image = todo.image
// image = UIImageView(image: newImage)
// if image.image == nil{
// print("nilish")
// image = UIImageView(image: UIImage(named: "EmptyProfile.png"))
// }
// image.image = todo.image
// if image.image == nil{
// print("pic is nil")
// image.image = UIImage(named: "CopyEmptyProfilePic.jpg")
// }
title.text = todo.title
date.text = "\(NSDate())"
let locale = NSLocale.current
let dateFormat = DateFormatter.dateFormat(fromTemplate: "yyyy-MM-dd", options:0, locale:locale)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = dateFormat
return cell
}
// MARK - UITableViewDelegate
// Delete the cell
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
todos.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath as IndexPath], with: UITableViewRowAnimation.automatic)
let defaults = UserDefaults.standard
if todos.count >= 0 {
// Save what we have
let data = NSKeyedArchiver.archivedData(withRootObject: todos)
defaults.set(data, forKey: "TDDATA")
defaults.synchronize()
print("saved \(todos.count)")
} else if let storedTodoData = defaults.data(forKey: "TDDATA"),
let storedTodos = NSKeyedUnarchiver.unarchiveObject(with: storedTodoData) as? [TodoModel] {
// There was stored data! Use it!
todos = storedTodos
print("Used \(todos.count) stored todos")
}
tableView.reloadData()
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 80
}
// Edit mode
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
tableView.setEditing(editing, animated: true)
}
// Move the cell
func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return self.isEditing
}
// func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
// let todo = todos.removeAtIndex(sourceIndexPath.row)
//todos.insert(todo, atIndex: destinationIndexPath.row)
// }
// MARK - UISearchDisplayDelegate
// Search the Cell
func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchString searchString: String?) -> Bool {
//filteredTodos = todos.filter({( todo: TodoModel) -> Bool in
// let stringMatch = todo.title.rangeOfString(searchString)
// return stringMatch != nil
//})
// Same as below
filteredTodos = todos.filter(){$0.title.range(of: searchString!)
!= nil}
return true
}
// MARK - Storyboard stuff
// Unwind
#IBAction func close(segue: UIStoryboardSegue) {
print("closed!")
tableView.reloadData()
}
// Segue
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EditTodo" {
print("it is")
var vc = segue.destination as! ViewController
// var indexPath = tableView.indexPathForCell(sender as UITableViewCell)
var indexPath = tableView.indexPathForSelectedRow
if let index = indexPath {
vc.todo = todos[index.row]
}
}
}
}
How I am receiving the data in viewDidLoad of receiving screen:
nameOfDocTF.delegate = self
nameOfDocTF.text = todo?.title
outputTextView.attributedText = todo?.desc
print(todo?.title)
//prints "nil"
As I suspected you´re missing the didSelectRowAt function which will be called when you click on a cell and from there you need to call your prepareForSegue. Implement the following function and try:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "EditTodo", sender: indexPath.row)
}
And then replace your prepareForSegue function with the following:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EditTodo" {
let vc = segue.destination as! ViewController
if let index = sender as? Int {
vc.todo = todos[index]
}
}
}
I create a ToDo List app.
I used tableView to list the tasks. And I use a custom class for cell. In cell contentView I have a label and one done button in it. I have successfully implemented the done button click action in my code. It works fine.
Problem
When I click the done button it deletes the last added task. But not the clicked one. And when I retry to click the done Button it perform no action. How to resolve this error
GIF added below, click link
Entity class ToDo
import Foundation
import CoreData
public class ToDo: NSManagedObject {
public override func awakeFromInsert() {
self.created = NSDate()
}
}
MainVC
import UIKit
import CoreData
class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
var controller: NSFetchedResultsController<ToDo>!
#IBOutlet weak var taskTextField: CustomTextField!
#IBOutlet weak var tableView: UITableView!
var toDo: ToDo!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// generateData()
attemptFetch()
}
// to give view to cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
return cell
}
// custom function
func configureCell(cell: ItemCell, indexPath: NSIndexPath) {
let toDo = controller.object(at: indexPath as IndexPath)
// call the method on the ItemCell
cell.configureCell(toDo: toDo)
// done button click
cell.doneBtn.tag = indexPath.row
cell.doneBtn.addTarget(self, action: #selector(MainVC.donePressed), for: UIControlEvents.touchUpInside)
}
// when select a cell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// it ensure it have object and atleast one object in there
if let objs = controller.fetchedObjects, objs.count > 0 {
let task = objs[indexPath.row]
performSegue(withIdentifier: "ItemDetailsVC", sender: task)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ItemDetailsVC" {
if let destination = segue.destination as? ItemDetailsVC {
if let task = sender as? ToDo {
destination.taskDetails = task
}
}
}
}
// count of cells
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// we check here if any sections then take info of them and count
if let sections = controller.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 0
}
// column count
func numberOfSections(in tableView: UITableView) -> Int {
if let sections = controller.sections {
return sections.count
}
return 0
}
// give height of a cell
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
// fetching function
func attemptFetch() {
// create a fetch request with fetching Entity
let fetchRequest: NSFetchRequest<ToDo> = ToDo.fetchRequest()
// sorting area
let dateSort = NSSortDescriptor(key: "created", ascending: true)
fetchRequest.sortDescriptors = [dateSort]
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
self.controller = controller
// actual fetching
do {
try controller.performFetch()
} catch {
let error = error as NSError
print("\(error)")
}
}
// when tableView changes this function starts listen for changes and
// it will handle that for you
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
// this function will listen for when we make change
// insertion, deletion .. etc
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case.insert:
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
case.delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
break
case.update:
if let indexPath = indexPath {
let cell = tableView.cellForRow(at: indexPath)
//update the cell data
configureCell(cell: cell as! ItemCell, indexPath: indexPath as NSIndexPath)
}
break
case.move:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
}
}
#IBAction func addBtnPressed(_ sender: UIButton) {
if taskTextField.text != "" && taskTextField.text != nil {
toDo = ToDo(context: context)
if let task = taskTextField.text {
toDo.title = task
}
ad.saveContext()
taskTextField.text = ""
self.tableView.reloadData()
}
}
// done button
func donePressed() {
if toDo != nil {
context.delete(toDo)
ad.saveContext()
}
}
func generateData() {
let task = ToDo(context: context)
task.title = "alwin"
let task1 = ToDo(context: context)
task1.title = "rambo"
let task2 = ToDo(context: context)
task2.title = "monisha"
let task3 = ToDo(context: context)
task3.title = "wounderlist"
let task4 = ToDo(context: context)
task4.title = "presentation"
let task5 = ToDo(context: context)
task5.title = "roundup"
// to save data
ad.saveContext()
}
}
ItemDetailsVC
import UIKit
class ItemDetailsVC: UIViewController {
var taskDetails: ToDo?
#IBOutlet weak var detailsLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// to clear the <DreamLIst to < only
if let topItem = self.navigationController?.navigationBar.topItem {
topItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil)
// this is execute when tap on an existing cell
if taskDetails != nil {
loadItemData()
}
}
}
func loadItemData() {
if let task = taskDetails {
detailsLbl.text = task.title
}
}
override func viewDidLayoutSubviews() {
detailsLbl.sizeToFit()
}
#IBAction func deletePressed(_ sender: UIBarButtonItem) {
if taskDetails != nil {
context.delete(taskDetails!)
ad.saveContext()
}
_ = navigationController?.popViewController(animated: true)
}
}
storyboard, click link below
ItemCell
import UIKit
class ItemCell: UITableViewCell {
#IBOutlet weak var taskTitle: UILabel!
#IBOutlet weak var doneBtn: UIButton!
var toDo: ToDo?
func configureCell(toDo: ToDo) {
taskTitle.text = toDo.title
}
}
OK Currently you are setting the selector of your done button to outside of its container (cell) this is bad practice in general, you are configuring the cell with a ToDo but not assigning the optional inside the cell, supposedly there to keep a reference to the ToDo.
In my opinion I would change this slightly so that you store the reference to the ToDo firstly:
func configureCell(toDo: ToDo) {
self.toDo = toDo
taskTitle.text = toDo.title
}
Now on your cell create a Protocol, then configure the cell with a ToDo and a delegate, then on button press tell the delegate your button was pressed with the relevant ToDo...
protocol ToDoCellDelegate: class {
func toDoCellButtonPressed(todo: ToDo?)
}
Now on your cell configure as:
func configureCell(toDo: ToDo, delegate: ToDoCellDelegate) {
self.delegate = delegate
self.toDo = toDo
taskTitle.text = toDo.title
}
and add a ref to the delegate in the cell:
weak var delegate: ToDoCellDelegate?
now change your buttons selector to a func inside the cell
func buttonPressed() {
self.delegate?.cellToDoButtonPressed(toDo: toDo)
}
Then in your VC you conform to the delegate passing self in the configuration and implement the delegate:
extension ItemDetailsVC: ToDoCellDelegate {
func toDoCellButtonPress(toDo: ToDo?) {
if let t = toDo {
//tell context to delete todo and remove cell.
}
}
}
Ok so then you should create an IBAction outlet for your button in ItemCell and then create a protocol of this form :
protocol ItemDelegate {
func clicked()
}
class ItemCell: UITableViewViewCell {
var delegate : ItemDelegate?
var indexPath: IndexPath?
//call delegate?.clicked() where you have the gesture recogniser
}
Then in cellForRowAtIndexPath
cell.delegate = self
cell.indexPath = indexPath
Then implement the extension for your class:
extension MyTableView: ItemDelegate {
func clicked(indexPath: IndexPath) {
//dismiss cell for indexPath
}
}