I am trying to have two sections in my tableView. I would like items to add to section 0 and then be able to select a row to move it from section 0 to section 1. So far, I have the items added to section 0, but the data doesn't load when it dismisses the second viewController. I have to change views (hit the add button the back button) and it finally shows up (still haven't figured that out). I know moving the row across sections must be done through the arrays and the didSelectRow method, but it's throwing me for a loop (especially with the coreData). Do I create a second array for the section 1 items? The TableView data is inputted from a second ViewController and managed by the NSFetchedResultsController for the Entity "SList". I have created an attribute for the "slcross" under "SList" (also included the input "slitem", "sldesc", "slqty", and "slprice" but have no idea how to go about getting it from section 0 to section 1. Or, do I need to create a second entity for the crossed off items? Sorry, I am in slightly over my head...
class ShoppingList: UIViewController, NSFetchedResultsControllerDelegate, UITableViewDataSource, UITableViewDelegate {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc : NSFetchedResultsController = NSFetchedResultsController()
func itemFetchRequest() -> NSFetchRequest{
let fetchRequest = NSFetchRequest(entityName: "SList")
let primarySortDescription = NSSortDescriptor(key: "slcross", ascending: true)
let secondarySortDescription = NSSortDescriptor(key: "slitem", ascending: true)
fetchRequest.sortDescriptors = [primarySortDescription, secondarySortDescription]
return fetchRequest
}
func getFetchRequetController() ->NSFetchedResultsController{
frc = NSFetchedResultsController(fetchRequest: itemFetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "slcross", cacheName: nil)
return frc
}
#IBOutlet weak var tableView: UITableView!
#IBAction func AddNew(sender: AnyObject) {
frc = getFetchRequetController()
frc.delegate = self
do {
try frc.performFetch()
} catch _ {
print("Failed to perform inital fetch.")
return
}
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
frc = getFetchRequetController()
frc.delegate = self
do {
try frc.performFetch()
} catch _ {
print("Failed to perform inital fetch.")
return
}
self.tableView.reloadData()
//TableView Background Color
self.tableView.backgroundColor = UIColor.clearColor()
self.tableView.separatorColor = UIColor.blackColor()
tableView.reloadData()
override func viewDidDisappear(animated: Bool) {
frc = getFetchRequetController()
frc.delegate = self
do {
try frc.performFetch()
} catch _ {
print("Failed to perform inital fetch.")
return
}
self.tableView.reloadData()
}
//tableView Data
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
let managedObject:NSManagedObject = frc.objectAtIndexPath(indexPath) as! NSManagedObject
moc.deleteObject(managedObject)
do {
try moc.save()
} catch _ {
print("Failed to save.")
return
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
let numberOfSections = frc.sections?.count
return numberOfSections!
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?{
let sectionHeader = "Items - #\(frc.sections![section].numberOfObjects)"
let sectionHeader1 = "Crossed Off Items - #\(frc.sections![section].numberOfObjects)"
if (section == 0) {
return sectionHeader
}
if (section == 1){
return sectionHeader1
}else{
return nil
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let numberOfRowsInSection = frc.sections?[section].numberOfObjects
return numberOfRowsInSection!
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let items = frc.objectAtIndexPath(indexPath) as! SList
cell.backgroundColor = UIColor.clearColor()
cell.textLabel?.text = "\(items.slitem!) - Qty: \(items.slqty!)"
cell.textLabel?.font = UIFont.systemFontOfSize(23)
return cell
}
//end tablevViewData*/
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.reloadData()
}
//segue to add/edit
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "edit" {
let cell = sender as! UITableViewCell
let indexPath = tableView.indexPathForCell(cell)
let SListController:SLEdit = segue.destinationViewController as! SLEdit
let items:SList = frc.objectAtIndexPath(indexPath!) as! SList
SListController.item = items
}
}
}
second ViewController
class SLEdit: UIViewController {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
#IBOutlet weak var slitem: UITextField!
#IBOutlet weak var sldesc: UITextField!
#IBOutlet weak var slqty: UITextField!
#IBOutlet weak var slprice: UITextField!
var item: SList? = nil
override func viewDidLoad() {
super.viewDidLoad()
if item != nil{
slitem.text = item?.slitem
sldesc.text = item?.sldesc
slqty.text = item?.slqty
slprice.text = item?.slprice
}
// "x" Delete Feature
self.slitem.clearButtonMode = UITextFieldViewMode.WhileEditing
self.sldesc.clearButtonMode = UITextFieldViewMode.WhileEditing
self.slqty.clearButtonMode = UITextFieldViewMode.WhileEditing
self.slprice.clearButtonMode = UITextFieldViewMode.WhileEditing
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func dismissVC() {
navigationController?.popViewControllerAnimated(true)
}
// Dispose of any resources that can be recreated.
#IBAction func saveButton(sender: AnyObject) {
if item != nil {
edititems()
} else {
createitems()
}
dismissVC()
}
func createitems() {
let entityDescription = NSEntityDescription.entityForName("SList", inManagedObjectContext: moc)
let item = SList(entity: entityDescription!, insertIntoManagedObjectContext: moc)
item.slitem = slitem.text
item.sldesc = sldesc.text
item.slqty = slqty.text
item.slprice = slprice.text
if slitem.text == nil{
createitems()
}else{
edititems()
}
do {
try moc.save()
} catch _ {
return
}
}
func edititems() {
item?.slitem = slitem.text!
item?.sldesc = sldesc.text!
item?.slqty = slqty.text!
item?.slprice = slprice.text!
do {
try moc.save()
} catch {
return
}
}
}
I am just learning and teaching myself so if you could show me and explain it so I could understand I would appreciate it!
... the data doesn't load when it dismisses the second viewController ...
I can't immediately see why that's the case. I suggest using breakpoints and/or print() to try to debug it. I would expect the table view to be automatically updated by the FRC in the controllerDidChangeContent delegate method, so start by putting
print("Controller didChangeContent reloaded the tableView")
in that method and see whether it appears in the log after you create/edit an item. Likewise for the AddNew IBAction method and viewDidDisappear.
... Do I create a second array for the section 1 items...
No need. The FRC should manage the sections and items for you: assuming slcross is a Boolean attribute, the FRC will analyse the SLItem objects and allocate them correctly to section 0 if slcross is false, and section 1 if it is true. (The indexPath used to "lookup" objects in the FRC, eg
let items = frc.objectAtIndexPath(indexPath) as! SList
comprises both the section and the row).
...how to go about getting it from section 0 to section 1...
To ensure that the items are correctly allocated when first created, add:
item.slcross = false
to the createItems() method. To move an item from section 0 to section 1, change this value to true - as you say, didSelectRowAtIndexPath is probably a good place to do this:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let items = frc.objectAtIndexPath(indexPath) as! SList
items.slcross = true
}
...do I need to create a second entity for the crossed off items?
No need, the FRC should do the necessary.
Related
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
I am making a basic note taking app. I cannot seem to change the text of my attributes in my code. Any advice?
import UIKit
import CoreData
class NoteInputTableViewController: UITableViewController, UINavigationControllerDelegate {
var listItem: List?
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
#IBOutlet weak var inputNoteLabel: UITextField!
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
if let item = listItem {
listItem.text = inputNoteLabel.text
} else {
let newItem = NSEntityDescription.insertNewObjectForEntityForName("List", inManagedObjectContext: managedObjectContext) as! List
newItem.text = inputNoteLabel.text
}
if let controller = viewController as? NoteHomeTableViewController {
controller.items = [items!]
}
}
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
} // viewDidLoad end
}
In my navigationController I am attempting to change the text of my listItem, but cannot seem to do so.
Below is my List+CoreDataProperties.swift file:
import Foundation
import CoreData
extension List {
#NSManaged var note: String?
#NSManaged var completionDate: NSDate?
}
And below is my NoteHomeTableViewController.swift file:
import UIKit
import CoreData
class NoteHomeTableViewController: UITableViewController, MyCellDelegate { // will need a delegate
var items: [List] = []
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
#IBAction func composeButtonPressed(sender: UIBarButtonItem) {
performSegueWithIdentifier("CellDetailSegue", sender: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as! NoteInputTableViewController
if let ip = sender as? NSIndexPath {
destination.listItem = items[ip.row]
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCell") as! MyCell
cell.indexPath = indexPath
cell.item = items[indexPath.row]
cell.delegate = self
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("CellDetailSegue", sender: indexPath)
}
func myCell(myCell: MyCell, didPressBackItem: List,
atIndexPath indexPath: NSIndexPath) {
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
let item = items[indexPath.row]
managedObjectContext.deleteObject(item)
do {
try managedObjectContext.save()
} catch {
print("error saving")
}
fetchItems()
tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
} // viewDidLoad end
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
fetchItems()
tableView.reloadData()
} // viewDidAppear end
func fetchItems() {
let request = NSFetchRequest(entityName: "List")
items = []
do {
let response = try managedObjectContext.executeFetchRequest(request) as! [List]
for item in response {
items.append(item)
}
} catch {
print("Error fetching data!")
}
} // fetch end
} // class end
I don't see where you initialize listItem.
I suggest
} else {
let newItem = NSEntityDescription.insertNewObjectForEntityForName("List", inManagedObjectContext: managedObjectContext) as! List
newItem.text = inputNoteLabel.text
}
should probably become
} else {
let newItem = NSEntityDescription.insertNewObjectForEntityForName("List", inManagedObjectContext: managedObjectContext) as! List
listItem = newItem
newItem.text = inputNoteLabel.text
}
Use the unwrapped item after the optional binding and assign the new initialized NSManagedObject to listItem
...
if let item = listItem {
item.text = inputNoteLabel.text
} else {
listItem = NSEntityDescription.insertNewObjectForEntityForName("List", inManagedObjectContext: managedObjectContext) as! List
listItem!.text = inputNoteLabel.text
...
or easier
...
if listItem == nil {
listItem = NSEntityDescription.insertNewObjectForEntityForName("List", inManagedObjectContext: managedObjectContext) as! List
}
listItem!.text = inputNoteLabel.text
...
I have a list of customers in a UITableView managed by an NSFetchedResultsController, class is called CustomersViewController. When I select a customer, a new view controller CustomerDetailViewController is loaded which displays their details and then a list of radiators related to them in another UITableView managed by an NSFetchedResultsController. The only editing I need on the tables is Deletion and this works fine in both tables managed by NSFetchedResultsController.
I want to be able to edit the customers details, so I have an edit button in the NavigationBar that segues to EditCustomerViewController from CustomerDetailViewController. As with previous segues the managedObjectContext and the managedObject (the selected customer) is passed through successfully and I can access all the objects values in the EditCustomerViewController, what I can't seem to do is edit them without getting these errors:
2016-02-18 12:30:08.349 Radiator Calculator[13825:2113477] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.29.5/UITableView.m:1720
2016-02-18 12:30:08.351 Radiator Calculator[13825:2113477] CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
From this error I am guessing the issue lies with the NSFetchedResultsController not liking me changing the value in the EditCustomerViewController two viewcontrollers ahead of where it was instantiated. Given that there is no table in this view controller I haven't set it up.
The code for the three viewcontrollers in question are:
Code for CustomersViewController:
import UIKit
import CoreData
class CustomersViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
var context: NSManagedObjectContext!
#IBOutlet weak var customerList: UITableView!
var selectedCustomer: NSManagedObject!
// MARK: - viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
print("Customers VC")
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
context = appDel.managedObjectContext
do {
try fetchedResultsController.performFetch()
} catch {
print("Error occured with FRC")
}
}
override func viewWillAppear(animated: Bool) {
//reload todo list data array
customerList.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: - Table data functions
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Customer Cell")
let customer = fetchedResultsController.objectAtIndexPath(indexPath)
print(customer)
cell.textLabel?.text = customer.name
return cell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
let customer = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
context.deleteObject(customer)
do {
try context.save()
} catch let error as NSError {
print("Error saving context after delete \(error.localizedDescription)")
}
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedCustomer = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
self.performSegueWithIdentifier("customerDetailSegue", sender: self)
}
// MARK: - Segue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
print("seg fired")
if segue.identifier == "addCustomerSegue" {
if let addCustomerViewController = segue.destinationViewController as? AddCustomerViewController {
addCustomerViewController.context = context
}
}
if segue.identifier == "customerDetailSegue"{
if let customerDetailViewController = segue.destinationViewController as? CustomerDetailViewController {
customerDetailViewController.context = context
customerDetailViewController.customer = selectedCustomer
}
}
}
// set up frc
lazy var fetchedResultsController: NSFetchedResultsController = {
let customerFetchRequest = NSFetchRequest(entityName: "Customers")
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
customerFetchRequest.sortDescriptors = [sortDescriptor]
let frc = NSFetchedResultsController(fetchRequest: customerFetchRequest, managedObjectContext: self.context, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()
//MARK: NSFetchedResultsControllerDelegate methods
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.customerList.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type{
case NSFetchedResultsChangeType.Insert:
//note that for insert we insert a row at _newIndexPath_
if let insertIndexPath = newIndexPath {
self.customerList.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
}
case NSFetchedResultsChangeType.Delete:
//note that for delete we delete the row at _indexPath_
if let deleteIndexPath = indexPath {
self.customerList.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
}
case NSFetchedResultsChangeType.Update:
//note that for update we update the row at _indexPath_
if let updateIndexPath = indexPath {
let cell = self.customerList.cellForRowAtIndexPath(updateIndexPath)
let customer = fetchedResultsController.objectAtIndexPath(updateIndexPath)
cell!.textLabel?.text = customer.name
}
case NSFetchedResultsChangeType.Move:
//note that for Move we delete the row at _indexPath_
if let deleteIndexPath = indexPath {
self.customerList.insertRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
}
//note that for move we insert a row at _newIndexPath_
if let insertIndexPath = newIndexPath {
self.customerList.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
}
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
//note needed as only have one section
}
func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
return sectionName
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.customerList.endUpdates()
}
}
and CustomerDetailViewController
import UIKit
import CoreData
class CustomerDetailViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
var context: NSManagedObjectContext!
var customer: NSManagedObject!
#IBOutlet weak var customerName: UILabel!
#IBOutlet weak var street: UILabel!
#IBOutlet weak var town: UILabel!
#IBOutlet weak var postCode: UILabel!
#IBOutlet weak var radiatorList: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
radiatorList.allowsSelection = false
if let name = customer.valueForKey("name") as? String {
customerName.text = name
}
if let addressLine1 = customer.valueForKey("address_line_1") as? String {
street.text = addressLine1
}
if let townName = customer.valueForKey("town") as? String {
town.text = townName
}
if let postcode = customer.valueForKey("postcode") as? String {
postCode.text = postcode
}
// set up FRC
do {
try fetchedResultsController.performFetch()
} catch {
print("Error occured with FRC")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Radiator Cell", forIndexPath: indexPath) as! RadiatorCell
let radiator = fetchedResultsController.objectAtIndexPath(indexPath) as? NSManagedObject
if let radiatorName = radiator?.valueForKey("radiatorName") as? String {
cell.radNameLabel.text = String(radiatorName)
}
if let radiatorPowerWatts = radiator?.valueForKey("radiatorPowerWatts") as? Double{
print(radiatorPowerWatts)
cell.radPowerWattsLabel.text = "\(ceil(radiatorPowerWatts)) Watts"
cell.radPowerBtusLabel.text = "\(ceil(radiatorPowerWatts / 0.293)) BTUs"
}
return cell
}
/*
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
*/
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
let radiator = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
context.deleteObject(radiator)
do {
try context.save()
} catch let error as NSError {
print("Error saving context after delete \(error.localizedDescription)")
}
}
}
// set up frc
lazy var fetchedResultsController: NSFetchedResultsController = {
let radiatorFetchRequest = NSFetchRequest(entityName: "Radiators")
let sortDescriptor = NSSortDescriptor(key: "radiatorName", ascending: true)
radiatorFetchRequest.predicate = NSPredicate(format: "customer = %#", self.customer)
radiatorFetchRequest.sortDescriptors = [sortDescriptor]
let frc = NSFetchedResultsController(fetchRequest: radiatorFetchRequest, managedObjectContext: self.context, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()
//MARK: NSFetchedResultsControllerDelegate methods
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.radiatorList.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type{
case NSFetchedResultsChangeType.Insert:
//note that for insert we insert a row at _newIndexPath_
if let insertIndexPath = newIndexPath {
self.radiatorList.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
}
case NSFetchedResultsChangeType.Delete:
//note that for delete we delete the row at _indexPath_
if let deleteIndexPath = indexPath {
self.radiatorList.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
}
case NSFetchedResultsChangeType.Update:
//note that for update we update the row at _indexPath_
if let updateIndexPath = indexPath {
let cell = self.radiatorList.cellForRowAtIndexPath(updateIndexPath)
let radiator = fetchedResultsController.objectAtIndexPath(updateIndexPath)
cell!.textLabel?.text = radiator.name
}
case NSFetchedResultsChangeType.Move:
//note that for Move we delete the row at _indexPath_
if let deleteIndexPath = indexPath {
self.radiatorList.insertRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
}
//note that for move we insert a row at _newIndexPath_
if let insertIndexPath = newIndexPath {
self.radiatorList.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
}
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
//note needed as only have one section
}
func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
return sectionName
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.radiatorList.endUpdates()
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "mapSegue"{
if let mapVC = segue.destinationViewController as? CustomerMapViewController{
var addressString = String()
if let addressLine1 = customer.valueForKey("address_line_1") as? String {
let s = addressLine1
addressString += "\(s), "
}
if let townName = customer.valueForKey("town") as? String {
let t = townName
addressString += "\(t), "
}
if let postcode = customer.valueForKey("postcode") as? String {
let p = postcode
addressString += "\(p)"
}
mapVC.address = addressString
}
}
if segue.identifier == "editCustomerSegue" {
if let editVC = segue.destinationViewController as? EditCustomerViewController {
editVC.context = context
editVC.customer = customer
}
}
}
}
and
import UIKit
import CoreData
class EditCustomerViewController: UIViewController {
var context: NSManagedObjectContext!
var customer: NSManagedObject!
override func viewDidLoad() {
super.viewDidLoad()
print("Edit customer view controller")
if let name = customer.valueForKey("name") as? String {
//this works
print(name)
}
customer.setValue("Hardcoded name change", forKey: "name")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
The application keeps running until I navigate back to the CustomersViewController when it finally crashes and I can briefly see that the customer name has changed in the list - it hasn't changed in the CustomerDetailViewController however.
Any help would be great, apologies for any lack of "swifty-ness" (I read that's a thing) - this is my first larger app in swift and iOS so I'm still learning as I go.
I'm trying to create a search bar in my Tableview, but it's not giving me a result of filtration, Table is empty
fatal error: Array index out of range
I thing that something goes wrong in Tableview update fuction or filterContent, but can't understand what. Could somebody explain it to me?
var find: [List] = [] //my core data file
var searchResult: [List] = [] //my core data file
let context: NSManagedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var detailViewController: MainVC? = nil
var frc : NSFetchedResultsController = NSFetchedResultsController()
var searchController: UISearchController!
func getFetchResultsController() ->NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: listFetchRequest(), managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
return frc
}
func listFetchRequest() -> NSFetchRequest{
let fetchRequest = NSFetchRequest (entityName: "List")
let sortDescriptor = NSSortDescriptor(key: "item", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
override func viewDidLoad() {
super.viewDidLoad()
frc = getFetchResultsController()
frc.delegate = self
do {
try frc.performFetch()
} catch {}
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? MainVC
}
searchController = UISearchController(searchResultsController: nil)
tableView.tableHeaderView = searchController.searchBar
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
let numberOfSections = frc.sections?.count
return numberOfSections!
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.active {
return searchResult.count
} else {
//return find.count
let numberOfRowsInSection = frc.sections?[section].numberOfObjects
return numberOfRowsInSection!
}
// #warning Incomplete implementation, return the number of rows
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let search = (searchController.active) ? searchResult[indexPath.row] : frc.objectAtIndexPath(indexPath) as! List
print(search)
//Configure the cell..
cell.textLabel!.text = search.item
let qty = search.qty
let address = search.address
cell.detailTextLabel?.text = "Dukan Sayi: \(qty)\(address)"
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if searchController.active {
return false
} else {
return true
}
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
let managedObject : NSManagedObject = frc.objectAtIndexPath(indexPath) as! NSManagedObject
context.deleteObject(managedObject)
do {
try context.save()
} catch {}
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
if let searchText = searchController.searchBar.text {
filterContent(searchText)
print(searchText)
tableView.reloadData()
}
}
func filterContent(searchText: String) {
searchResult = find!.filter({ ( a: List) -> Bool in
let nameMatch = a.item.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
return nameMatch != nil
})
}
I have a UITableView that I want to filter based on a selection from slide panel view controller. This is the function that gets the returned value form the panel.
func itemSelected(type: Item) {
self.selectedItem = Item.title
delegate?.collapseSidePanels?()
}
Table view code.
var myData: Array<AnyObject> = []
var selectedItem:Array<AnyObject> = []
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellID: NSString = "Cell"
var Cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellID as String) as! UITableViewCell
var data: NSManagedObject = myData[indexPath.row] as! NSManagedObject
if tableView == selectedItem {
data = self.selectedItem[indexPath.row] as! NSManagedObject
} else
{
data = myData[indexPath.row] as! NSManagedObject
}
Cell.textLabel?.text = data.valueForKeyPath("itemname") as? String
var tt = data.valueForKeyPath("itemtype") as! String
Cell.detailTextLabel?.text = ("Item Type: \(tt)")
return Cell
}
I need to filter on the itemtype.
edit - Will not filter still so here is the full code for the tableViewController.
import UIKit
import CoreData
import Foundation
#objc
protocol tableViewControllerDelegate {
optional func toggleLeftPanel()
optional func toggleRightPanel()
optional func collapseSidePanels()
}
class tableViewController: UITableViewController, NSFetchedResultsControllerDelegate, SidePanelViewControllerDelegate {
var delegate: tableViewControllerDelegate?
var myData: Array<AnyObject> = []
var myFilteredData: Array<AnyObject> = []
#IBAction func leftTapped(sender: AnyObject) {
delegate?.toggleLeftPanel?()
}
// Use this to change table view to edit mode
// and to Change the title when clicked on.
// Make sure to have sender set as UIBarButtonItem
// or you can not change the title of the button.
var condition: Bool = true
#IBAction func buttonEdit(sender: UIBarButtonItem) {
if(condition == true) {
tableView.editing = true
sender.title = "Done"
condition = false
} else {
tableView.editing = false
sender.title = "Edit"
condition = true
}
}
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var fetchedResultController: NSFetchedResultsController = NSFetchedResultsController()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
// This is neeed when using panel view controller to show the bottom navbar.
self.navigationController?.setToolbarHidden(false, animated: true)
// ref app del
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
// Ref data
let context: NSManagedObjectContext = appDel.managedObjectContext!
let freq = NSFetchRequest(entityName: "Products")
myData = context.executeFetchRequest(freq, error: nil)!
}
override func viewDidAppear(animated: Bool) {
}
// 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.
if (self.myFilteredData.count != 0) {
return self.myFilteredData.count
} else {
return self.myData.count
}
}
func getFetchedResultController() -> NSFetchedResultsController {
fetchedResultController = NSFetchedResultsController(fetchRequest: NSFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
return fetchedResultController
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellID: String = "Cell"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellID as String) as! UITableViewCell
var data: NSManagedObject
if (self.myFilteredData.count != 0){
data = myFilteredData[indexPath.row] as! NSManagedObject
cell.textLabel?.text = data.valueForKeyPath("productname") as? String
var tt = data.valueForKeyPath("itemtype") as! String
cell.detailTextLabel?.text = ("Item J Type: \(tt)")
} else {
data = myData[indexPath.row] as! NSManagedObject
cell.textLabel?.text = data.valueForKeyPath("productname") as? String
var tt = data.valueForKeyPath("itemtype") as! String
cell.detailTextLabel?.text = ("Item Type: \(tt)")
}
return cell
}
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
let item: AnyObject = myData[sourceIndexPath.row]
myData.removeAtIndex(sourceIndexPath.row)
myData.insert(item, atIndex: destinationIndexPath.row)
}
// called when a row deletion action is confirmed
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
switch editingStyle {
case .Delete:
// remove the deleted item from the model
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
context.deleteObject(myData[indexPath.row] as! NSManagedObject)
myData.removeAtIndex(indexPath.row)
context.save(nil)
// remove the deleted item from the `UITableView`
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
default:
return
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "showProduct"){
let selectedIndexPath:NSIndexPath = self.tableView.indexPathForSelectedRow()!
let genView:genViewController = segue.destinationViewController as! genViewController
genView.row = selectedIndexPath.row
}
else if (segue.identifier == "addProduct"){
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func itemSelected(item: Type) {
var selectedType = item.title
delegate?.collapseSidePanels?()
for (key, value) in enumerate(self.myData) {
if (value.valueForKeyPath("itemtype") !== "selectedType") {
self.myFilteredData.append(value)
dump(myFilteredData)
} else {
// do nothing with it
}
}
tableView.reloadData()
}
}
Depending on however you want the data filtered, you could loop through myData in itemSelected(), find the elements that you want in your filtered list and save them in a new array (myFilteredData).
var myFilteredData: Array<AnyObject> = []
func itemSelected(type: Item) {
self.selectedItem = Item.title
delegate?.collapseSidePanels?()
for (key, value) in enumerate(self.myData) {
if (value.valueForKeyPath("itemtype") == "yourCondition") {
self.myFilteredData.append(value)
} else {
// do nothing with it
}
}
tableView.reloadData() // use tableView.reloadSections with rowAnimation for better effect.
}
You would then reload the tableview with tableView.reloadSections(_ sections: NSIndexSet,
withRowAnimation animation: UITableViewRowAnimation), which will trigger the cellForRowAtIndexPath function. Here, you would need to decide if you want to use myData or myFilteredData for the cell's labels.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
var data:NSManagedObject
if (self.myFilteredData.count != 0) {
data = myFilteredData[indexPath.row] as! NSManagedObject
} else {
data = myData[indexPath.row] as! NSManagedObject
}
...
}
Also, don't forget to modify the numberOfRowsInSection function to return the size of the array you are populating the tableView with.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.myFilteredData.count != 0) {
return self.myFilteredData.count
} else {
return self.myData.count
}
}