I have a small iOS application which relies on CoreData.
I have at the moment two entities, "Product" and "StorageLocation".
There is a one-to-many relationship between them.
I generated the classes manually; the Model Editor also shows the correct class name.
When trying to save a new "Product" which contains a relationship to a "StorageLocation" I get this error:
-[StorageLocation addProductsObject:]: unrecognized selector sent to instance 0x60000389d280"
The generated NSManagedObject class is as follows:
import Foundation
import CoreData
extension StorageLocation {
#nonobjc public class func fetchRequest() -> NSFetchRequest<StorageLocation> {
return NSFetchRequest<StorageLocation>(entityName: "StorageLocation")
}
#NSManaged public var id: UUID?
#NSManaged public var name: String?
#NSManaged public var products: NSSet?
}
// MARK: Generated accessors for products
extension StorageLocation {
#objc(addProductsObject:)
#NSManaged public func addToProducts(_ value: Product)
#objc(removeProductsObject:)
#NSManaged public func removeFromProducts(_ value: Product)
#objc(addProducts:)
#NSManaged public func addToProducts(_ values: NSSet)
#objc(removeProducts:)
#NSManaged public func removeFromProducts(_ values: NSSet)
}
extension StorageLocation : Identifiable {
}
Problem, as error says, is the method "addToProducts" which I invoke like this:
let newItem = Product(context: viewContext)
newItem.id = id
newItem.name = name
newItem.qty = Int32(actualQty)
newItem.expDate = expDate
location.addToProducts(newItem)
I also checked if the model matches with the classes, no error found.
Any idea/hint?
Thanks
Marco
It's hard to be sure but since you generated the file manually, it might be that it didn't get added to the app target. That would mean the code exists but isn't being compiled as part of the app. Try this:
Click on the file in the file navigator on the left side of the window.
Bring up the "file inspector" tab on the right. That's the one you get if you press cmd-opt-1.
Look in the inspector where it says "target membership". This should list the app plus any test targets, as well as app extensions if you have any.
Make sure your app's name is checked in the target membership list.
If it wasn't checked, and you check it, your code will probably work.
Related
I'm using CoreData in my SwiftUI app. To have some better handling I added some wrappers to the CoreData classes like this:
extension Category {
public var wrappedName: String {
name ?? ""
}
}
This is working fine till now. But now I need to observe this wrappers also and I use the Category as an StateObject. Therefor I wanted to Publish it like this:
extension Category {
#Published public var wrappedName: String {
name ?? ""
}
}
This is triggering the error: Non-static property 'wrappedName' declared inside an extension cannot have a wrapper
If I remove the #Published I get an error for the #StateObject:
Property type 'Category?' does not match that of the 'wrappedValue' property of its wrapper type 'StateObject'
How to solve this issue?
I'm trying to make a persistence framework-agnostic codebase. i.e. I want protocol-oriented programming, but I'm having trouble making that work with CoreData.
Say I have a protocol:
public protocol BookItem {
var title: String { get }
var filename: String { get }
var createdAt: Date { get }
var content: BookItemContent? { get }
}
and another protocol
public protocol BookItemContent {
var bookItem: BookItem { get }
var data: Data { get }
}
When I make concrete types in CoreData, I might have:
class CDBookItem: NSManagedObject, BookItem {
#NSManaged public var title: String?
#NSManaged public var filename: String?
#NSManaged public var createdAt: NSDate?
#NSManaged public var content: CDBookItemContent?
}
class CDBookItemContent: NSManagedObject, BookItemContent {
#NSManaged public var data: NSData?
#NSManaged public var bookItem: CDBookItem?
}
I have 3 issues here:
1) Now I have multiple properties with different return types. (e.g. the .content property)
How do I best solve this? The worst and dirty way is to rename all the Core-Data attributes, then wrap them to conform.
The other way I can see is to manually generate the NSManagedObject subclasses, then change the flavour that the CoreData codegen is creating, so to remove the optionality. Still, then I'm left with content: BookItemContent and content: CDBookItemContent, so I don't know if there's an elegant way to do what I'm hoping to do here. I was hoping I could just declare that the NSManagedObject subclass conforms to its intended protocol type and the compiler figures this out.
2) Core Data codegen uses 'NSObject' types, instead of their modern equivalent. (i.e. NSData and not Data, NSDate and not Date). Can I just modify the codegen files to remove the "NS" and get free bridging?
3) Is it known that CoreData always creates optional object properties in the codegen, even if you don't want them to be? Is the solution literally to overwrite the manually generated files?
In the end, I'm finding Protocol-oriented Programming is not exactly as promised. I was hoping almost an entire codebase could be done in this way. In the end the solution was to make NSManagedObject subclasses conform to a protocol by having to explicitly write accessors for the protocol's properties that wrap some attribute on the core data model. i.e. return myManagedObject.contentObject as! BookItemContent
If I go to editor > create management subclass and then let Xcode generate a subclass I always get errors.
What am I doing wrong here that I always get these errors?
extension Passport { //'Passport' is ambiguous for type lookup in this context
#nonobjc public class func fetchRequest() -> NSFetchRequest<Passport> {
return NSFetchRequest<Passport>(entityName: "Passport") //'Passport' is ambiguous for type lookup in this context
}
#NSManaged public var passportId: String? //#NSManaged only allowed on an instance property or method
#NSManaged public var person: Person? //#NSManaged only allowed on an instance property or method
}
How can is fix those errors or what do i wrong ?
You have to set the entity codegen to not to class definition but to Manual/None.
This configuration is the default Codegen configuration when you create an entity in the data model editor. With this configuration, Xcode will automatically generate the required NSManagedObject subclass as part of the project’s derived data.
I had a working Swift project under v3.2 working with the AWS APIGateway generated SDK. However, I tried converting to Swift 4.0 and now it returns no results and no errors.
Is this just unsupported currently?
I guess I found my answer on github:
https://github.com/aws/aws-sdk-ios/issues/744
The relevant quote is below and there is no response on when Swift 4 will be supported.
Currently the generated SDK is Swift 3 only.
Update #1:
I found this some time ago that provides a workaround to this problem:
https://github.com/aws/aws-sdk-ios/issues/750#issuecomment-337046816
This is happening due to change in #objc inference. See change:
SE-0160
What this implies is, the properties in Swift class are no longer
visible to ObjectiveC code and we need to explicitly make them
available. The current work around for Swift 4 would be to either have
#obj declaration before the properties to be mapped for dynamo db
table or declare all properties to be visible to ObjectiveC using
#objcMembers
Example:
Class level: #objcMembers class MyTable: AWSDynamoDBObjectModel,
AWSDynamoDBModeling {
var hashKey: String?
var attribute1: NSNumber?
public static func dynamoDBTableName() -> String {
return "MyTableName"
}
public static func hashKeyAttribute() -> String {
return "hashKey"
}
} or
Attribute level: class MyTable: AWSDynamoDBObjectModel,
AWSDynamoDBModeling {
#objc var hashKey: String?
#objc var attribute1: NSNumber?
public static func dynamoDBTableName() -> String {
return "MyTableName"
}
public static func hashKeyAttribute() -> String {
return "hashKey"
}
}
I will investigate further to see if we can mitigate this issue some other way.
I want know about extension.
Case 1.
import Foundation
import Parse
class Room: PFObject, PFSubclassing {
#NSManaged var name: String?
static func parseClassName() -> String {
return "Room"
}
}
case 2.
import Foundation
import Parse
class Room: PFObject {
#NSManaged var name: String?
}
extension Room: PFSubclassing {
class func parseClassName() -> String {
return "Room"
}
}
What is different? Both are working perfectly I just know which one is better and why?
An extension lets you add functionality to a class (to extend it) without having to subclass.
So imagine you want to add a quickAlertFunction so you can easily display a basic alert with a title, message and ok button:
func displayQuickAlert(title: String: message: String) {
// create a alert VC with title and mesage
// add the ok button/action
// present the alertVC
}
If you done this by subclassing, you would need to update all your ViewControllers to extend from your subclass, but if you done this as an extension of UIViewController you wouldn't need to update anything.
It's generally better to use an extension when you want to add general functionality to a class, if you want to add specific changes, maybe like extending a textfield and adding some autocomplete or something.. you wouldn't want these changes to be global on all Textfields, just instances of your subclass.
Also, One last thing to note.. you cannot add properties in an extension, but you can in a subclass