Convert ABRecordID to NSNumber always -1 - ios

I am trying to save an ABRecordID of an existing contact from the Address Book into Core Data. I have an Int32 defined in my model, but in order to save my object there I need to convert the ABRecordID (which is an Int32) into an NSNumber
Here is my code that converts it:
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!, property: ABPropertyID, identifier: ABMultiValueIdentifier) {
let multiValue: ABMultiValueRef = ABRecordCopyValue(person, property).takeRetainedValue()
let index = ABMultiValueGetIndexForIdentifier(multiValue, identifier)
let pickedVal = ABMultiValueCopyValueAtIndex(multiValue, index).takeRetainedValue() as! String
let theRecord = recordIdFromPersonRecord(person)
println(ABRecordGetRecordID(person!))
func recordIdFromPersonRecord (personRecord: ABRecord) -> NSNumber {
let recordId = ABRecordGetRecordID(personRecord)
println(personRecord)
return (NSNumber(int: recordId))
}
This code always prints -1 in the console, no matter what record I select in my address book. I'm a newbie programmer and I'm totally stuck.
Thanks for your help!
EDIT:
Commenter below indicated that this is a value reserved for records that do not exist in the address book database...
That is confusing. I am just browsing the AB to get the name value and the ID out of existing records... how can existing records not have a record saved to the address book database? Here is what the definition from the docs is: Records with this ID have not been saved to the Address Book database. Am I misunderstanding?

In iOS 8, you can browse the contacts without it ever requesting permission to the address book (because you might, for example, allow the user to perform the default action with ever returning actual data to the app). But to actually get the particulars on a contact, you have to explicitly request permission to do so.
It would appear that you must have used AddressBookUI.framework to present the contact UI, but never asked for permission to access the particulars about the contact.

Related

How to fetch relationship entities when performing a core data migration?

I am performing a core data migration. I have this subclass for my migration:
class TagtoSkillMigrationPolicyV1toV2: NSEntityMigrationPolicy {
override func createDestinationInstances( forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
// 1 - create destination instance of skill and skillmetadata
let skillEntityDescription = NSEntityDescription.entity( forEntityName: "Skill",in: manager.destinationContext)
let newSkill = Skill(entity: skillEntityDescription!, insertInto: manager.destinationContext)
let skillMetaDataDescription = NSEntityDescription.entity(forEntityName: "SkillMetaData", in: manager.destinationContext)
newSkill.metaData = SkillMetaData(entity: skillMetaDataDescription!, insertInto: manager.destinationContext)
var posts = sInstance.value(forKey: "posts")
posts.sort {
($0.timeStamp! as Date) < ($1.timeStamp! as Date)
}
if let mostRecentThumbnail = posts.first?.postImage {
let thumbnail = Utils.makeThusmbnail(url: URL(string: mostRecentThumbnail)!, for: 150.0)
newSkill.metaData?.thumbnail = thumbnail?.pngData()
}
if let name = sInstance.value(forKey: "name"){
newSkill.setValue(name, forKey: "name")
}
manager.associate(sourceInstance: sInstance, withDestinationInstance: newSkill, for: mapping)
}
}
This custom migration is from my old Tag entity to my new Skill entity. For each Tag, it creates a skill entity and a skillmetaData entity that it attaches to it. There are no entries in the .xcmappingmodel file, as I am doing everything manually - please let me know if I still need entries there.
Right now, when it runs, it gives a signal SIGABRT:
Could not cast value of type '_NSFaultingMutableOrderedSet' (0x7fff87c44e80) to 'NSArray' (0x7fff87c51fb0).
2020-04-14 15:55:40.108927-0700 SweatNetOffline[28046:3645007] Could not cast value of type '_NSFaultingMutableOrderedSet' (0x7fff87c44e80) to 'NSArray' (0x7fff87c51fb0).
When I set a breakpoint and inspect sInstance.value(forKey: "posts"), I get
▿ Optional<Any>
- some : Relationship 'posts' fault on managed object (0x6000010dcf00) <NSManagedObject: 0x6000010dcf00> (entity: Tag; id: 0xdf4b05c2e82b2c4e <x-coredata://A6586C18-60C3-40E7-B469-293A50EB5728/Tag/p1>; data: {
latestUpdate = "2011-03-13 00:17:25 +0000";
name = flowers;
posts = "<relationship fault: 0x600002607f40 'posts'>";
uuid = "4509C1B3-7D0F-4BBD-AA0B-7C7BC848DA80";
})
so its not entirely loading those posts - its giving that fault there.
The goal is to get the thumbnail from the latest of those posts. How can I access this relationship item?
I found a way to do it using
let lastThumbnail = sInstance.mutableOrderedSetValue(forKeyPath: "posts.postImage").lastObject
Reading this helped a lot. My understanding is at this point in the migration, we are dealing with NSManagedObjects and never the actual Model which would have attributes like "posts" that it usually does in core data.
In Objective-C especially and swift to some extent, NSObjects are accessed with key-value coding. They are accessed indirectly - meaning instead of reading the object itself, you are reading what the NSMutableOrderedSet wants to show you for that key - so it can do stuff to it before presenting it. Thus you can't for instance, fetch just "forKey: posts" and then expect posts to have the attributes your post usually does. Anyone is welcome to flesh out this explanation if they know more :).
One thing I was trying to do is to fetch by that key-path in a sortable way - so that I can find the post that has the most recent timestamp and then use that associated thumbnail. Instead I am using .lastObject which works because this is an NSMutableOrderedSet and not an NSMutableSet because my original model specified this relationship to be an ordered relationship. This means that it will pick the last inserted item which is ok for me for now. Still interested in a way to filter that result though if anyone has advice.

perform segue to bring user to chat with post creator

I try to create a button which should bring the normal user to the chatController with the creator of a post
There are a number of issues here, and really too many to address in a single answer but let me try to point you in the right direction.
Lets start with this
let user = job?.addedByUser
self.messagesController?.showChatControllerForUser(user)
so the user var is assigned from the Job Class
class Job {
var addedByUser: String!
Notice that addedByUser property is a string, therefor the userVar is a string.
However, you're passing it to a function which requires a user class, not a string.
func showChatControllerForUserCreator(_ user: User) {
Which then throws an error
Cannot convert value of type 'String?' to expected argument type
'User'
The way to fix it is to decide if a user is a String or something else. It would most likely be a UserClass that you create
class UserClass {
var uid = ""
var user_name = ""
}
at the same time you need to have your users stored in a users node
users
uid_0
user_name = "Henry"
uid_1
user_name = "Bud"
and in your Jobs class, store a reference to the user with it's uid.
This could get rather lengthy but the next step is, after you loaded your job, to then load the user from it's uid. The JobClass would be
class Job {
var addedByUid: String! //this will be the users Firebase uid
etc etc

issue when appending the RLMArray, update RLMArray in RLMObject and link it with object in swift realm 3.0

want some method like which do update the existing followers, and if not exists do add it to DB and link them to user something like
GETS CRASH OVER append in write block, due to duplicate primary key,
also, it works perfectly if no followers has been added in HKUser Table, once it comes to update it crashes
import UIKit
import RealmSwift
class HKUser: Object{
dynamic var full_name = ""
dynamic var email: String?
dynamic var user_id: String?
let followers = List<HKUser>()
override static func primaryKey() -> String? {
return "user_id"
}
}
I want to update the connection of a user in DB also, so I want to do some thing like
//1. updated the userFollower array with required data
let userFollowers:[HKUser] = []
//2. now need to link it with my user object and update it in db
if let user = realmWrapper.sharedInstance.getUser(forID: id) {
try! realm.write {
//want some method like which do update the existing followers,
//and if not exists do add it to db and link them to user something like
//realm.add(user, update: true)
user.followers.append(contentsOf: followers)
/**********
GETS CRASH OVER HERE,
due to duplicate primary key,
it works perfect if no followers has been added in HKUser Table,
once it comes to update it crashes
**********/
}
}
List<T>.append() method save to the Realm implicitly if the objects are unmanaged. That is why the duplicated primary key exception happens.
To avoid this, you can add or update the unmanaged objects before appending to the List. Then you append the objects to the List.
try! realm.write {
...
realm.add(followers, update: true) // Add or update the objects first
user.followers.append(contentsOf: followers)
...
}
Maybe it's not the case (not enough code in your question to tell) but it looks like you prepare the userFollowers array in step 1 to contain ALL the current followers for that specific user.
If this is the case then you'll end up re-adding all the existing followers not only the new ones, hence the duplicate keys.

Delete an objectID from an array in Parse via Button

I have an application setup so that there is an array that contains objectId's that the current user is friends/connected with. I'm attempting to create a button that deletes a single objectId from that array (kind of like an unfriend button). I'm currently passing the objectId value from a query in one view controller to another view controller that actually contains the friends profile and the delete/unfriend button. I'm getting the actual objectId as a string in the friends profile and I can print the objectId but I can't seem to figure out a way to delete this single objectId from the array.
Using PFUser.currentUser()?.removeObject() gives me an invalid field name error.
Anyone have an insight for me?
Let me know if there is more clarification needed! Thanks in advance.
Update
I have tried to also utilize this as my object code. userObjectId is received from another view controller and grabs the proper objectId of what I'd like to remove from the current users "accepted" array. This code gives me an error of "NSInternalInconsistencyException reason: Tried to save an object with a pointer to a new, unsaved object" I've tried to remove this objectId from the array in several different ways but can't seem to get it to stick. Thanks again.
var userObjectId = ""
var object: PFObject = PFObject(className: "User")
object["objectId"] = userObjectID
PFUser.currentUser()?.removeObjectsInArray([object], forKey: "accepted")
PFUser.currentUser()?.saveInBackground()
let object = PFObject(withoutDataWithClassName: "Your Class", objectId: "objectId from array")
object.deleteInBackgroundWithBlock { (success, error) -> Void in
if error == nil && success == true {
//Delete the objectId from the array
}
}
This will also remove the object from any array that holds it.

Magical Record import (next step)

I've put next step in the title as this is not the same problem as my previous question with almost the exact same title.
I have a Person entity.
Person
--------
name - mappedKeyName: FullName
email - mappedKeyName: EmailAddress
personID - mappedKeyName: Id
--------
photos
And a Photo entity.
Photo
--------
image
createDate - mappedKeyName: Date
photoID - mappedKeyName: Id
--------
owner (type Person) - mappedKeyName: UserId - relatedByAttribute: personID
There are other objects that relate to Person too and the JSON for these comes as so...
{
ObjectId : blah,
Owner : {
Id : 12345asdfg,
FullName : Oliver,
EmailAddress : oliver#oliver.com
}
}
With this JSON my setup works with the import. Any person records that don't exist (with the Id) are created. And any that do exist are updated.
However, the photos JSON object comes like this...
{
Id : thisIsThePhotoID,
Date : today,
UserId : 12345asdfg
}
When the objects come down like this the Magical record import stops when it gets to the person import.
The code crashes at...
- (id) MR_relatedValueForRelationship:(NSRelationshipDescription *)relationshipInfo
{
NSString *lookupKey = [self MR_lookupKeyForRelationship:relationshipInfo];
return lookupKey ? [self valueForKeyPath:lookupKey] : nil; // it stops here.
}
The value of lookupKey is #"personID".
Printing out relationshipInfo at the breakpoint gives...
$6 = 0x1fd695e0 (<NSRelationshipDescription: 0x1fd695e0>),
name owner,
isOptional 0,
isTransient 0,
entity Photo,
renamingIdentifier owner,
validation predicates (),
warnings (),
versionHashModifier (null)
userInfo {
mappedKeyName = UserId;
relatedByAttribute = personID;
},
destination entity Person,
inverseRelationship photos,
minCount 1,
maxCount 1,
isOrdered 0,
deleteRule 1
I really have no idea why this isn't working. I don't get any sensible errors to report.
MagicalRecord cannot map the relationship automatically with this JSON format:
{
Id : thisIsThePhotoID,
Date : today,
UserId : 12345asdfg
}
In order for MagicalRecord to map the relationship to a Person object, it would have to be an object in the JSON as well, for example:
{
Id : thisIsThePhotoID,
Date : today,
User : {
UserId : 12345asdfg
}
}
This way MagicalRecord knows it's an object and it will do the appropriate lookup in your existing database for the Person record with the above ID and map the relationship.
So there are two issues with this, though. If you cannot change the JSON output you have to create a category class on Photo where you manually map the relationship yourself. I'll get to that after the second issue.
The second issue is that the above JSON format assumes you already have parsed the users and stored the records in your database. If you have not MagicalRecord will create a new Person record with the above ID, but since no other attributes exist on that object (notice the UserId key is the only attribute in the dictionary) it will be fairly empty and not include the name and email address. You can always extend your JSON (if you have that possibility) to include those attributes as well in the Person dictionary inside the Photo dictionary:
{
Id : thisIsThePhotoID,
Date : today,
User : {
UserId : 12345asdfg,
FullName : Oliver,
EmailAddress : oliver#oliver.com
}
}
The JSON payload is quite small so it doesn't hurt to do that if you can. Plus it will only create a new Person record if one doesn't exist in the database already.
And then for the manual mapping. If you cannot change the JSON to the above format you have to manually override the relationship mapping as the JSON is not prepared the way MagicalRecord does mapping.
Create a category class for Photo called Photo+Mapping.h/.m. I like to stick with +Mapping for these. Then the class should be Photo (Mapping) in the header and implementation file and you're good to go.
MagicalRecord has a number of instance methods available to override (see the latter part of this article on MagicalRecord importing written by the author of MagicalRecord), among them are import<;attributeName>;: and import<;relationshipName>;:. There are also a willImport:, didImport: and shouldImport: methods on the class itself which allows you to override any mapping.
For your case you can use import<;relationshipName>;: or shouldImport:. I took these two because one has a bit of a benefit depending on whether you have already mapped all your Person objects and they're available for relationship mapping on the Photo object.
Here are the examples of what you can do (you can choose to combine a few of them if you wish, it doesn't hurt to do so). A note here: ALWAYS use the current NSManagedObjectContext when overriding mapping (easily accessible with MagicalRecord through self.managedObjectContext) otherwise you will end up with context issues.
Be sure to import Person:
#import "Photo+Mapping.h"
#import "Person.h"
// Assuming you only want to import the Photo object if you already have a Person stored this is a great method to tell MagicalRecord whether to continue with importing or not
-(BOOL)shouldImport:(id)data {
Person *person = [Person findFirstByAttribute:data[#"UserId"] value:#"personID" inContext:self.managedObjectContext];
if (!person) {
// no Person object exists so don't import the Photo object - again this is up to you since you might want to create the record if not
return NO;
}
// you can set the relationship here (you might as well) or use the importPerson: method below (doing a second lookup, which is unnecessary at this point)
[self setPerson:person];
return YES;
}
// If you use this method you're doing the lookup to check whether a record exist when MagicalRecord is trying to map the Person relationship
-(void)importPerson:(id)data {
Person *person = [Person findFirstByAttribute:data[#"UserId"] value:#"personID" inContext:self.managedObjectContext];
if (!person) {
// if no Person record exists for the associated UserId, you should create one (or not - if you choose not to, it's wise to throw away this Photo object)
person = [Person createInContext:self.managedObjectContext];
[person setPersonID:data[#"UserId"]];
}
// set the relationship
[self setPerson:person];
}
// finally you can also use the following method when MagicalRecord is done mapping and get rid of the Photo object if the Person relationship is nil:
-(void)didImport:(id)data {
if (!self.person) {
[self deleteInContext:self.managedObjectContext];
}
}
Hope this helps! Let me know if you have any questions.

Resources