Update Core Data from a UISwitch inside a TableViewCell - ios

I have a static table that is bound to some Core Data values, I'm not sure how I would use NSFetchedResultsController in this instance, though I have seen discussions about how much more recommended it is.
I grab my Core Data object which is passed via Segue.
I also have a model that is setup to contain questions, with one of the properties containing the Core Data value (this is why I don't think I can use NSFetchedResultsController, as even though my Core Data entity contains some of the values I need, I'm not sure I would need a full data set)
self.surveyQuestion.append(SurveyQuestion(question: "Does the customer have a 'Proof of ownership'?", answer: coreDataEntity.isProofOfOwnership as Bool))
The questions are Survey related such as "Is your property X?" with a UiSwitch which is mapped to a Core Data value:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let cell : SurveyQuestionTableViewCell = tableView.dequeueReusableCellWithIdentifier("SurveyQuestionCell") as! SurveyQuestionTableViewCell
cell.lblQuestion.textColor = UIColor.grayColor()
let surveyQuestion = self.surveyQuestion[indexPath.row]
cell.lblQuestion.text = surveyQuestion.question
cell.toggQuestion.on = surveyQuestion.answer
if cell.toggQuestion.on {
cell.lblQuestion.textColor = UIColor.blackColor()
cell.accessoryType = .DetailDisclosureButton
}
return cell
}
Now, when I tap on the UISwitch I need it to update the Core Data value, and reload the table, its hooked up to a CustomTableViewCell like so:
*edit - Nearly got this thing working! heres my UITableViewCell class
class SurveyQuestionTableViewCell: UITableViewCell {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
#IBOutlet weak var lblQuestion: UILabel!
#IBOutlet weak var toggQuestion: UISwitch!
var surveyQuestionReference : SurveyQuestion?
var tableViewReference : UITableView?
#IBAction func toggledQuestion(sender: AnyObject) {
let tempContext: NSManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
tempContext.parentContext = self.managedObjectContext
tempContext.performBlock({
let entityName = "CoreDataEntity"
let request = NSFetchRequest(entityName: entityName)
request.predicate = NSPredicate(format: "id = %#", self.surveyQuestionReference!.id)
do {
let results = try tempContext.executeFetchRequest(request) as? [NSManagedObject]
if results!.count > 0{
if let moc = self.managedObjectContext{
moc.performBlockAndWait({
for result in results!{
result.setValue(self.toggQuestion.on, forKey: (self.surveyQuestionReference?.property)!)
}
})
}
}
do {
try tempContext.save()
//completion(finished: true)
} catch let error {
print(error)
}
}catch{
print("error")
}
})
print(sender)
dispatch_async(dispatch_get_main_queue()) {
self.tableViewReference!.reloadData()
}
}
I can obviously access the bit where the toggle is triggered, but this class doesn't know anything about the Core Data bit, I was thinking about using notifications but that just seems kind of messy...

when you create your cell, pass in a reference to the coredata object, and the tableView itself and store them as attributes of SurveyQuestionTableViewCell, then you can do everything you need to in setSelected()
in your custom cell class, add an attribute for the question
class SurveyQuestionTableViewCell: UITableViewCell {
#IBOutlet weak var lblQuestion: UILabel!
#IBOutlet weak var toggQuestion: UISwitch!
var surveyQuestionReference : SurveyQuestionType
vat tableViewReference : UITableView
...
and then in cellForRowAtIndexPath after you create the cell
cell.surveyQuestionReference = surveyQuestion
cell.tableViewReference = tableView
where SurveyQuestionType is whatever you have previously defined
in setSelected, you can use those stored attributes
surveyQuestionReference = self.toggQuestion.on
tableViewReference.reloadData()

Here's another option, using a shared Instance
import Foundation
import MapKit
import CoreData
class DataModelInstance : NSObject, NSCoding
{
var appDelegate : AppDelegate?
var managedContext : NSManagedObjectContext?
var persistentStoreCoordinator : NSPersistentStoreCoordinator?
// plus whatever else you need
class var sharedInstance : DataModelInstance
{
struct Singleton
{
static let instance = DataModelInstance()
}
return Singleton.instance
}
and then in any class which needs access to this data model
var dataModel = DataModelInstance.sharedInstance
I know there are those who just won't ever use singletons, but it can be a much more elegant solution to making these attributes available where they are needed
With a shared data model, you can simply move all of your data attributes out of the class they are currently in, and reference them through the data model - then if you have the same data model in your custom cell class, you can do whatever you can do in the main view. To keep your GUI and processing logic separate, you can put everything in the data model
dataModel.refreshTable()
and then define a function in the data model that takes care of your table view - you could save all current edits to the data, and reload, without having to put any of that logic in individual cell classes

for updating any record in core data try to use this code:
let managedObjectContext:NSManagedObjectContext=(UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
let req=NSFetchRequest(entityName: "Entity Name")
req.returnsObjectsAsFaults=false
let result:NSArray=try! managedObjectContext.executeFetchRequest(req)
if result.count>0{
let res=result[Int(indexPath.row!]as! NSManagedObject
res.setValue("The Value", forKey: "Key Name")
do {
try managedObjectContext.save()
} catch _ { print("Update Unsuccessful") }

You must use [unowned self] in within the closure. See Apple's docs. This is how it's done. See also CoreDataKit, a 28-star github repo Core Data stack. It's available on cocoapods and honestly, why not just drop something like this into your app and not worry about "unowned selves" and other philosophical brain twisters, eh?
if let moc = self.managedObjectContext{
moc.performBlockAndWait({
/* In here we are in a closure (Swift version of a block), so "self" is problematic. Use unowned self instead (in objective c you'd have to do weak self). */
[unowned self] in
for result in results!{
result.setValue(self.toggQuestion.on,
forKey: (self.surveyQuestionReference?.property)!)
}
})
}

Related

Memory leak in Core Data fetch request with Swift

I've come across a memory leak when I make a Core Data fetch request using Swift. However, I make an almost identical fetch request in a different part of the app, but it doesn't cause a leak. In both cases, the fetch requests are made in viewDidLoad of a view controller, and the results of the fetch request are assigned to an optional property of the view controller.
Here's the method for the fetch request that does not cause any leaks:
class LocationFilter {
//Lots of other code...
class func getAllPlacesOfRegionType<T: Region>(regionType: RegionType) -> [T] {
let fetchRequest = NSFetchRequest(entityName: regionType.rawValue)
var places: [T]
do {
places = try CoreDataStack.sharedInstance.context.executeFetchRequest(
fetchRequest) as! [T]
} catch let error as NSError {
NSLog("Fetch request failed: %#", error.localizedDescription)
places = [T]()
}
places.sortInPlace({ (firstPlace, nextPlace) -> Bool in
//Ingenious sorting code...
})
return places
}
}
This method is called in viewDidLoad of a viewController, and the result is assigned to the property var allRegions: [Region]? without any leaks. Here's the code:
class PlacesTableViewController: UITableViewController {
var allRegions: [Region]?
#IBOutlet weak var segmentedRegions: UISegmentedControl!
#IBAction func selectRegionSegment(sender: UISegmentedControl) {
// When the segmented control is tapped, the appropriate list will be loaded.
switch sender.selectedSegmentIndex {
case 0: //Country Segment
allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.Country)
case 1: //States segment
allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.Province)
case 2: //Cities segment
allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.City)
case 3: //Addresses segment
allRegions = LocationFilter.getAllPlacesOfRegionType(RegionType.Address)
default:
break
}
// Then reload the cells with animations.
let index = NSIndexSet(index: 0)
tableView.reloadSections(index, withRowAnimation: UITableViewRowAnimation.Automatic)
}
override func viewDidLoad() {
super.viewDidLoad()
selectRegionSegment(segmentedRegions)
}
}
The following method is called in viewDidLoad of a different viewController to set the property var allDays: [Day]!.
class DateFilter {
//Lots of other code in the class...
class func getAllDays() -> [Day] {
let fetchRequest = NSFetchRequest(entityName: "Day")
let days: [Day]
do {
days = try CoreDataStack.sharedInstance.context.executeFetchRequest(
fetchRequest) as! [Day]
} catch let error as NSError {
NSLog("Fetch request failed: %#", error.localizedDescription)
days = [Day]()
}
return days
}
}
This is where it is called:
class SearchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var allDays: [Day]!
override func viewDidLoad() {
super.viewDidLoad()
allDays = DateFilter.getAllDays()
let backgroundView = UIView(frame: CGRectZero)
tableView.tableFooterView = backgroundView
tableView.backgroundColor = UIColor.groupTableViewBackgroundColor()
}
}
Xcode instruments detect a memory leak when this is called. Supposedly the responsible library is libswiftFoundation.dylib, and the responsible frame is static Array<A>._forceBridgeFromObjectiveC<A>(NSArray, result:inout [A]?) -> (). When I look at Cycles & Roots, it shows an NSArray at the root, with +16 list: (null) and +24 [no ivar]: (null) branching off.
Am I doing something wrong with how I store the results of my fetch request? Or is this a bug in how Swift interacts with Core Data?
Edit: Tidied up code in accordance with Mundi's suggestion.
Edit 2: Added code that calls the fetch request functions.
After trying lots of things, I'm pretty sure it's a bug in how Core Data converts NSArray to Swift Arrays when fetching my Day entities. Perhaps it has to do with the relationships or attributes of the Day entities. I'll continue to look into it.
For now, I found a work around. Instruments kept pointing back to the libswiftFoundation method for converting NSArray to Array, and the Cycles & Roots kept showing an NSArray with no ivar. Based on my research this has to do with the initialization of the NSArray created by the fetch request, which is converted behind the scenes to a Swift array. Since I can't change this, I made a new Array from the fetch results:
override func viewDidLoad() {
super.viewDidLoad()
let fetchResults: [Day] = DateFilter.getAllDays()
allDays = fetchResults.map({$0})
let backgroundView = UIView(frame: CGRectZero)
tableView.tableFooterView = backgroundView
tableView.backgroundColor = UIColor.groupTableViewBackgroundColor()
}
And magically, the memory leak is gone! I'm not entirely sure why the fetchResults array is leaky, but that seems to be the source of the problem.
Just experienced this exact same issue. I went around adding weak self to every capture list, to no avail. Turned out to be a name space collision where the system failed to infer which array (or maybe its type) I was talking about (because they shared the same name). I renamed the below 'foundCategories' from 'categories' which was a property of my view controller.
func fetchCategoriesAndNotes() {
categories = []
fetchEntity("Category", predicates: nil) { [weak self] (found) -> Void in
guard let foundCategories = found as? [Category] else { return }
for category in foundCategories {} ... } }
Memory leak is gone.
I think your fetch code is overly verbose. In particular, I think that assigning the fetch result to another variable causes some sort of conversion from Objective-C class NSArray (which is the result type of a fetch request) which in some way causes your leak. (I also do not fully understand why but I think it also has to do with the fact that this is a class function defining variables.)
I would suggest simplifying your code.
let request = NSFetchRequest(entityName: "Day")
do { return try context.executeFetchRequest(request) as! [Day]) }
catch { return [Day]() }

over writing mangedObjectContext - data dissapearing

The setup is this:
I have a table view that populates with two different sets of data from two different methods located in the same class.
When I run the first set of data by itself, it works great.
When I instantiate the second method that is suppose to grab data based on an ID within the first set of data which is pulled with each loop of the "cellForRowAtIndexPath" , it breaks as if I tried to call on data that no longer exists.
If feels like somewhere in the process the first data set is being replaced by the second data set.
So, the below works.
import Foundation
import UIKit
import CoreData
class LendersViewController: UIViewController {
var lenders = Array<Lenders>()
let lendersModel = LendersModel()
//MARK: VC Delegate Methods
override func viewDidLoad() {
// Get the data from CoreData and Put it into a variable that I can get to later.
self.lenders = lendersModel.readLenderData() as Array
}
}
extension LendersViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//return lenderData.count!
return self.lenders.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
// Set the cell to reference the LendersViewCell class below.
// This contains all the connections to the cell.
let cell: LendersViewCell = tableView.dequeueReusableCellWithIdentifier("LenderCell") as LendersViewCell
let lendersNSArray = lenders as NSArray
cell.cellLenderName.text = lendersNSArray[indexPath.row].valueForKey("corpName") as String?
return cell
}
}
class LendersViewCell: UITableViewCell {
#IBOutlet weak var cellLenderLogo: UIImageView!
#IBOutlet weak var cellAprValue: UILabel!
#IBOutlet weak var cellDownValue: UILabel!
#IBOutlet weak var cellMonthlyValue: UILabel!
#IBOutlet weak var cellFavButtonLabel: UIButton!
#IBOutlet weak var cellLenderName: UILabel!
}
But as soon as I modify the "cellForRowAtIndexPath" with the below, which calls on the second method for its data set, into cellForRowAtIndexPath ...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
// Set the cell to reference the LendersViewCell class below.
// This contains all the connections to the cell.
let cell: LendersViewCell = tableView.dequeueReusableCellWithIdentifier("LenderCell") as LendersViewCell
let lendersNSArray = lenders as NSArray
cell.cellLenderName.text = lendersNSArray[indexPath.row].valueForKey("corpName") as String?
//Adding the below two lines causes an error.
var getLenderId = lendersNSArray[indexPath.row].valueForKey("lenderId") as? String
self.lenderUserComments = lendersModel.readUserCommentsData(getLenderId!)
return cell
}
The first iteration works but the second iteration throws this error.
"fatal error: unexpectedly found nil while unwrapping an Optional value"
The error here isn't the problem though. I get that the value "getLenderId" is nil and unwrapping it as nil is throwing the error.
The problem is that the data set from the first method disappears after I instantiate the second method.
Couple points.
Both methods are in the same class but read data from two different Core data tables.
I am using NSFetchedResultsControllerDelegate in that class and that is how the data is manages prior to sending it as an array to the processes making the request.
I searched the site but came across lots of unrelated topics. I am sure that someone has run into this before so if this has been asked with greater clarity a link to the better question would be appreciated.
Thanks all,
p.s. Here is the model class with the methods that are being called.
import Foundation
import CoreData
import UIKit
class LendersModel:NSObject, NSFetchedResultsControllerDelegate{
//MARK: - Variables
// *** Setup the params used to work with the database.
private let entityName:String = "LenderUserComments"
private let entityName2:String = "Lenders"
private let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
//Reads ALL lender data
func readLenderData()-> Array<Lenders>{
// Setup database connection and save to context.
var context:NSManagedObjectContext = appDel.managedObjectContext
var request = NSFetchRequest(entityName: entityName2)
request.returnsObjectsAsFaults = false;
// Get the data and put it into a variable
var results = context.executeFetchRequest(request, error: nil)
return results as Array<Lenders>
}
// Reads User's comments on given data.
func readUserCommentsData(lenderId:String)-> Array<LenderUserComments>{
// Setup database connection and save to context.
var context:NSManagedObjectContext = appDel.managedObjectContext
var request = NSFetchRequest(entityName: entityName)
request.returnsObjectsAsFaults = false;
request.predicate = NSPredicate(format: "lenderId = %#", lenderId)
// Get the data and put it into a variable
var results = context.executeFetchRequest(request, error: nil)
return results as Array<LenderUserComments>
}
....
}
Added at the request of the a commenter.
var managedObjectContext: NSManagedObjectContext {
let coordinator = self.persistentStoreCoordinator
_managedObjectContext = NSManagedObjectContext()
_managedObjectContext!.persistentStoreCoordinator = coordinator
return _managedObjectContext!
}
Solution :I replaced the managedObjectContext variable in AppDelegate.swift. Credits to Hashmat Khalil for providing the core of the solution. The below works like a charm.
var managedObjectContext: NSManagedObjectContext {
if (_managedObjectContext != nil)
{
return _managedObjectContext!; // making sure you don't reinitialise
}
let coordinator = self.persistentStoreCoordinator
_managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
_managedObjectContext!.persistentStoreCoordinator = coordinator
return _managedObjectContext!
}
so now looking at your code every time you access appDel.managedObjectContext you are creating a new instance. when the old instance is gone you won't be able to access the data as well and you have to refetch everything. so make sure that you use only one instance. and it would be better for GUI context to have main concurrency.
var managedObjectContext: NSManagedObjectContext {
if (_managedObjectContext)
{
return _managedObjectContext; // making sure you don't reinitialise
}
let coordinator = self.persistentStoreCoordinator
_managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
_managedObjectContext!.persistentStoreCoordinator = coordinator
return _managedObjectContext!
}
that's it.

Core Data to create TableViewCells Swift

So I am trying to create a TableViewCell with Core Data, but when defining the cells, they all turn in the last input at the Core Data. The app is taking the user textfield input and turning into the table view cell label, and the zipInStr to the TableViewCell detail.
This is the function that add the values to the CoreData:
#IBAction func createFav(sender: AnyObject) {
//Defining variables to save at core data
var newTextInput = textInput.text
var trimmNewTextInput = newTextInput.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
var zipInStr: String = zipCode.text!
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var newFav = NSEntityDescription.insertNewObjectForEntityForName("Cells", inManagedObjectContext: context) as NSManagedObject
newFav.setValue(zipInStr, forKey: "favsDictKey")
newFav.setValue(trimmNewTextInput, forKey: "favsDictValues")
context.save(nil)
println(newFav)
textInput.text = String()
}
}
And this is the function that is creating the TableViewCells:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Cells")
request.returnsObjectsAsFaults = false
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
var results: NSArray = context.executeFetchRequest(request, error: nil)!
println(results)
if (results.count == 0){
println("Error")
} else {
for res in results{
cell.textLabel?.text = res.valueForKey("favsDictValues") as? String
cell.detailTextLabel?.text = res.valueForKey("favsDictKey") as? String
}
}
return cell
}
I am pretty sure the error have something to do with the loop, since when I print the results I can see all the inputs with its respective values
Your setup is not correct. You should employ a NSFetchedResultsController (see Xcode template for easy code to copy) - this is the best way to populate a table view with Core Data.
In cellForRowAtIndexPath you simply retrieve the correct item for the index path. Don't put fetch request into this method, as it is potentially called many times (e.g. while scrolling).
let cell = self.fetchedResultsController.objectAtIndexPath(indexPath) as Cells
BTW, "Cells" seems to be an inappropriate name for an entity. You need a singular that describes the data you are displaying. Perhaps, "Favorite"?
As per my comment, to get your current setup working, make results into a stored property. Add
var results : NSArray = NSArray()
at the beginning of your class definition. Then move the code to populate the array from your cellForRowAtIndexPath to viewDidLoad:
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Cells")
request.returnsObjectsAsFaults = false
results = context.executeFetchRequest(request, error: nil)!
println("\(results)")
Now, the tableView will call cellForRowAtIndexPath multiple times, once for each row that it wants to display. So the code in that method needs to configure it with the data for the relevant row. The indexPath provided as an argument to the call indicates which row (and section, if you break your table view into sections) the tableView is requesting. So your code does not need to loop through all the values in results - it just needs to reference the correct element of the array.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let res = results[indexPath.row] as NSManagedObject
cell.textLabel?.text = res.valueForKey("favsDictValues") as? String
cell.detailTextLabel?.text = res.valueForKey("favsDictKey") as? String
return cell
}
That should get your tableView working (unless I've messed up some optionals or casts - if so, sorry, I'm still getting up to speed with Swift).
This approach will work fine provided your data is relatively static (i.e. doesn't change whilst the tableView is being displayed) and provided you don't want to break the table into sections. In either of these situations (and, some would say, in any case), you should use an NSFetchedResultsController. That will automatically keep track of how many (and which) items appear in each section, etc, and can also be configured to automatically update the tableView if new items are added to the data whilst it is being displayed.

How do I use NSManagedObject and get all the data back?

I'm having trouble getting my NSManagedObject to work.
In one file I have:
import UIKit
import CoreData
#objc(Location)
class Location: NSManagedObject {
#NSManaged var title:String
#NSManaged var lat:NSDecimalNumber
#NSManaged var lon:NSDecimalNumber
#NSManaged var course:NSDecimalNumber
#NSManaged var alt:NSDecimalNumber
}
In a TableView class I have:
...
var locations:NSArray = [Location]()
override func viewDidLoad() {
super.viewDidLoad()
locations = self.getAllLocations()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var logCell:TableViewCell = self.tableView.dequeueReusableCellWithIdentifier("loggedCell") as TableViewCell
logCell.loggedTitle.text = locations[indexPath.row].title
if let lat = locations[indexPath.row].lat {
println(lat)
}
return logCell
}
func getAllLocations() -> [Location] {
let appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Locations")
request.returnsObjectsAsFaults = false
if let results = context.executeFetchRequest(request, error: nil) {
return results as [Location]
} else {
// Failed, return empty error. Alternatively, report error.
return []
}
}
...
I am able to retrieve the values from CoreData. If I println( locations[indexPath.row] ) it's all there. However in func tableView I am only able to get the title. lat, lon, everything but title errors out with 'AnyObject' does not have a member named 'lat', as in the lines:
if let lat = locations[indexPath.row].lat {
println(lat)
}
I'm sure it's something basic as I am new to Swift and iOS development. I would appreciate a point in the right direction please.
It's partly about Swift's strict typing and partly about the interplay between Swift types and Cocoa (Objective-C) types.
The problem here is that you have typed locations as an NSArray. That's a Cocoa collection, not a Swift collection. Swift doesn't know what kind of thing is inside an NSArray.
You may know (or believe) that what's at locations[indexPath.row] is a Location, therefore, but Swift doesn't know that. You have to tell it, by casting (with as).
Alternatively, if there is no reason not to do so, type locations as a Swift array of Location, a [Location]. Now Swift knows what's in it. I should also point out that as things stand, your initializer is pointless:
var locations:NSArray = [Location]()
You start by making a Swift array of Location, yes, but then by typing locations as an NSArray and assigning into it, you throw away all knowledge that this thing is supposed to contain only Location objects.

Passing an object from one viewcontroller to another viewcontroller got nil in swift

Recently, I'm learning about CoreData in Swift. My purpose is about to send the value of one object "editContact" in class "AllContactTableViewController" as code below.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let appDelegate:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedObjectContext:NSManagedObjectContext = appDelegate.managedObjectContext!
let entity = NSEntityDescription.entityForName("Contact", inManagedObjectContext: managedObjectContext)
var request = NSFetchRequest(entityName: "Contact")
request.returnsObjectsAsFaults = false
var results: NSArray = managedObjectContext.executeFetchRequest(request, error: nil)!
let editContactView : EditContactTableViewController = EditContactTableViewController()
var editContact : Contact = results.objectAtIndex(indexPath.row) as Contact
editContactView.editContact = editContact
println("\(editContactView.editContact)")
}
to another viewcontroller called "EditContactTableViewController" (as code below)
class EditContactTableViewController: UITableViewController {
var editContact : Contact!
override func viewDidLoad() {
super.viewDidLoad()
firstNameField.text = self.editContact.firstName
lastNameField.text = self.editContact.lastName
phoneField.text = self.editContact.phone
emailField.text = self.editContact.email
companyField.text = self.editContact.company
addressField.text = self.editContact.address
}
}
then it caused the error as "fatal error: unexpectedly found nil while unwrapping an Optional value"
in the log. It seems the value of object called "editContact" in this class has changed to nil.
Do you have idea how to fix this problem?
First, use a NSFetchedResultsController. There are many advantages, and you also will safely get the object you need with
self.fetchedResultsController.objectAtIndexPath(indexPath)
If your contact is displayed in the cell you must already have fetched it. So it does not make any sense to fetch it again. Also you have an unused variable (entity) in your very verbose but unnecessary code.
The best way is to use a segue in storyboard from the cell to the edit controller. You can then set up the object in prepareForSegue.
One nice setup is to subclass the table view cell and give it a property of type Contact. The displaying of the attributes in the cell can be handled by the subclass, helping you to uncluttered the table view controller. You can then easily retrieve the object in prepareForSegue from the sender parameter.

Resources