Edit the contents list of TableViewCell in Swift - ios

I have a TableViewController with lists of data entry using CoreData. I also have ViewController with UITextView as linked by Segue to TableViewController. I have completed operations like add, save and delete on items.
Now I am working on edit by linking the selected TableViewCell to UITextView on next ViewController.I have created an action DONE on ViewController.I am able to pass data from selected Cell to TextView of next ViewController using Segue.
Now, I need to update the edited text on UITextView by clicking DONE button to back to Cells of TableViewController and also save data to CoreData. Plzz give the genuine advices and suggestions....I am new to Swift and I have enclosed my code here.
import UIKit
import CoreData
class ToDoTableViewController: UITableViewController {
var listItems = [NSManagedObject]()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.add, target: self , action: #selector(ToDoTableViewController.addItem))
}
func addItem(){
let alertController = UIAlertController(title: "To Do Tasks Lists!!!!", message: "Write Down...", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "Confirm", style: UIAlertActionStyle.default, handler: ({
(_) in
if let field = alertController.textFields![0] as? UITextField {
self.saveItem(itemToSave: (field.text!))
self.tableView.reloadData()
}
}
))
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil)
alertController.addTextField(configurationHandler: ({
(textField) in
textField.placeholder = "Type in Something!!!!"
}))
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
func saveItem(itemToSave : String){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: "ListEntity", in: managedContext)
let item = NSManagedObject(entity: entity!, insertInto: managedContext)
item.setValue(itemToSave, forKey: "item")
do {
try managedContext.save()
listItems.append(item)
}
catch {
print("Error")
}
}
override func viewWillAppear(_ animated: Bool) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ListEntity")
do{
let results = try managedContext.fetch(fetchRequest)
listItems = results as! [NSManagedObject]
}
catch {
print("Error")
}
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let objectToDelete = listItems[indexPath.row]
listItems.remove(at: indexPath.row)
managedContext.delete(objectToDelete)
tableView.deleteRows(at: [indexPath], with: .fade)
do {
try managedContext.save()
}
catch {
print("Error")
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! as UITableViewCell
let item = listItems[indexPath.row]
cell.textLabel?.text = item.value(forKey: "item") as! String?
cell.backgroundColor = UIColor.clear
return cell
}
func getIndexPathForSelectedCell() -> IndexPath?
{
var indexPath2:IndexPath?
if tableView.indexPathsForSelectedRows!.count > 0 {
indexPath2 = tableView.indexPathsForSelectedRows![0]
}
return indexPath2
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "detailView")
{
if let indexPath2 = getIndexPathForSelectedCell()
{
// here write code for move to next controller.
let vc = segue.destination as! TextEditViewController
//vc.FirstString = listItems[(indexPath2 as NSIndexPath).row] as String
let item = listItems[(indexPath2 as NSIndexPath).row]
vc.FirstString = (item.value(forKey: "item") as! String?)!
}
}
}
}
//TextEditViewController
import UIKit
import CoreData
class TextEditViewController: UIViewController {
#IBOutlet weak var textEdit: UITextView!
var FirstString = String()
override func viewDidLoad() {
super.viewDidLoad()
textEdit.text = FirstString
print(self.FirstString)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Pass your CoreData model reference to next screen:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "detailView")
{
if let indexPath2 = getIndexPathForSelectedCell()
{
// here write code for move to next controller.
let vc = segue.destination as! TextEditViewController
//vc.FirstString = listItems[(indexPath2 as NSIndexPath).row] as String
let item = listItems[(indexPath2 as NSIndexPath).row]
vc.FirstString = (item.value(forKey: "item") as! String?)!
vc.recentItem = item; //Like this. Create a recentItem refrence to your next controller of NSManagedObject or your custom core data modal.
}
}
}
When you are done with your editing in TextEditViewController:
recentItem.item = "String" //Your updated string
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
do {
try managedContext.save()
}
catch {
print("Error")
}
Hope this will work...!!

Related

How to update Core Data assigned to indexpath in Custom cell via button

I have an app of type "to do list" where I want to check my item on the list with custom button checkmark. Structure of app looks like that: main list uses UITableViewController which has custom TableViewCell with UIButton "checkmarkButton" and Label "cellLabel". Their values are saved to core data after creating, with checkmarkButton default state as false, and are assigned to a cell in cellForRowAtindexPath using value(forKeyPath:). In TableViewCell class I have a checkmarkBtnTapped function which changes displayed image for a button (check/uncheck), tint this image to a specified color, and update button "state" as bool in CoreData attribute for its key path, fetch CoreData and reloadTableView. Some functions that use my list array and other stuff from core data, or table view come from UITableViewController so I implemented delegate for them.
The problem is when I tap the checkmarkButton and it uses updateBtnState new row is created with changed state (i.e. i have tapped row with button in state "false", my actual row with label is still on "false" and new row is added with empty label and button in state "true") i guess this is due to updateBtnState() method that reference only to managedObjectContext and not to indexPath. But when I try to reference item as a point of indexPath and not NSManagedObject i cannot pass this function to TableViewCell class due to IndexPath parameter. Below in TableViewController.swift i left updateBtnState2() that i think could solve my problem but is unusable in TableViewCell checkmarkBtnTapped() function
TableViewController.swift
import UIKit
import CoreData
class TableViewController: UITableViewController, ButtonSelectionDelegate {
var list: [NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addTapped))
let cellNib = UINib(nibName: "TableViewCell", bundle: nil)
self.tableView.register(cellNib, forCellReuseIdentifier: "cell")
}
override func viewWillAppear(_ animated: Bool) {
UIApplication.shared.statusBarStyle = .lightContent
fetch()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UIApplication.shared.statusBarStyle = UIStatusBarStyle.default
}
func save(name: String, state: Bool) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let entity =
NSEntityDescription.entity(forEntityName: "Item", in: managedObjectContext)!
let Item = NSManagedObject(entity: entity, insertInto: managedObjectContext)
Item.setValue(name, forKeyPath: "name")
Item.setValue(state, forKeyPath: "isChecked")
do{
try managedObjectContext.save()
list.append(Item)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
func fetch(){
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedObjectContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Item")
do{
list = try managedObjectContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
func updateBtnState(state: Bool){
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Item", in: managedObjectContext)!
let Item = NSManagedObject(entity: entity, insertInto: managedObjectContext)
Item.setValue(state, forKeyPath: "isChecked")
do{
try managedObjectContext.save()
} catch let error as NSError {
print("Couldnt update. \(error)")
}
}
func updateBtnState2(indexPath: IndexPath, state: Bool){
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let item = list[indexPath.row]
item.setValue(state, forKeyPath: "isChecked")
do{
try managedObjectContext.save()
list[indexPath.row] = item
} catch let error as NSError {
print("Couldnt update. \(error)")
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return(list.count)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
let item = list[indexPath.row]
cell.selectionDelegate = self
cell.cellLabel.text = item.value(forKeyPath: "name") as? String
cell.checkmarkButton.isSelected = item.value(forKeyPath: "isChecked") as! Bool
return cell
}
func updateTableView(){
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
#objc func addTapped(){
let alert = UIAlertController(title: "New Name", message: "Add a new name", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default) {
[unowned self] action in
guard let textField = alert.textFields?.first,
let nameToSave = textField.text else {
return
}
self.save(name: nameToSave, state: false)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .default)
alert.addTextField()
alert.addAction(cancelAction)
alert.addAction(saveAction)
present(alert, animated: true)
}
TableViewCell.swift
import UIKit
protocol ButtonSelectionDelegate: class {
func fetch()
func updateTableView()
func updateBtnState(state: Bool)
}
class TableViewCell: UITableViewCell {
weak var selectionDelegate: ButtonSelectionDelegate!
#IBOutlet var checkmarkButton: UIButton!
#IBOutlet var cellLabel: UILabel!
#IBAction func checkmarkBtnTapped(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
if sender.isSelected {
selectionDelegate?.updateBtnState(state: true)
let image: UIImage? = UIImage(named: "done_icon.png")?.withRenderingMode(.alwaysTemplate)
checkmarkButton.setImage(image, for: .normal)
checkmarkButton.tintColor = UIColor( red: CGFloat(21/255.0), green: CGFloat(126/255.0), blue: CGFloat(251/255.0), alpha: CGFloat(1.0))
selectionDelegate?.fetch()
selectionDelegate?.updateTableView()
print("checkmarkButton pressed to done")
} else {
selectionDelegate?.updateBtnState(state: false)
let image: UIImage? = UIImage(named: "undone_icon.png")?.withRenderingMode(.alwaysTemplate)
checkmarkButton.setImage(image, for: .normal)
checkmarkButton.tintColor = UIColor.gray
selectionDelegate?.fetch()
selectionDelegate?.updateTableView()
print("checkmarkButton pressed to undone")
}
}
override func layoutSubviews() {
super.layoutSubviews()
let image: UIImage? = UIImage(named: "undone_icon.png")?.withRenderingMode(.alwaysTemplate)
checkmarkButton.setImage(image, for: .normal)
checkmarkButton.tintColor = UIColor.gray
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
extension UIButton {
func hasImage(named imageName: String, for state: UIControlState) -> Bool {
guard let buttonImage = image(for: state), let namedImage = UIImage(named: imageName) else {
return false
}
return UIImagePNGRepresentation(buttonImage) == UIImagePNGRepresentation(namedImage)
}
}
The best way to work with table and collection view cells is to have the cell take care of all configuration of its UI. To do this you pass the data in to the cell and it then puts the right data in the right data formatted as desired. You already have a custom UITableViewCell so this will be pretty easy to do…
// Make the to-do item's property names into strings so you can't mistype them later.
// The other option would be to create the subclass of NSManagedObject so the properties are directly accessible.
let isChecked = "isChecked"
let name = "name"
class TableViewCell: UITableViewCell {
// Add a property to hold the actual to-do item
var item: NSManagedObject? {
didSet {
updateCell()
}
}
// Make all outlets private so you aren't tempted to touch them from outside
#IBOutlet private var checkmarkButton: UIButton!
#IBOutlet private var cellLabel: UILabel!
// Create both images once for each cell rather than every time the image changes
let doneImage = UIImage(named: "done_icon.png")?.withRenderingMode(.alwaysTemplate)
let notDoneImage = UIImage(named: "undone_icon.png")?.withRenderingMode(.alwaysTemplate)
let doneColor = UIColor( red: CGFloat(21/255.0), green: CGFloat(126/255.0), blue: CGFloat(251/255.0), alpha: CGFloat(1.0))
let notDoneColor = UIColor.gray
private func updateCell() {
guard let item = item else { return }
cellLabel?.text = item.value(forKeyPath: name) as? String
checkmarksButton?.isSelected = item.value(forKeyPath: isChecked) as! Bool
}
#IBAction private func checkmarkBtnTapped(_ sender: UIButton) {
// Safely unwrap the to-do item
guard let item = item else { return }
sender.isSelected = !sender.isSelected
let selected = sender.isSelected
item.setValue(selected, forKeyPath: "isChecked")
checkmarkButton.setImage(selected ? doneImage : notDoneImage, for: .normal)
checkmarkButton.tintColor = selected ? doneColor : notDoneColor
print("checkmarkButton pressed to \(selected ? "done" : "undone")")
}
…
}
This way the cell can update the managed object directly rather than trying to reconnect to it through the view controller.
Also, the code in layoutSubviews shouldn't really be needed but if it is awakeFromNib is the better place to put it.
Once you cell is done you can get rid of those update button functions and change cellForRowAt to…
class TableViewController: UITableViewController {
…
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
let item = list[indexPath.row]
cell.item = item
return cell
}
…
}

Appending Values Consistently in Swift

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

The top of tableView has some empty area

The issue occurs as the pic .
at the top of tableView has some empty area
The interface is that viewController(embed in navigationController).I put one tableView on it.And construct its constraints.But run the code ,and the issue occurs .I wanna try to use two methods to solve it ,but no any changes.
self.automaticallyAdjustsScrollViewInsets = true
I adjust the constraints of tableView to superView,I set the constraint of top to -100,it works well.
The code as below:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var people:[NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.title = "My book"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName:"Person")
do {
people = try managedContext.fetch(fetchRequest)
}catch let error as NSError {
print("Cannot fetch \(error),\(error.userInfo)")
}
}
#IBAction func addName(_ sender: AnyObject) {
let alert = UIAlertController(title: "New name", message: "Add a name", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in
guard let textField = alert.textFields?.first,
let nameToSave = textField.text else {return
}
self.save(name: nameToSave)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .default)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert,animated: true)
}
func save(name:String) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Person", in: managedContext)
let person = NSManagedObject(entity: entity!, insertInto: managedContext)
person.setValue(name, forKey: "name")
do {
try managedContext.save()
people.append(person)
}catch let error as NSError {
print("Could not save\(error),\(error.userInfo)")
}
}
}
extension ViewController :UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let person = people[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = person.value(forKey: "name") as? String
return cell
}
}
You should put constraint for the tableView from storyboard and set top space = 0
You can also try setting the edgesForExtendedLayout in your viewDidLoad method:
self.edgesForExtendedLayout = []
try to implement
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return .min
}

Segue not going on Table Cell click

I am having a problem with my TableCell Segue.
I have setup a Segue with the identifier ShowAssesment on the prototype cell to Show a navigation controller connected to a table view, but when i click a cell it just highlights it and nothing happens.
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var subjects = [NSManagedObject]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(UITableViewCell.self,
forCellReuseIdentifier: "Cell")
}
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return subjects.count
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCellWithIdentifier("Cell")
let check = subjects[indexPath.row]
cell!.textLabel!.text =
check.valueForKey("name") as? String
return cell!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func addName(sender: AnyObject) {
let alert = UIAlertController(title: "New Subject", message: "Add a new Subject", preferredStyle: .Alert)
let saveAction = UIAlertAction(title: "Save",
style: .Default,
handler: { (action:UIAlertAction) -> Void in
let textField = alert.textFields!.first
self.saveName(textField!.text!)
self.tableView.reloadData()
})
let cancelAction = UIAlertAction(title: "Cancel",
style: .Default) { (action: UIAlertAction) -> Void in
}
alert.addTextFieldWithConfigurationHandler {
(textField: UITextField) -> Void in
}
alert.addAction(saveAction)
alert.addAction(cancelAction)
presentViewController(alert,
animated: true,
completion: nil)
}
func saveName(name: String) {
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let entity = NSEntityDescription.entityForName("Subjects",
inManagedObjectContext:managedContext)
let check = NSManagedObject(entity: entity!,
insertIntoManagedObjectContext: managedContext)
check.setValue(name, forKey: "name")
do {
try managedContext.save()
subjects.append(check)
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Subjects")
do {
let results =
try managedContext.executeFetchRequest(fetchRequest)
subjects = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle,forRowAtIndexPath indexPath: NSIndexPath) {
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "ShowAssesment") {
let indexPath = tableView.indexPathForCell((sender as? UITableViewCell)!)
let listVC = segue.destinationViewController as? AssesmentViewController
let subject = subjects[indexPath!.row]
listVC?.assesments = ["Death"]
}
}
}
I don't know if you have setup your segue properly and connect it to a button in cell or so but if you wan't to be on cell tap you need to implement didselectrow and perform segue from code like
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("ShowAssesment", sender: self);
}
Check to make sure you set it up correctly. You also might try something like
func tableView(tableView:UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
self.performSegueWithIdentifier("ShowAssesment", sender: tableView.cellForRowAtIndexPath(indexPath))
}

Can't pass variable into prepareForSegue in Swift?

I'm trying to pass selectedName to VC #2 (BrandTableViewController). If I println(selectedName) in viewDidDisappear I get the value, however, the value is nil when it's in prepareForSegue? Can anyone see why?
import UIKit
import CoreData
class NameTableViewController: UITableViewController, UITableViewDelegate {
//Changes [String] to [NSManagedObject]
var people = [NSManagedObject]()
#IBOutlet var nameTableView: UITableView!
var selectedName: Person?
override func viewDidLoad() {
super.viewDidLoad()
title = "\"People\""
nameTableView.delegate = self
// 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()
// Retreive the managedObjectContext from AppDelegate
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
// Print it to the console
println(managedObjectContext)
}
// Mark: - Add Button Alert
#IBAction func addButton(sender: AnyObject) {
var alert = UIAlertController(title: "New name",
message: "Add a new name",
preferredStyle: .Alert)
let saveAction = UIAlertAction(title: "Save",
style: .Default) { (action: UIAlertAction!) -> Void in
let textField = alert.textFields![0] as! UITextField
self.saveName(textField.text)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel",
style: .Default) { (action: UIAlertAction!) -> Void in
}
alert.addTextFieldWithConfigurationHandler {
(textField: UITextField!) -> Void in
}
alert.addAction(saveAction)
alert.addAction(cancelAction)
presentViewController(alert,
animated: true,
completion: nil)
}
//function to save a name (code from: http://www.raywenderlich.com/85578/first-core-data-app-using-swift)
func saveName(name: String) {
//1
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let entity = NSEntityDescription.entityForName("Person",
inManagedObjectContext:
managedContext)
let person = NSManagedObject(entity: entity!,
insertIntoManagedObjectContext:managedContext)
//3
person.setValue(name, forKey: "name")
//4
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
//5
people.append(person)
}
// MARK: - Table view data source
// override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// // #warning Potentially incomplete method implementation.
// // Return the number of sections.
// return 1
// }
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return people.count
}
private struct Storyboard {
static let CellReuseIdentifier = "Name"
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(Storyboard.CellReuseIdentifier, forIndexPath: indexPath) as! UITableViewCell
let person = people[indexPath.row]
cell.textLabel!.text = person.valueForKey("name") as? String
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let row = self.tableView.indexPathForSelectedRow()!.row
println("row \(row) was selected")
selectedName = people[indexPath.row] as? Person
println(selectedName)
// if let unWrappedSelectedName = selectedName {
// // println(unWrappedSelectedName)
// }
// else {
// println("no person was selected in didSelectRowAtIndexPath")
// }
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let personToDelete = people[indexPath.row]
//abstract this into helper function later
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
managedContext.deleteObject(personToDelete)
self.fetchCoreData()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the item to be re-orderable.
return true
}
*/
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
fetchCoreData()
}
//Helper Function to Fetch Core Data
func fetchCoreData() {
//1
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let fetchRequest = NSFetchRequest(entityName:"Person")
//3
var error: NSError?
let fetchedResults = managedContext.executeFetchRequest(fetchRequest,
error: &error) as? [NSManagedObject]
if let results = fetchedResults {
people = results
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let btvc = segue.destinationViewController as? BrandsTableTableViewController {
if let identifer = segue.identifier {
if identifer == "segueToBrands" {
btvc.selectedName = selectedName
println(selectedName)
}
}
}
else {
println("we have a segue problem")
}
}
The prepareForSegue: method is called before tableView:didSelectRowAtIndexPath: method.And I saw that you only assign selectedName in method didSelectRowAtIndexPath.So selectedName will always be nil in prepareForSegue.
You can assign selectedName in tableView:willSelectRowAtIndexPath:
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) {
let row = self.tableView.indexPathForSelectedRow()!.row
println("row \(row) was selected")
selectedName = people[indexPath.row] as? Person
println(selectedName)
}
In your preprareForSegue() you can access the selected cell (or cells) as such:
override func prepareForSegue (segue: UIStoryboardSegue, sender: AnyObject?) {
switch segue.identifier ?? "" {
case "theSegue":
if let indexPath = tableView.indexPathForSelectedRow () {
// get what you need from the cell or the DataSource object
let controller = segue.destinationViewController as! PersonController
controller.person = people[indexPath.row]
}
// ...
}
}

Resources