I have a coreData NSManagedObject as follows:
public class Records: NSManagedObject {
#NSManaged public var uid: String
#NSManaged public var datetime: Date
}
In addition, I have a helper to retrieve the record by UID:
func getRecordByUid(uid: String) -> Records!{
do {
let fetchRequest : NSFetchRequest<Records> = Records.createFetchRequest()
fetchRequest.predicate = NSPredicate(format: "uid = %#", uid)
let result: [Records] = try container.viewContext.fetch(fetchRequest)
return result.first
} catch {
print(error.localizedDescription)
return nil
}
}
Now, in my view controller I used a core-data object as non-optional (for adding new record or editing existing record purpose) as described below:
class AddRecordViewController: UIViewController {
var container: NSPersistentContainer!
var record: Records!
var currentUid = ""
#IBOutlet weak var dateTextField: PickerBasedTextField!
override func viewDidLoad() {
super.viewDidLoad()
// initialise core data
container = NSPersistentContainer(name: "MyModel")
container.loadPersistentStores { (storeDescription, error) in
self.container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
if let error = error {
print("Unsolved error \(error.localizedDescription)")
}
}
if let existingRecord = Facade.share.model.getRecordByUid(uid: currentUid) {
record = existingRecord
} else {
record = Records(context: self.container.viewContext)
}
// datePicker
let formatter = DateFormatter()
formatter.dateStyle = .medium
dateTextField.text = formatter.string(from: record.datetime)
...
}
}
The problem is that it cause an error in dateTextField.text = ... line, because it thinks the record is optional, however it isn't a case:
(lldb) po record
▿ Optional<Records>
Fatal error: Unexpectedly found nil while unwrapping an Optional value
What should I do?
I think your code would behave much better if you wrapped the fetch with the create into one method that always returns an object.
Something like
func getOrCreateRecord(uid: String) -> Records{
var record: Records?
do {
let fetchRequest : NSFetchRequest<Records> = Records.createFetchRequest()
fetchRequest.predicate = NSPredicate(format: "uid = %#", uid)
let result: [Records] = try container.viewContext.fetch(fetchRequest)
record = result.first
} catch {
print(error.localizedDescription)
}
return record ?? Records(context: container.viewContext)
}
There still might be an issue with the text field but I still think it makes sense to create a wrapper method for this logic.
I think that dateTextField is probably nil, and the fatal error is related to it. Either that, or Records(context: self.container.viewContext) is a failable initializer that returns a nil object in some cases.
Related
I am trying to convert my below fetch request code from core data to generic type.
let request = NSPredicate(format: "name == %# AND password == %# AND type == %#", "admin", "admin", "admin")
let fetchReq : NSFetchRequest = UserRegistration.fetchRequest()
fetchReq.predicate = request
let adminDetail :[UserRegistration] = DatabaseEngine.fetch(fetchRequest: fetchReq)!
Converted so far:
extension UIViewController{
class func getData<T: NSManagedObject>(req: NSPredicate) -> T{
let fetchReq : NSFetchRequest = T.fetchRequest()
fetchReq.predicate = req
return DatabaseEngine.fetch(fetchRequest: fetchReq as! NSFetchRequest<NSManagedObject>)! as! T
}
}
DatabaseEngine.fetch function.
static func fetch (fetchRequest: NSFetchRequest<T> = NSFetchRequest(), context:NSManagedObjectContext = kApplicationDelegate.managedObjectContext) -> [T]? {
let entity = NSEntityDescription.entity(forEntityName: typeName(some:T.self)
, in:context)
// Configure Fetch Request
fetchRequest.entity = entity
do {
return try context.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as? [T]
} catch {
//let fetchError = error as NSError
// return nil
}
return nil
}
But no results any more. Anybody help me to convert this code with few explaining lines. Ans will be appreciated sure.
According to my comment I recommend to use a protocol with extension for example
protocol Fetchable
{
associatedtype FetchableType: NSManagedObject = Self
static var entityName : String { get }
static var managedObjectContext : NSManagedObjectContext { get }
static func objects(for predicate: NSPredicate?) throws -> [FetchableType]
}
extension Fetchable where Self : NSManagedObject
{
static var entityName : String {
return String(describing: self)
}
static var managedObjectContext : NSManagedObjectContext {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
static func objects(for predicate: NSPredicate?) throws -> [FetchableType]
{
let request = NSFetchRequest<FetchableType>(entityName: entityName)
request.predicate = predicate
return try managedObjectContext.fetch(request)
}
}
Change (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext to the reference to your managed object context.
Make all NSManagedObject subclasses adopt Fetchable. There is no extra code needed in the subclasses.
Now you can get the data with
do {
let predicate = NSPredicate(format: ...
let objects = try MyEntity.objects(for: predicate)
} catch {
print(error)
}
That's all, objects are [MyEntity] without any type casting and always non-optional on success.
The protocol is easily extendable by default sorting descriptors, sorting directions etc.
Currently getting fatal error: unexpectedly found nil while unwrapping an Optional value when executing this line of code deliveries = employee.delievery?.allObjects as! [NSManagedObject]
I'm currently sending an employee from another tableView like this var employees : [NSManagedObject]! and prepareForSegue like this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue == "DelieverySegue"{
let employeeDeliveries = segue.destinationViewController as! DelieveryTableViewController
let indexPath = self.tableView.indexPathForSelectedRow
let selectedEmployee = employees[indexPath!.row] as! Employee
employeeDeliveries.employee = selectedEmployee
}
}
The reason why i prepareForSegue like this is because the tableView I'm segueing to has a variable var employee: Employee!
Once i go to segue it crashes at this line of code in my DeliveryTableViewController in the viewDidLoad()
deliveries = employee.delievery?.allObjects as! [NSManagedObject]
What my NSManagedObject looks like for both.
extension Employee {
#NSManaged var first: String?
#NSManaged var last: String?
#NSManaged var phoneNumber: String?
#NSManaged var wage: NSNumber?
#NSManaged var id: NSNumber?
#NSManaged var delievery: NSSet?
}
extension Delivery {
#NSManaged var address: String?
#NSManaged var phoneNumber: String?
#NSManaged var subTotal: NSNumber?
#NSManaged var total: NSNumber?
#NSManaged var employee: Employee?
}
and this is how I'm setting the relationship in my other viewController that creates a new delivery
#IBAction func saveButton(sender: AnyObject) {
let description = NSEntityDescription.entityForName("Employee", inManagedObjectContext: manageObjectContext)
let employee = Employee(entity: (description)!, insertIntoManagedObjectContext: manageObjectContext)
employee.first = first.text
employee.last = last.text
employee.phoneNumber = phoneNumber.text
employee.wage = Float(wage.text!)
employee.id = Int(employeeId.text!)
do{
try
manageObjectContext.save()
first.text = ""
last.text = ""
phoneNumber.text = ""
wage.text = ""
employeeId.text = ""
status.text = "Success!"
}catch let error as NSError{
status.text = error.localizedFailureReason
}
}
am i casting it wrong?.. i have established a relationship between Employee and Delivery already. I'm currently trying to display the deliveries of the employee i am sending when clicking on a specific employee in the EmployeeTableViewController any help regarding this would be extremely helpful thank you for your time.
What i get at run time, it fails after segueing to DeliveryTableViewController
While the source of your issue is undetermined by what code you have, your coding style is part of the problem:
deliveries = employee.delievery?.allObjects as! [NSManagedObject]
Is a VERY unsafe way to write Swift code and gives you no answers when things go wrong. Instead, consider writing it like this:
guard let delievery = employee.delivevery else {
print("Employee: \(employee)")
fatalError("No deliver object referenced by employee")
}
guard let deliveries = delievery.allObjects as? [NSManagedObject] else {
fatalError("Unexpected class type in allObjects")
}
With this change you can see if you don't have a delievery object or if there is an issue with your call to allObjects.
Continuing with this, why are you calling allObjects? Employee has a relationship to Delivery which is called delievery. This means that it returns an NSSet. Why call allObjects at all? That is just going to turn it into an Array which is just another flavor of a collection.
In any event, force downcasting in Swift is BAD and should be avoided unless you know absolutely without a doubt that you will get an object back every single time. In this situation, you do not have that guarantee and should either be using an if let or guard let to confirm what you are getting.
hey Marcus I'm gonna up load a picture of what happens when i select an employee and segue over to my DeliveryTableViewController
Ok, this is showing that even the employee can be nil and is unexpectedly nil. I am guessing you have an instance variable like this:
var employee: Employee!
Again, this is a bad habit in Swift. You should write it as a proper optional:
var employee: Employee?
Then unwrap it when you need it:
guard let empl = employee else {
fatalError("Employee not set")
}
guard let delievery = empl.delivevery else {
print("Employee: \(employee)")
fatalError("No deliver object referenced by employee")
}
deliveries = delievery
And now you will have a properly unwrapped collection of Delivery objects assigned to your instance variable and you can display them from there.
NSFetchedResultsController
Having said all of that, you should not be displaying the deliveries like this. You really should be using an NSFetchedResultsController instead. Something like this:
guard let emp = employee else {
fatalError("Employee not assigned")
}
let fetch = NSFetchRequest(entityName: "Delivery")
fetch.predicate = NSPredicate(format: "employee == %#", emp)
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetch, context: emp.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
//Finish implementing the NSFetchedResultsController
Then use the NSFetchedResultsController to display the deliveries in your UITableView.
makes sense so how would i do so if you look above in my EmployeeTableViewController prepareForSegue thats obviously not correct.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let identifier = segue.identifier else {
fatalError("Segue without an identifier")
}
switch identifier {
case "DelieverySegue":
guard let employeeDeliveries = segue.destinationViewController as? DelieveryTableViewController else {
fatalError("Unexpected view controller class")
}
guard let indexPath = self.tableView.indexPathForSelectedRow else {
fatalError("No row selected")
}
let employee = fetchedResultsController.objectAtIndexPath(indexPath)
//Use a NSFetchedResultsController here also
//let selectedEmployee = employees[indexPath!.row] as! Employee
employeeDeliveries.employee = employee
default: break
}
}
Again, I would be using an NSFetchedResultsController here as well instead of an array. This is what the NSFetchedResultsController is designed for.
However, you can still use your array if you choose. I would not recommend it.
The error here is that you are treating segue as a String when it is not. You must extract the identifier from UIStoryboardSegue object and then compare that against the identifier you are expecting.
This is my code, which builds in XCode 7.2.1.
When I try to build the project in XCode 7.3 beta 2, I got the error "Value of type 'Self.ManageableType' has no member 'uid'"
protocol Manageable {
typealias ManageableType : NSManagedObject
var uid: String { get set }
}
extension Manageable {
static func className() -> String {
return String(self)
}
static func fetchObjects(predicate: NSPredicate?,
completion:(fetchedObjects: [String: ManageableType]) -> ()) {
let entityDescription = NSEntityDescription.entityForName(className(),
inManagedObjectContext: CoreDataStack.sharedInstance.context)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entityDescription
if let p = predicate {
fetchRequest.predicate = p
}
var fetchedObjectsDict: [String: ManageableType] = [:]
do {
let result = try CoreDataStack.sharedInstance.context.executeFetchRequest(fetchRequest) as! [ManageableType]
if result.count > 0 {
for object in result {
fetchedObjectsDict[object.uid] = object
}
}
} catch {
print("ERROR FETCH MANAGEABLE OBJECTS: \(error)")
}
completion(fetchedObjects: fetchedObjectsDict)
}
}
When I try to change loop code block into:
for object in result {
let uid = object.valueForKey("uid") as! String
fetchedObjectsDict[uid] = object
}
I got the error "Ambiguous use of 'valueForKey'"
Why these errors happen here in new XCode version, help please?
Your protocol extension needs a type constraint
extension Manageable where Self : NSManagedObject, ManageableType == Self { ... }
I made an app which's using core data. I made a function which saves 1 or 2 values / write data into core data. This is the following method:
func saveName(name: String) {
let myDate:NSDate = NSDate()
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as NSManagedObject
if markCell == true {
newManagedObject.setValue(name, forKey: "markedCell")
markCell = false
}
else {
newManagedObject.setValue(name, forKey: "name")
newManagedObject.setValue(myDate, forKey: "datum")
}
// Save the context.
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
It occurs a crash in the function cellForRowAtIndexPath if markCell == true. If markCell == false (step into else) all works perfect.
If I run this function:
func saveName(name: String) {
let myDate:NSDate = NSDate()
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as NSManagedObject
newManagedObject.setValue(name, forKey: "markedCell")
markCell = false
newManagedObject.setValue(name, forKey: "name")
newManagedObject.setValue(myDate, forKey: "datum")
// Save the context.
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
no crash occurs but than I also added a value to markedCell. I only want to add a value into markedCell if the bool is set to true (the user pressed a button -> bool will be set to true and func saveNamewill be called).
Load data from core data (create UITableViewCell):
//Get task
let context = self.fetchedResultsController.managedObjectContext
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as NSManagedObject
var taskString:NSString
taskString = object.valueForKey("name") as String
cell.textLabel!.text = object.valueForKey("name") as? String
//Set accessory type
var request:NSFetchRequest = NSFetchRequest(entityName: "Person")
request.predicate = NSPredicate(format:"markedCell = %#", taskString)
var results : [NSManagedObject] = context.executeFetchRequest(request, error: nil) as [NSManagedObject]
if (results.count > 0) {
//Element exists
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
println("Cell is marked")
}
else {
//Doesn't exist
cell.accessoryType = UITableViewCellAccessoryType.None
println("Cell isn't marked")
}
I can bet that the problem comes from the fact that markedCell is declared as optional property in your Core Data model while name or/and datum are not optional.
If this is the case your saving works fine when you enter the else loop because at that point you have:
markedCell == nil //this is allowed in your Core Data model
name != nil
datum != nil
However, when you do not enter into the else loop you have:
markedCell != nil
name == nil
datum == nil
and one of the last two lines is incompatible with your Core Data model. If you want to use your original code you need to ensure that all properties mentioned here are declared as optional.
I'm building a dummy iOS project in order to understand how to implement validation in Core Data with Swift. The Core Data model of the project has one entity called Person that contains two attributes: firstName and lastName. The project is based on Swift but, in order to start it, I'm using Objective-C to define the NSManagedObject subclass:
Person.h
#interface Person : NSManagedObject
#property (nonatomic, retain) NSString *firstName;
#property (nonatomic, retain) NSString *lastName;
#end
Person.m
#implementation Person
#dynamic firstName;
#dynamic lastName;
-(BOOL)validateFirstName:(id *)ioValue error:(NSError **)outError {
if (*ioValue == nil || [*ioValue isEqualToString: #""]) {
if (outError != NULL) {
NSString *errorStr = NSLocalizedStringFromTable(#"First name can't be empty", #"Person", #"validation: first name error");
NSDictionary *userInfoDict = #{ NSLocalizedDescriptionKey : errorStr };
NSError *error = [[NSError alloc] initWithDomain:#"Domain" code: 101 userInfo: userInfoDict];
*outError = error;
}
return NO;
}
return YES;
}
#end
Person-Bridging-Header.h
#import "Person.h"
In the Core Data Model Editor, I've set the entity class inside the Data Model Inspector as indicated:
class: Person
The first time I launch the project, I create an instance of Person in the AppDelegate application:didFinishLaunchingWithOptions: method with the following code:
if !NSUserDefaults.standardUserDefaults().boolForKey("isNotInitialLoad") {
let person = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: managedObjectContext!) as Person
person.firstName = "John"
person.lastName = "Doe"
var error: NSError?
if !managedObjectContext!.save(&error) {
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isNotInitialLoad")
NSUserDefaults.standardUserDefaults().synchronize()
}
The project has one UIViewController with the following code:
class ViewController: UIViewController {
var managedObjectContext: NSManagedObjectContext!
var person: Person!
override func viewDidLoad() {
super.viewDidLoad()
//Fetch the Person object
var error: NSError?
let fetchRequest = NSFetchRequest(entityName: "Person")
let array = managedObjectContext.executeFetchRequest(fetchRequest, error:&error)
if array == nil {
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
person = array![0] as Person
}
#IBAction func changeFirstName(sender: AnyObject) {
//Generate a random firstName
let array = ["John", "Jimmy", "James", "Johnny", ""]
person.firstName = array[Int(arc4random_uniform(UInt32(5)))]
var error: NSError?
if !managedObjectContext.save(&error) {
println("Unresolved error \(error), \(error!.userInfo)")
return
}
//If success, display the new person's name
println("\(person.firstName)" + " " + "\(person.lastName)")
}
}
changeFirstName: is linked to a UIButton. Therefore, whenever I click on this button, a new String is randomly generated and assigned to person.firstName. If this new String is empty, validateFirstName:error: generates a NSError and the save operation fails.
This works great but, in order to have a pure Swift project, I've decided to delete Person.h, Person.m and Person-Bridging-Header.h and to replace them with a single Swift file:
class Person: NSManagedObject {
#NSManaged var firstName: String
#NSManaged var lastName: String
func validateFirstName(ioValue: AnyObject, error: NSErrorPointer) -> Bool {
if ioValue as? String == "" {
if error != nil {
let myBundle = NSBundle(forClass: self.dynamicType)
let errorString = myBundle.localizedStringForKey("First name can't be empty", value: "validation: first name error", table: "Person")
let userInfo = NSMutableDictionary()
userInfo[NSLocalizedFailureReasonErrorKey] = errorString
userInfo[NSValidationObjectErrorKey] = self
var validationError = NSError(domain: "Domain", code: NSManagedObjectValidationError, userInfo: userInfo)
error.memory = validationError
}
return false
}
return true
}
}
In the Core Data Model Editor, I've also changed the entity class inside the Data Model Inspector as indicated:
class: Person.Person //<Project name>.Person
The problem now is that the project crashes whenever I call changeFirstName:. The weirdest thing is that if I put a breakpoint inside validateFirstName:, I can see that this method is never called.
What am I doing wrong?
I am a little bit guessing here, but the (id *)ioValue parameter is mapped to Swift as
ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>
therefore the Swift variant should probably look like
func validateFirstName(ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>, error: NSErrorPointer) -> Bool {
if let firstName = ioValue.memory as? String {
if firstName == "" {
// firstName is empty string
// ...
}
} else {
// firstName is nil (or not a String)
// ...
}
return true
}
Update for Swift 2:
func validateFirstName(ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
guard let firstName = ioValue.memory as? String where firstName != "" else {
// firstName is nil, empty, or not a String
let errorString = "First name can't be empty"
let userDict = [ NSLocalizedDescriptionKey: errorString ]
throw NSError(domain: "domain", code: NSManagedObjectValidationError, userInfo: userDict)
}
// firstName is a non-empty string
}
As #SantaClaus correctly noticed, the validation function must now
throw an error if the validation fails.
Apple's Core Data Programming Guide is now updated for Swift 3. Here's the example code from the Managing Object Life Cycle > Object Validation page (memory has been renamed to pointee):
func validateAge(value: AutoreleasingUnsafeMutablePointer<AnyObject?>!) throws {
if value == nil {
return
}
let valueNumber = value!.pointee as! NSNumber
if valueNumber.floatValue > 0.0 {
return
}
let errorStr = NSLocalizedString("Age must be greater than zero", tableName: "Employee", comment: "validation: zero age error")
let userInfoDict = [NSLocalizedDescriptionKey: errorStr]
let error = NSError(domain: "EMPLOYEE_ERROR_DOMAIN", code: 1123, userInfo: userInfoDict)
throw error
}
EDIT: The example is not quite right. To get it to work, I've changed AutoreleasingUnsafeMutablePointer<AnyObject?> to an unwrapped optional and value?.pointee to value.pointee.