iOS 12 Errors: appears to be from a different NSManagedObjectModel than this context's - swift

I started to get the following error when launching my application on iOS 12 simulator. Did anybody face issue like this?
2018-08-11 21:17:44.440144+0300 CafeManager[4633:128874] [error] error: The fetch request's entity 0x600001f6e940 'TablesTable' appears to be from a different NSManagedObjectModel than this context's
I have global constant defined in AppDelegate:
let viewContext = AppDelegate.viewContext
And use it with NSFetchedResultsController for UITableView update, for example:
import UIKit
import CoreData
class HistoryTablesTableViewController: FetchedResultsTableViewController {
//MARK: variables
private var fetchedResultsController: NSFetchedResultsController<TablesTable>?
private var currentTable: TablesTable?
private var tableNameTextField: UITextField!
//MARK: system functions for view
override func viewDidLoad() {
super.viewDidLoad()
sideMenu()
addSyncObserver()
}
override func viewWillAppear(_ animated: Bool) {
updateGUI()
}
// MARK: IBOutlets
#IBOutlet weak var menuButton: UIBarButtonItem!
// MARK: side menu
private func sideMenu() {
if revealViewController() != nil {
menuButton.target = revealViewController()
menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
revealViewController().rearViewRevealWidth = 260
view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
}
//MARK: functions for table update
private func updateGUI () {
let request : NSFetchRequest<TablesTable> = TablesTable.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "tableName", ascending: true, selector: #selector(NSString.localizedStandardCompare(_:)))]
fetchedResultsController = NSFetchedResultsController<TablesTable>(fetchRequest: request, managedObjectContext: viewContext, sectionNameKeyPath: nil, cacheName: nil)
try? fetchedResultsController?.performFetch()
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! HistoryTablesTableViewCell
if let tablesTable = fetchedResultsController?.object(at: indexPath) {
cell.tableNameLabel.text = tablesTable.tableName
cell.cellDelegate = self
cell.table = tablesTable
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath as IndexPath)
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
currentTable = fetchedResultsController?.object(at: indexPath)
performSegue(withIdentifier: "showTableSessions", sender: cell)
}
//MARK: prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showTableSessions" {
if let tableSessionsTVC = segue.destination as? TableSessionsTableViewController {
tableSessionsTVC.title = self.currentTable!.tableName!
tableSessionsTVC.currentTable = self.currentTable!
}
}
}
}
// MARK: Delegates
extension HistoryTablesTableViewController: HistoryTablesTableViewCellDelegate {
func didPressTablesCellButton(table: TablesTable) {
currentTable = table
}
}
// Common extension for fetchedResultsController
extension HistoryTablesTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController?.sections?.count ?? 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController?.sections, sections.count > 0 {
return sections[section].numberOfObjects
}
else {
return 0
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = fetchedResultsController?.sections, sections.count > 0 {
return sections[section].name
}
else {
return nil
}
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return fetchedResultsController?.sectionIndexTitles
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return fetchedResultsController?.section(forSectionIndexTitle: title, at: index) ?? 0
}
}
// Observer to check that sync was performed to update GUI
extension HistoryTablesTableViewController {
private func addSyncObserver () {
NotificationCenter.default.addObserver(forName: Notification.Name(rawValue: appDelegate.syncDidFinishNotification), object: nil, queue: nil) {
[weak self] notification in
DispatchQueue.main.async {
self?.updateGUI()
}
}
}
}
In the same time it looks like that app works, but had no chance to test everything properly yet.
I use CoreData, Seam3 framework.
I found the only one mention of this error on github, but do not see solution.

I had been getting this error with iOS 12 also. This is how I finally fixed it in my project. This is in Objective C, not Swift, but hopefully it will get you going in the right direction.
The code that produced this error looked like this
// in the init code for my object
NSString *pathm = [[NSBundle mainBundle] pathForResource:#"mycoredb" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:pathm];
self.model = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
// in another method in the object
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [[self.model entitiesByName] objectForKey:#"Model_name"];
[request setEntity:entity];
The issue has something to do with the entity. So I updated my code to reflect the example on this page: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/FetchingObjects.html
Here is how my code looks now
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Model_name"];
no more "appears to be from a different NSManagedObjectModel than this context" error.

In my case it was a simple solution, I am using 4 contexts and I had to set their parent to the main context.
var mainManagedContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
lazy var backgroundQueueContext: NSManagedObjectContext = {
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.parent = mainManagedContext
return context
}()
lazy var mainQueueContext: NSManagedObjectContext = {
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = mainManagedContext
return context
}()
lazy var alternativeContext: NSManagedObjectContext = {
let context = persistentContainer.newBackgroundContext()
return context
}()

I ran into something similar when we created two instances of model even if they are read from the same momd file. My assumption was that if it's read from the same file, they should be the same model, but I guess something has changed internally that no longer treats two instances from read from the same momd file as the same model.
Make sure when you're referencing viewContext it is as shared (same instance) of NSManagedObjectContext and not initializing a new model instance from the file.
I hope this helps.

Related

CoreData gets added to Table View AGAIN Every Time I Show View Controller (duplicates data)

I followed this tutorial
https://www.youtube.com/watch?v=35mKM4IkHS8&lc=UgztyK4XjUuAOrKk0XJ4AaABAg.9LtwRc_M0Gv9Nt8GIlAzDo
Basically I made a NotePad App that has a core data save function.
I made this app on another view controller
So There is MainViewController > NoteViewViewController
The first time I click the notepad section it loads core data perfectly well, but if I close out the NoteView and reopen it -- it duplicates all the saved Notes in Core Data
Here is the. Note ViewController
import UIKit
import CoreData
var noteList = [Note]()
class NoteTableView: UITableViewController
{
func nonDeletedNotes() -> [Note]
{
var noDeleteNoteList = [Note]()
for note in noteList
{
if(note.deletedDate == nil)
{
noDeleteNoteList.append(note)
}
}
return noDeleteNoteList
}
var firstLoad = true
override func viewDidLoad() {
if(firstLoad == true)
{
firstLoad = false
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context: NSManagedObjectContext = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Note")
do {
let results:NSArray = try context.fetch(request) as NSArray
for result in results
{
let note = result as! Note
noteList.append(note)
}
}
catch
{
print("Fetch Failed")
}
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell
{
let noteCell = tableView.dequeueReusableCell(withIdentifier: "noteCellID", for: indexPath) as! NoteCell
let thisNote: Note!
thisNote = nonDeletedNotes()[indexPath.row]
noteCell.titleLabel.text = thisNote.title
noteCell.descLabel.text = thisNote.desc1
return noteCell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return nonDeletedNotes().count
}
override func viewDidAppear(_ animated: Bool) {
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
self.performSegue(withIdentifier: "editNote", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if(segue.identifier == "editNote")
{
let indexPath = tableView.indexPathForSelectedRow!
let noteDetail = segue.destination as? FocusWheelViewController
let selectedNote : Note!
selectedNote = nonDeletedNotes()[indexPath.row]
noteDetail!.selectedNote = selectedNote
tableView.deselectRow(at: indexPath, animated: true)
}
}
}
I'm sure there is a common solution but I'm not sure what it is and wasn't able to follow the posts asking similar questions as my code was different and I truthfully don't understand the mechanics well enough to apply other answers to this
I found the easiest solution was to just add these two lines so the table view refreshed every-time, then loaded the data
noteList.removeAll()
tableView.reloadData()
So the code looks something like this:
var firstLoad = true
override func viewDidLoad() {
if(firstLoad == true)
{
noteList.removeAll() //NEWCODE
tableView.reloadData() //NEWCODE
firstLoad = false
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context: NSManagedObjectContext = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Note")
do {
let results:NSArray = try context.fetch(request) as NSArray
for result in results
{
let note = result as! Note
noteList.append(note)
}
}
catch
{
print("Fetch Failed")
}
}
}
The problem is var firstLoad = true. Because every time the controller start, firtLoad always true and the app will get data from Coredata and append to noteList.
The solution is UserDefaults. The first time when you run app, firstLoad always true. So you need to save the value bool of firstLoad to UserDefaults
// Set
UserDefaults.standard.setValue(true, forKey: "firstLoad")
// Get
UserDefaults.standard.bool(forKey: "firstLoad")
import UIKit
import CoreData
class NoteTableView: UITableViewController{
var noteList = [Note]()
func nonDeletedNotes() -> [Note]{
var noDeleteNoteList = [Note]()
for note in noteList {
if(note.deletedDate == nil) {
noDeleteNoteList.append(note)
}
}
return noDeleteNoteList
}
override func viewDidLoad() {
if noteList.count == 0 {
if(UserDefaults.standard.bool(forKey: "firstLoad") == true){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context: NSManagedObjectContext = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Note")
do {
let results:NSArray = try context.fetch(request) as NSArray
for result in results
{
let note = result as! Note
noteList.append(note)
UserDefaults.standard.setValue(false, forKey: "firstLoad")
}
}
catch {
print("Fetch Failed")
}
}
} {
else {
UserDefaults.standard.setValue(false, forKey: "firstLoad")
}
}
}
}
And maybe you need to check duplicate value when get data from CoreData.

Swift: How to fetch Relationship Core Data by Month

I'm new to Swift and need your help.
I have two View Controllers with a tableview and two Entities called Groups and Singlegroups with an one to many relationship.The Entity Singlegroups has an attribute from type Date.
In View Controller 1 (MasterViewController) I show all Groups in my TableView and in the second View Controller (DetailViewController) I show all Singlegroups related to the Group of the selected row.
Now I want to load the SingleGroups on second View Controller only from current month but I can't get it to work, because I have no FetchRequest in the second View Controller. I transfer the Single Groups for the selected row in the prepareForSegue method.
I tried to call a fetchRequest manually in thousands different ways but nothing happend.
Hope you understand my problem and can help me.
MasterViewController ViewWillAppear:
import UIKit
import CoreData
class MasterViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var groups: [Groups] = []
#IBOutlet weak var groupsTableView: UITableView!
var groupsTextField: UITextField?
override func viewDidLoad() {
super.viewDidLoad()
groupsTableView.delegate = self
groupsTableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillAppear(_ animated: Bool) {
// Core date initialization
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Groups> = Groups.fetchRequest()
do {
groups = try managedContext.fetch(fetchRequest)
groupsTableView.reloadData()
} catch {
// TODO: error handling
print("Could not fetch groups")
}
}
PrepareForSegue in MasterViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetailViewController" {
guard let destination = segue.destination as? DetailViewController,
let selectedRow = self.groupsTableView.indexPathForSelectedRow?.row else {
return
}
destination.group = groups[selectedRow]
destination.title = groups[selectedRow].groupTitle
}
DetailViewController ViewWillAppear:
import UIKit
import CoreData
class DetailViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
var singleGroupDate: UILabel!
var singleGroupName: UILabel!
var singleGroupAmount: UILabel!
#IBOutlet weak var dateLabelTextField: UITextField!
#IBOutlet weak var singleGroupSum: UILabel!
#IBOutlet weak var singleGroupTableView: UITableView!
var groups: [Groups] = []
var group: Groups?
override func viewDidLoad() {
super.viewDidLoad()
singleGroupTableView.delegate = self
singleGroupTableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
// Core date initialization
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
// create alert
let alert = UIAlertController(
title: "Could not get app delegate",
message: "Could not get app delegate, unexpected error occured. Try again later.",
preferredStyle: .alert)
// add OK action
alert.addAction(UIAlertAction(title: "OK", style: .default))
// show alert
self.present(alert, animated: true)
return
}
let managedContext = appDelegate.persistentContainer.viewContext
singleGroupTableView.reloadData()
DetailViewController TableView:
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return group?.singleGroups?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = singleGroupTableView.dequeueReusableCell(withIdentifier: "SingleGroupsTableViewCell", for: indexPath) as! SingleGroupsTableViewCell
let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = Locale.current
currencyFormatter.positivePrefix = currencyFormatter.plusSign
currencyFormatter.negativePrefix = currencyFormatter.minusSign
if let singleGroup = group?.singleGroups?[indexPath.row] {
cell.singleGroupNameLabel?.text = singleGroup.singleGroupName
cell.singleGroupAmountLabel?.text = currencyFormatter.string(from: singleGroup.singleGroupAmount as NSNumber)
cell.singleGroupAmountLabel.textColor = UIColor.red
cell.singleGroupDateLabel?.text = DateHelper.convertDate(date: singleGroup.singleGroupTimeStamp)
}
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
deleteSingleGroup(at: indexPath)
}
}
UPDATE:
I solved it myself by writing a new fetch request for the SingleGroups Entity and changing the numbersOfRowsInSection method of my tableview.
func orderFetchRequest() -> NSFetchRequest<NSFetchRequestResult> {
let startDateFetch = Date().startOfMonth()
let endDateFetch = Date().endOfMonth()
self.startDate = startDateFetch!
self.endDate = endDateFetch!
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "SingleGroups")
let sortDescriptor = NSSortDescriptor(key: "singleGroupTimeStamp", ascending: false)
let predicate1 = NSPredicate(format: "group == %#", group!)
let predicate2 = NSPredicate(format: "singleGroupTimeStamp >= %# AND singleGroupTimeStamp <= %#", startDate as CVarArg, endDate as CVarArg)
let compound = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate1, predicate2])
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.predicate = compound
return fetchRequest
}
func fetchData() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = orderFetchRequest()
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedContext, sectionNameKeyPath:nil, cacheName: nil)
do {
try fetchedResultsController.performFetch()
singleGroupTableView.reloadData()
}
catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedResultsController.fetchedObjects?.count ?? 0

pass core data between views swift

I'm currently working on an inventory app that will hold information for two entities. The first entity holds two attributes "site name" and "site address". second entity holds "itemName" "itemQuantity" and "item picture". I'm able to save the data to core data. I'm having issues with fetching the data right now. Does any one know how to pass data between the view controllers.
I'm uploading the site view controller, detail view controller, and my new item view controller.
class SiteTableViewController: UIViewController {
var managedContext : NSManagedObjectContext = CoreDataHelper.sharedInstance.myContext
var sites : [Site]? = [Site]()
//variable to hold selected site
var selectedSite : Site?
let myCellIdentifier = "cell"
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view.
sites = [Site]()
reloadData()
automaticallyAdjustsScrollViewInsets = false
//let barButtonItem = UIBarButtonItem(barButtonSystemItem: .Compose, target: self, action: "addSite:")
//self.navigationItem.leftBarButtonItems = [barButtonItem]
}
//load new data into table
func reloadData() {
let fetchRequest = NSFetchRequest(entityName: "Site")
let sortDescriptor = NSSortDescriptor(key: "siteName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
sites = try self.managedContext.executeFetchRequest(fetchRequest) as! [Site]
tableView.reloadData()
} catch {
print("Error fetching objects")
}
tableView.reloadData()
}
override func viewWillAppear(animated: Bool) {
reloadData()
}
}
extension SiteTableViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(myCellIdentifier, forIndexPath: indexPath)
let site = sites![indexPath.row]
cell.textLabel?.text = site.valueForKey("siteName") as? String
cell.detailTextLabel?.text = site.valueForKey("siteAddress") as? String
return cell
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let logItemToDelete = sites![indexPath.row]
sites?.removeAtIndex(indexPath.row)
managedContext.deleteObject(logItemToDelete)
do {
try managedContext.save()
} catch {
print("Could not save or delete object")
}
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sites!.count
}
// func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//
// selectedSite = sites![indexPath.row]
//
// }
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detailSegue" {
var nextVC = segue.destinationViewController as! DetailTableViewController
var selectedItem : Site = sites![self.tableView.indexPathForSelectedRow!.row] as Site
fetchSite = selectedItem.valueForKey("siteName") as? NSManagedObject
nextVC.valueToPass = selectedItem.siteName!
}
}
}
here is my detail view controller
import UIKit
import CoreData
public var fetchSite : NSManagedObject?
class DetailTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var holdSiteData: NSMutableArray = NSMutableArray()
var managedContext : NSManagedObjectContext = CoreDataHelper.sharedInstance.myContext
#IBOutlet weak var tableView: UITableView!
var sites : [Site]!
//string to hold fetchResults controll
//open string to hold siteName
var valueToPass = ""
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "\(valueToPass)"
// Do any additional setup after loading the view.
tableView.delegate = self
tableView.dataSource = self
automaticallyAdjustsScrollViewInsets = false
tableView.estimatedRowHeight = 75.0
tableView.rowHeight = UITableViewAutomaticDimension
retrieveItemRelationship()
}
override func viewWillAppear(animated: Bool) {
retrieveItemRelationship()
}
func retrieveItemRelationship() {
// fetchSite.setValue(NSSet(keyCommands), forKey: "test")
let fetchRequest = NSFetchRequest(entityName: "SiteItem")
let sortDescriptors = NSSortDescriptor(key: "itemName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptors]
do {
let result = try fetchSite?.managedObjectContext?.executeRequest(fetchRequest)
} catch let error as NSError {
print(error)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell
// let cellInfo = fetchedResultsController.objectAtIndexPath(indexPath) as! SiteInfo
cell.cellName?.text = "Test"
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return holdSiteData.count
}
}
this is my last view to save the data to. I figured i need to pass the data here then associate the two entities so that I can save it correctly. For instance, for site name "Staples Center". I need to pull this over to the final view controller then save the information to this object.
import UIKit
import CoreData
class ItemNewViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
//var managedObjectContext : NSManagedObjectContext!
var managedContext : NSManagedObjectContext = CoreDataHelper.sharedInstance.myContext
//categories array for picker view
var categories = ["Building Materials", "Electrical", "Cleaning Products",
"Tools & Hardware", "Plumbing", "Paint", "Appliances", "Other"]
//textFields
#IBOutlet weak var itemNameTextField: UITextField!
#IBOutlet weak var quantityTextField: UITextField!
#IBOutlet weak var imageView: UIImageView!
//Label properties
#IBOutlet weak var newItemLabel: UILabel!
#IBOutlet weak var quantityLabel: UILabel!
#IBOutlet weak var categoryLabel: UILabel!
//pickerLabel
#IBOutlet weak var pickerListIcon: UIPickerView!
//clicker property label and textlabel for number
#IBOutlet weak var increaseNumberClicker: UIStepper!
#IBOutlet weak var numberFieldClicker: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//label property customize view code
newItemLabel.layer.masksToBounds = true
newItemLabel.layer.borderWidth = 2.0
newItemLabel.layer.borderColor = UIColor.whiteColor().CGColor
newItemLabel.layer.cornerRadius = 6
//label property customize view code
quantityLabel.layer.masksToBounds = true
quantityLabel.layer.borderWidth = 2.0
quantityLabel.layer.borderColor = UIColor.whiteColor().CGColor
quantityLabel.layer.cornerRadius = 6
//lable property customize view code
categoryLabel.layer.masksToBounds = true
categoryLabel.layer.borderWidth = 3.0
categoryLabel.layer.borderColor = UIColor.whiteColor().CGColor
categoryLabel.layer.cornerRadius = 6
//label picker customize view
pickerListIcon.layer.masksToBounds = true
pickerListIcon.layer.borderWidth = 3.0
pickerListIcon.layer.borderColor = UIColor.whiteColor().CGColor
pickerListIcon.layer.cornerRadius = 6
pickerListIcon.layer.backgroundColor = UIColor.blackColor().CGColor
self.pickerListIcon.dataSource = self
self.pickerListIcon.delegate = self
numberFieldClicker.enabled = false
//UIStepper value
increaseNumberClicker.wraps = true
increaseNumberClicker.autorepeat = true
increaseNumberClicker.layer.masksToBounds = true
increaseNumberClicker.layer.borderWidth = 3.0
increaseNumberClicker.layer.borderColor = UIColor.whiteColor().CGColor
increaseNumberClicker.layer.cornerRadius = 6
increaseNumberClicker.layer.backgroundColor = UIColor.blackColor().CGColor
//below code declares a variable that inherits from UITapGesture
//this allows the image view to be an action
//*Make sure you enable user interaction in storyBoard very IMPORTANT!
let imageTapRecognizer = UITapGestureRecognizer(target: self, action: "enableImage")
self.imageView.addGestureRecognizer(imageTapRecognizer)
}
// below function allows the camera view to be accessible
func enableImage(){
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) {
let cameraView = UIImagePickerController()
cameraView.sourceType = UIImagePickerControllerSourceType.Camera
cameraView.delegate = self
self.presentViewController(cameraView, animated: true, completion: nil)
}
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
self.imageView.image = image
picker.dismissViewControllerAnimated(true, completion: nil)
}
//function used to increase UIStepper. increment by integers of one
#IBAction func clickerIncreaser(sender: UIStepper) {
numberFieldClicker.text = Int(sender.value).description
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return categories.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return categories[row]
}
func pickerView(pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
//passing the categorories array into the new constant myCategoriesArray
let myCategoriesArray = categories[row]
let myTextColor = NSAttributedString(string: myCategoriesArray, attributes: [NSForegroundColorAttributeName: UIColor.whiteColor()])
return myTextColor
}
//save data to core data
#IBAction func saveButton(sender: AnyObject) {
if (itemNameTextField.text?.isEmpty)! {
let displayAlertController = UIAlertController(title: "Form Incomplete", message: "Please provide item with a name", preferredStyle: .Alert)
let okButton : UIAlertAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
displayAlertController.addAction(okButton)
self.presentViewController(displayAlertController, animated: true, completion: nil)
} else {
//declare site object and object
let siteInfoEntity = NSEntityDescription.entityForName("SiteItem", inManagedObjectContext: self.managedContext)
//declare item and item object
let siteObject = SiteItem(entity: siteInfoEntity!, insertIntoManagedObjectContext: self.managedContext)
//convert String to Integer
let quantityNumber: Int? = Int(quantityTextField.text!)
siteObject.setValue(itemNameTextField.text?.capitalizedString, forKey: "itemName")
siteObject.setValue(quantityNumber, forKey: "itemQnty")
fetchSite?.setValue(NSSet(object: siteObject), forKey: "reverseItems")
self.managedContext.saveOrLogError()
self.navigationController?.popViewControllerAnimated(true)
}
}
}
Once again if someone can point me in the right direction for saving my relationships to core data and retrieving these relationships would be great. Still new to core data so need all the help i can get!
Thank you
Your detail controller can have a property Site. In prepareForSegue you pass the selected Site object to the detail controller.
Both the site list and the list of items should be backed by NSFetchedResultsController, not in-memory arrays.

UI TableView cell opening a new view with multiple data using coredata in swift

as a beginner in iOS and Swift, I have a project that has to have a tableview with multiple cells, in which every cell contains several data types. i.e. Strings, dates etc., where in one view controller, there is the table view for viewing the cells, the second view controller is for creating a cell and entering the data, and the third view is for displaying the same data when clicking the cell. I've decided to store all of that using coredata since I was told it's most efficient and simple for beginners. I've used several tutorials on this matter but none of them handle this type of problem I have. Best example is how the Contact list works on iOS.
The code I've done so far is this:
var titleCellList = [NSManagedObject]()
var infoCellList = [NSManagedObject]()
class CellsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var cellsTableView: UITableView!
//MARK: Default Functions
override func viewDidLoad() {
super.viewDidLoad()
title = "\"Lists\""
cellsTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
// Do any additional setup after loading the view.
}
// MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return TitleCellList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
let cellTitle = titleCellList[indexPath.row]
cell.textLabel!.text = cellTitle.valueForKey("title") as? String
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(animated: Bool) {
cellsTableView.reloadData()
}
//MARK: Storing CoreData
func saveName(name: String) {
//1
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let entity = NSEntityDescription.entityForName("Data", inManagedObjectContext: managedContext)
let title = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext)
//3
title.setValue(name, forKey: "title")
//4
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
//5
titleCellList.append(title)
}
//MARK: Fetching CoreData
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//1
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let fetchRequest = NSFetchRequest(entityName:"Data")
//3
var error: NSError?
let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObject]
if let results = fetchedResults {
titleCellList = results
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
// MARK: Table Editing Methods
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
context.deleteObject(titleCellList[indexPath.row] as NSManagedObject)
titleCellList.removeAtIndex(indexPath.row)
context.save(nil)
cellsTableView.reloadData()
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let row = indexPath.row
println("Row: \(row)")
println(titleCellList[row])
performSegueWithIdentifier("checkCellSegue", sender: self)
}
Second View Controller (the one for creating a cell with data)
class AddNewViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var titleTextField: UITextField!
#IBOutlet var shortInfoTextView: UITextView!
//MARK: Default Functions
override func viewDidLoad() {
super.viewDidLoad()
self.titleTextField.delegate = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.view.endEditing(true)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
#IBAction func addDataButtonPressed(sender: UIButton) {
if titleTextField.text != "" {
CellsViewController().saveName(titleTextField.text)
titleTextField.text = ""
shortInfoTextView.text = ""
println("New title Added!")
}else {
println("No empty titles allowed!")
}
}
Now, most of this code is from a tutorial, and when I tried adding other data entity's, it didn't work. In the datamodel I currently have only 1 entity named "Data" which contains 4 models. So, to sum it up, I need to store 4 data models in one entity and load them on a different view controller when clicking on a cell which of course, has a title that the user wrote. And just to note, I've spent hours searching online for an answer so this is my last line so to say.
Any help would be greatly appreciated.
So, here is the one approach I used on this little issue. I basically just pass arguments with the prepareForSegue method, and inside of it I just pass the data I want to use in the other class/VC.
The Code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
// Setter for Second VC, destination path --> var declarations in Second VC
if segue.identifier == "checkCellSegue" {
let destination = segue.destinationViewController as! SecondViewController
if let indexPath = self.tableView?.indexPathForCell(sender as! UITableViewCell) {
let object = fetchedResultsController?.objectAtIndexPath(indexPath) as? Data
destination.cellTitle = object?.cellTitle
destination.textViewInfo = object?.textViewInfo
destination.timerValue = object?.timerValue
}
}
So, first we declare the destination which is the name of our Second VC or whatever you named it. Then, since I am accessing data trough a TableView cell we need to fetch my CoreData Entity with the indexPath. After that the final declaration is the Model Class which has all the data values from the entity, which will work like a singleton.
destination.cellTitle // --> in the 2.nd VC we declared a new var called cellTitle, var cellTitle:String
object?.cellTitle // --> in the model class "Data.swift" the declaration is #NSManaged var cellTitle:String
So, thats it. I am still a little newbie on iOS so if there are any mistakes, just say so.

tableView.reloadData() causing a crash of the app

In the Swift app, I present an addition view to add an element to the CoreData database. If I call tableview.reloadData(), the app crashes on the + button on the main screen. If I omit the reload data then the add view is presented and the data is added to the CoreData file.
the main view, from configureCell down:
func configureCell(cell: TransectTableViewCell, indexPath:NSIndexPath) {
let transectEntry = fetchedResultController.objectAtIndexPath(indexPath) as! Transects
cell.transectNameLabel.text = transectEntry.transectName
cell.transectNameLabel.textColor = UIColor.blackColor()
cell.transectNameLabel.shadowColor = UIColor.whiteColor()
cell.transectNameLabel.shadowOffset = CGSizeMake(1, 1)
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let countEntry = fetchedResultController.objectAtIndexPath(indexPath) as! Transects
coreDataStack.context.deleteObject(countEntry)
coreDataStack.saveContext()
}
}
func tableView(tableView: UITableView,
heightForRowAtIndexPath indexPath: NSIndexPath)
-> CGFloat {
return 50;
}
func didFinishViewController(viewController: AddTransectViewController, didSave: Bool) {
if didSave {
var error: NSError? = nil
let context = viewController.context
self.coreDataStack.saveContext()
}
dismissViewControllerAnimated(true, completion: {})
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "addTransectSegue" {
let newTransectViewController = segue.destinationViewController as! AddTransectViewController
let transectEntryEntity = NSEntityDescription.entityForName("Transects", inManagedObjectContext: coreDataStack.context)
let newTransectEntry = Transects(entity: transectEntryEntity!, insertIntoManagedObjectContext: coreDataStack.context)
newTransectViewController.transectNewEntry = newTransectEntry
newTransectViewController.context = newTransectEntry.managedObjectContext
newTransectViewController.delegate = self
}
if segue.identifier == "transectTasksSegue" {
let indexPath = tableView.indexPathForSelectedRow()!
let transectSelected = fetchedResultController.objectAtIndexPath(indexPath) as! Transects
let tasksViewController = segue.destinationViewController as! TransectTasksViewController
tasksViewController.coreDataStack = coreDataStack
tasksViewController.selectedTransect = transectSelected
}
}
func controllerDidChangeContent(controller:
NSFetchedResultsController) {
tableView.reloadData()
}
The addition view is:
import UIKit
import CoreData
import Foundation
protocol TransectDelegate {
func didFinishViewController(ViewController:AddTransectViewController, didSave:Bool)
}
class AddTransectViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var transectNameTextField: UITextField!
#IBOutlet weak var latitudeTextField: UITextField!
#IBOutlet weak var longitudeTextField: UITextField!
#IBOutlet weak var altitudeTextField: UITextField!
var transectNewEntry: Transects!
var context: NSManagedObjectContext!
var delegate:TransectDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func updateTransectEntry() {
if let entry = transectNewEntry {
entry.transectName = transectNameTextField.text
entry.latitude = latitudeTextField.text
entry.longitude = longitudeTextField.text
entry.altitude = altitudeTextField.text
}
}
#IBAction func cancelButtonWasTapped(sender: AnyObject) {
delegate?.didFinishViewController(self, didSave: false)
}
#IBAction func saveButtonWasTapped(sender: AnyObject) {
updateTransectEntry()
delegate?.didFinishViewController(self, didSave: true)
}
}
I am missing something, but cannot see what. Ideas would be welcome.
The app hangs up on cell.transectNameLabel.text = transectEntry.transectName
with: Thread 1:EXC_BAD_ACCESS (code=1, address=0x0)
My real confusion is that this works perfectly:
import UIKit
import CoreData
class PlantSpeciesViewController: UIViewController, NSFetchedResultsControllerDelegate, PlantSpeciesDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView:UITableView!
var coreDataStack: CoreDataStack!
lazy var fetchedResultController:
NSFetchedResultsController = self.plantSpeciesFetchedResultsController()
var plantSpecies: PlantSpecies!
var selectedFamily: PlantFamily!
var context: NSManagedObjectContext!
var plantFamilyName: String!
override func viewDidLoad() {
super.viewDidLoad()
tableView.backgroundColor = UIColor.clearColor()
view.backgroundColor = UIColor(patternImage: UIImage (named: "Monitor backdrop.png")!)
self.title = plantFamilyName
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func plantSpeciesFetchedResultsController()
->NSFetchedResultsController {
fetchedResultController =
NSFetchedResultsController(
fetchRequest: plantSpeciesFetchRequest(),
managedObjectContext: coreDataStack.context,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultController.delegate = self
var error: NSError? = nil
if (!fetchedResultController.performFetch(&error)){
println("Error: \(error?.localizedDescription)")
abort()
}
return fetchedResultController
}
func plantSpeciesFetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "PlantSpecies")
fetchRequest.fetchBatchSize = 20
let predicate = NSPredicate(format: "familyName == %#", selectedFamily)
fetchRequest.predicate = predicate
let sortDescriptor = NSSortDescriptor(key: "plantSpecies", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
//var error: NSError?
return fetchRequest
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultController.sections!.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedResultController.sections![section].numberOfObjects
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("plantSpeciesCell", forIndexPath: indexPath) as! PlantSpeciesTableViewCell
cell.backgroundColor = UIColor.clearColor()
configureCell(cell, indexPath: indexPath)
return cell
}
func configureCell(cell: PlantSpeciesTableViewCell, indexPath:NSIndexPath) {
let plantEntry = fetchedResultController.objectAtIndexPath(indexPath) as! PlantSpecies
cell.speciesNameLabel.text = plantEntry.plantSpecies
cell.speciesNameLabel.textColor = UIColor.blackColor()
cell.speciesNameLabel.shadowColor = UIColor.whiteColor()
cell.speciesNameLabel.shadowOffset = CGSizeMake(1, 1)
cell.speciesImageView.image = UIImage (data: plantEntry.plantImage)
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let countEntry = fetchedResultController.objectAtIndexPath(indexPath) as! PlantFamily
coreDataStack.context.deleteObject(countEntry)
coreDataStack.saveContext()
}
}
func tableView(tableView: UITableView,
heightForRowAtIndexPath indexPath: NSIndexPath)
-> CGFloat {
return 90;
}
func didFinishViewController(viewController: AddPlantSpeciesViewController, didSave: Bool) {
if didSave {
var error: NSError? = nil
let context = viewController.context
self.coreDataStack.saveContext()
}
dismissViewControllerAnimated(true, completion: {})
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "addSpeciesSegue" {
let newPlantViewController = segue.destinationViewController as! AddPlantSpeciesViewController
let plantEntryEntity = NSEntityDescription.entityForName("PlantSpecies", inManagedObjectContext: coreDataStack.context)
let newSpeciesEntry = PlantSpecies(entity: plantEntryEntity!, insertIntoManagedObjectContext: coreDataStack.context)
newPlantViewController.selectedFamily = selectedFamily
newPlantViewController.plantNameEntry = newSpeciesEntry
newPlantViewController.context = newSpeciesEntry.managedObjectContext
newPlantViewController.delegate = self
}
}
func controllerDidChangeContent(controller:
NSFetchedResultsController) {
tableView.reloadData()
}
}
coupled with:
import UIKit
import CoreData
import Foundation
protocol PlantSpeciesDelegate {
func didFinishViewController(ViewController:AddPlantSpeciesViewController, didSave:Bool)
}
class AddPlantSpeciesViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#IBOutlet weak var plantNameTextField: UITextField!
#IBOutlet weak var plantImageView: UIImageView!
#IBOutlet weak var imageSwitch: UISwitch!
#IBOutlet weak var imageFromFileButton: UIButton!
#IBOutlet weak var imageFromCameraButton: UIButton!
let imagePicker = UIImagePickerController()
var plantNameEntry: PlantSpecies!
var selectedFamily: PlantFamily!
var passedPlantFamily: String!
var newPlantName: String!
var newImageData: NSData!
var context: NSManagedObjectContext!
var delegate:PlantSpeciesDelegate?
override func viewDidLoad() {
super.viewDidLoad()
plantImageView.image = UIImage(named: "placeholder image.jpg")
imagePicker.delegate = self
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func imageSourceSwitch(sender: AnyObject) {
if imageSwitch.on == true
{
self.imageFromFileButton.enabled = true
self.imageFromCameraButton.enabled = false
}
else
{
self.imageFromCameraButton.enabled = true
self.imageFromFileButton.enabled = false
}
}
#IBAction func imageFromFile(sender: AnyObject) {
imagePicker.sourceType = .PhotoLibrary
presentViewController(imagePicker, animated: true, completion: nil)
}
#IBAction func imageFromCamera(sender: AnyObject) {
imagePicker.sourceType = .Camera
presentViewController(imagePicker, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
self.plantImageView.image = image
dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func getPlantName() {
newPlantName = plantNameTextField.text
plantNameTextField.resignFirstResponder()
}
func updateSpeciesEntry() {
if let entry = plantNameEntry {
entry.plantSpecies = newPlantName
entry.plantImage = UIImageJPEGRepresentation(plantImageView.image, 1.0)
entry.familyName = selectedFamily
}
}
#IBAction func cancelButtonWasTapped(sender: AnyObject) {
delegate?.didFinishViewController(self, didSave: false)
}
#IBAction func saveButtonWasTapped(sender: AnyObject) {
updateSpeciesEntry()
delegate?.didFinishViewController(self, didSave: true)
}
}
So, what is the difference?
This happen to me too, fixed it with:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
instead of just tableView.reloadData()
, cause it seems that it was being called from wrong thread.
The reason for the crash is most likely that the table cannot load the data, for example a value does not exist and is being force unwrapped. The crash only happens, therefore, when you try to collect the data. Check through all the values to be sure.
I'd like to expand on GJZ answer & what brought me here. I have UITableViewCells that have a textfield. I attempted to get values the user entered from the field.
let companyNameCell: TextEntryCell = self.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as! TextEntryCell
what I realized is that when I dynamically hide and showed rows by changing the cell heights using the tableview.reloadrows method, the app would crash if it attempted to read a cell the user could not see on their screen. that is because the cells I think were deallocated.