NSManagedObject subclasses in Swift can not use custom accessor? - core-data

I am using Swift in Core Data generated subclass of NSManagedObject. There is a transient optional property title.(The optional is not Swift's optional, but Core Data's optional.)
So I need a custom getter. My code is
class ShoppingList: NSManagedObject {
#NSManaged var title: String
func title() -> String {
return "something"
}
}
The Objective-C version of the getter works fine. However, Xcode tells me that "the func title() is an invalid redeclaration". I tried to use computed property, but get that "#Managed property can not use computed property".
So my question is, is there an alternative way to get custom accessors(getters) in Swift version of NSManagedObject subclassing?

You could use a different name for a computed property, and have it return the title variable.
#NSManaged var title: String
var myTitle : String {
return self.title
}
Would that work for you?
Apple does it like this in many places by naming the actual var with an underscore in front, and the computed property with the same name but without the underscore

Related

NSManagedObject description in Swift

Something weird is going on with NSManagedObject.description() it prints nothing but a blank line.
import Foundation
import CoreData
#objc(MyEntity)
class MyEntity: NSManagedObject {
#NSManaged var title: String
}
Then I create an Object and set its title. When I call println("\(myObject)") it will print a blank line instead of <xSomEtHinG : MyEntity>
(the object es creates and persists ok. println("\(myObject.title)") works like expected)
Any ideas?
You can create Extension and override description property as you want.
extension MyEntity {
override public var description: String {
return "Title= \(title)"
}
}

How to define CoreData relationship in Swift?

In CoreData, I have defined an unordered to-many relationship from Node to Tag. I've created an Swift entity like this:
import CoreData
class Node : NSManagedObject {
#NSManaged var tags : Array<Tag>
}
Now I want to add a Tag to an instance of Node, like this:
var node = NSEntityDescription.insertNewObjectForEntityForName("Node", inManagedObjectContext: managedObjectContext) as Node
node.tags.append(tag)
However, this fails with the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for to-many relationship: property = "tags"; desired type = NSSet; given type = _TtCSs22ContiguousArrayStorage000000000B3440D4; value = (
"<_TtC8MotorNav3Tag: 0xb3437b0> (entity: Tag; id: 0xb343800 ; data: {...})"
).'
What is the correct type for to-many relationships?
To be able to work with one-to-many relationship in Swift you need to define property as:
class Node: NSManagedObject {
#NSManaged var tags: NSSet
}
If you try to use NSMutableSet changes will not be saved in CoreData. And of course it is recommended to define reverse link in Node:
class Tag: NSManagedObject {
#NSManaged var node: Node
}
But still Swift cannot generate dynamic accessors in runtime, so we need to define them manually. It is very convenient to define them in class extension and put in Entity+CoreData.swift file. Bellow is content of Node+CoreData.swift file:
extension Node {
func addTagObject(value:Tag) {
var items = self.mutableSetValueForKey("tags");
items.addObject(value)
}
func removeTagObject(value:Tag) {
var items = self.mutableSetValueForKey("tags");
items.removeObject(value)
}
}
Usage:
// somewhere before created/fetched node and tag entities
node.addTagObject(tag)
Important: To make it all work you should verify that class names of entities in you CoreData model includes your module name. E.g. MyProjectName.Node
As of Xcode 7 and Swift 2.0, the release note 17583057 states:
The NSManaged attribute can be used with methods as well as
properties, for access to Core Data’s automatically generated
Key-Value-Coding-compliant to-many accessors.
#NSManaged var employees: NSSet
#NSManaged func addEmployeesObject(employee: Employee)
#NSManaged func removeEmployeesObject(employee: Employee)
#NSManaged func addEmployees(employees: NSSet)
#NSManaged func removeEmployees(employees: NSSet)
These can be declared in your NSManagedObject subclass. (17583057)
So you just have to declare the following methods and CoreData will take care of the rest:
#NSManaged func addTagsObject(tag: Tag)
#NSManaged func removeTagsObject(tag: Tag)
#NSManaged func addTags(tags: NSSet)
#NSManaged func removeTags(tags: NSSet)
Actually you can just define:
#NSManaged var employees: Set<Employee>
And use the insert and remove methods of the Set directly.
Building on #Keenle's answer, if you want to be cheeky and concise and be able to say
node.tags.append(tag)
one can wrap the call to self.mutableSetValueForKey:
class Node: NSManagedObject {
var tags: NSMutableOrderedSet {
return self.mutableOrderedSetValueForKey("tags")
}
}

Faking enums in Entity Framework 4.0

There are a lot of workarounds for the missing support of enumerations in the Entity Framework 4.0. From all of them I like this one at most:
http://blogs.msdn.com/b/alexj/archive/2009/06/05/tip-23-how-to-fake-enums-in-ef-4.aspx?PageIndex=2#comments
This workaround allows you to use enums in your LINQ queries which is what i exactly need. However, I have a problem with this workaround. I get for every complex type I'm using a new partial autogenerated class.Therefore the code does not compile any more because I already have a wrapper class with this name in the same namespace which converts betwen the backed integer in the database and the enum in my POCO classes. If I make my wrapper a partial class, the code still does not compile as it now contains two properties with the same name "Value". The only possibility is to remove the Value property by hand everytime I generate the POCO classes because the DB model changed (which during the development phase happens very often).
Do you know how to prevent a partial class to be generated out of complex property everytime the EF model changes?
Can you recommend me some other workarounds supporting enumerations in LINQ queries?
That workaround is based on the fact that you are writing your POCO classes yourselves = no autogeneration. If you want to use it with autogeneration you must heavily modify T4 template itself.
Other workaround is wrapping enum conversion to custom extension methods.
public static IQueryable<MyEntity> FilterByMyEnum(this IQueryable<MyEntity> query, MyEnum enumValue)
{
int val = (int)enumValue;
return query.Where(e => e.MyEnumValue == val);
}
You will then call just:
var data = context.MyEntitites.FilterByMyEnum(MyEnum.SomeValue).ToList();
I am using an approach based on the one described in your link without any modifications of the T4 templates. The contents of my partial wrapper classes are as follows:
public partial class PriorityWrapper
{
public Priority EnumValue
{
get
{
return (Priority)Value;
}
set
{
Value = (int)value;
}
}
public static implicit operator PriorityWrapper(Priority value)
{
return new PriorityWrapper { EnumValue = value };
}
public static implicit operator Priority(PriorityWrapper value)
{
if (value == null)
return Priority.High;
else
return value.EnumValue;
}
}
I've only changed that instead of a back store variable with enum value I am using the autogenerated int typed Value property. Consequently Value can be an auto-implemented property and EnumValue property needs to do the conversion in getter and setter methods.

Validating C# class fields using custom Attributes

class Employee
{
[ValueNotEmpty("Empty strings not allowed"]
public string Name{get;set;}
}
"ValueNotEmpty" is a custom attribute.I tried using YABOV library but i have to call Validate method of the base class explicitly to validate the custom attributes and return the validation messages.
In the implementation class, i use reflection to set values on "Name" field.When i set value on "Name" field i expect the try block to throw an exception saying "Empty strings not allowed".Is there any way i can do this without explicitly calling a method to validate the class when i set value for the field?.
A few thoughts to this problem:
If your property calls a PropertyChanged event, there might be a mechanism which uses this to validate it. For instance in combination with data binding. If you don't have PropertyChanged, you need to call Validate somewhere explicitly, unless you're using AOP.
Executing code when you just set a value would require AOP. In .Net standard libraries, there is no AOP technology included, you would have to integrate one (eg. Spring). AOP requires code generation or byte-code enhancement. So it's not a trivial thing.
You could try inheritiing from IDataErrorInfo and the implementing as below, but I suppose that this is what you mean by calling the validation explicitly.
public class Employee : IDataErrorInfo
{
public string Name{get;set;}
string IDataErrorInfo.Error {get {return null;}}
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName);}
}
string GetValidationError(string propertyName)
{
string error = null;
switch(propertyName)
{
case "Name":
error = ValidateName();
break;
default:
error = "Unknown proeprty";
break;
}
}
string ValidateName()
{
if(!string.IsNullOrEmpty(this.Name))
{
return null;
}
return "Empty Name";
}
}

AllowMultiple does not work with Property Attributes?

I'm tying to collect all Custom Attributes placed over a Property. There are more than one Attributes of the same type assigned to the Property, but when collecting them , the resulting collection only contains the first Attribute of the specific type:
The Attribute class
[AttributeUsage(System.AttributeTargets.Property,
AllowMultiple = true)]
public class ConditionAttribute : Attribute{...}
Usage:
[ConditionAttribute("Test1")]
[ConditionAttribute("Test2")]
[ConditionAttribute("Test3")]
public Color BackColor{get; set;}
Now when looping through all Props of the object 'value' whose class contains the Prop "BackColor":
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(value))
{
foreach (Attribute attribute in property.Attributes)
{ ... }
....
}
the collection property.Attributes only contains ONE Attribute of type "ConditionAttribute" : The one with "Test1". The others are ignored;-(
So does AllowMultiple not work for Property Attributes ?
Thanks in advance
henrik
According to a post on MSDN, this is by design as part of the PropertyDescriptor class.
However, you can actually solve the problem by overriding TypeId in your custom attribute (Thanks to Ivan from Mindscape for pointing this out):
public override object TypeId
{
get
{
return this;
}
}
Yes, it does work. Not sure why it does not work via PropertyDescriptors.
You can always do: Attribute.GetCustomAttributes(methodInfo, typeof(ConditionAttribute))
Another way to tweak this,
[ConditionAttribute("Test1,Test2,Test3")]
public Color BackColor{get; set;}
and in your validation code,
Dim lstProperties() As String = _ChkColors.Split(",")
For each strProp as string in lstPropertyes
' your validation
' return
Next

Resources