Building NSObject to save ABRecordRef data local - iphone

I want to store the Content of a ABRecordRef in my own Application independent of the entry in the AdressBook. After crossreading through stackoverflow and the apple developers site i found this to be the best way:
If got that right, one would need a NSCoding conform NSObject subclass with all values of ABRecordRef and at least the functions initWithABRecordRef and getABRecordRefFromPeopleData (assuming one names his class peopleData), where initWithABRecordRef would fill all values from an ABRecordRef (if not null) into an instance of the own class and getABRecordRefFromPeopleData returns an ABRecordRef opaque type with the Data stored in an instance of the own class.
My question to that:
I wonder if someone out there already did that, because I can imagine, other people came to the exact same problem before. If so, getting hands on that class would be aewsome, if not i am going to give it a try on my own and load the results up here if wanted.
Maybe you even know a better way to do that?
If you did implement a solution, please share it. If you need the same, I'd appreciate working that out together.
Edit:
I now started working on the thing and (as i expected) i ran into some unclear problems.
As it comes to kABStringPropertyType values my concept is pretty straight forward. Small example:
#implementation RealRecord
#synthesize firstName; //NSString
- (id)initWithRecordRef:(ABRecordRef)record{
//Single valued entrys
NSString *contactName = (NSString *)ABRecordCopyValue(record, kABPersonFirstNameProperty);
firstName=contactName;
[contactName release];
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder{
//single value entrys
self.firstName= [aDecoder decodeObjectForKey:#"firstName"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:firstName forKey:#"firstName"];
}
- (ABRecordRef)returnABRecordRefFromRealRecord{
ABRecordRef returnRecord =ABPersonCreate();
ABRecordSetValue(returnRecord, kABPersonFirstNameProperty, firstName, NULL);
return returnRecord;
}
This works fine. Now im not so clear with how to do the same thing with the kABMultiStringPropertyType.I made me a NSMutableArray emailList and went on like this:
- (id)initWithRecordRef:(ABRecordRef)record{
//Multi valued entrys
ABMutableMultiValueRef email=ABRecordCopyValue(record, kABPersonEmailProperty);
for (int i = 0; i < ABMultiValueGetCount(email); i++) {
NSString *anEmail = [(NSString*)ABMultiValueCopyValueAtIndex(email, i) autorelease];
[emailList addObject:anEmail];
}
return self;
}
Now im not shure, how to do the rest of the tasks on the multi values. Can i just encode and decode the NSMutableArray like i did with the Strings or do i have to set a key for all of the emails in emailList?
And how do i get the damn thing back into the returnRecord?

Look into using libsqlite3.dylib and creating a sql database that will access the flat files generated for all the properties.
Sample iOS project with sqlite3 library is here:
http://www.techotopia.com/index.php/An_Example_SQLite_based_iOS_7_Application
&
Detail on doing so with ABAddressbook for contact's multi-value properties here:
http://linuxsleuthing.blogspot.com/2012/10/addressing-ios6-address-book-and-sqlite.html

Related

Navigating plists and settings objects

I have a resource file (plist) added to my project. I am currently coding some sort of a "helper" for reading and writing to it. I have 3 get and 3 set methods. First one returns an object at the top, second one returns object which is inside of another dictionary (see code) and the third one returns an object at any given depth I just have to specify the node names so it can get there. (I hope you can understand me)
The problem comes with setters. Setting an "surface" object is no big deal so is setting an object that is in another dictionary. The problem comes when I try to set an object at a depth.
Before I write anything else I will post the code so you can understand what I'm saying.
fullContent is a NSMutableDictionary containing the file.
//This one is easy, just return the object for the key.
- (id)getSurfaceObjectForKey:(NSString*)key
{
return [fullContent objectForKey:key];
}
//Hope you understand this one. Main parent is a string with the name of the first node. It gets a dictionary out of my plist and returns an object for key (I have a dictionary structured plist)
- (id)getMainParentChildObjectForKey:(NSString*)key
{
NSAssert(!mainParent, #"Main parent must not be nil");
return [[fullContent objectForKey:mainParent] objectForKey:key];
}
//This one gets the element at any given depth I just have to pass in an array containing node names
- (id)getObjectForKey:(NSString *)key atDepthWithChildren:(NSArray *)children
{
id depthElement = fullContent;
for (int i = 0; i < children.count; i++)
depthElement = [depthElement objectForKey:[children objectAtIndex:i]];
return [depthElement objectForKey:key];
}
//Sets a top (surface) object
- (void)setSurfaceObject:(id)object ForKey:(NSString *)key
{
[fullContent setObject:object forKey:key];
[self writePlistContent];
}
//Sets an object inside a dictionary (mainParent - string with the name of dictionary node)
- (void)setMainParentChildObject:(id)object forKey:(NSString *)key
{
[[fullContent objectForKey:mainParent] setObject:object forKey:key];
[self writePlistContent]; //Self explanatory. I write this to file
}
//This is where my problem comes. How do I save this to plist without making any other changes to it? Im guessing I have to rebuild it from inside up?
- (void)setObject:(id)object forKey:(NSString *)key atDepthWithChildren:(NSArray *)children
{
id depthElement = fullContent;
for (int i = 0; i < children.count; i++)
depthElement = [depthElement objectForKey:[children objectAtIndex:i]];
[depthElement setObject:object forKey:key]; //I set the desired object but I dont know how to save it
for (int i = 0; i < children.count - 1; i++)
{
//Here i guess i would have to build the NSDictionary from inside out. Using a NSMutable array perhaps?
}
}
I hope you understand my problem. I hope Im not complicating things too much. Im just really tired and have been up for nearly 24 hours now and cant think of a way to solve this.
Thank you in advance.
I don't understand why you don't just use your:
[self writePlistContent];
to save it.
Surely it will save the entire contents of the plist.

How to add userInfo to a UIAlertView?

I would like to know how to add a userInfo object, or any NSDictionary, to a UIAlertView?
Thank you.
You could try subclassing UIAlertView to add the field, or store a reference in your delegate class instead. But the most general way to attach any object to any other object is to use objc_setAssociatedObject.
To do so, you have to #import <objc/runtime.h>, and you need an arbitrary void * that is used as a key (the usual trick is to declare a static char fooKey; in your .m file and use its address). Then you can attach the object like this:
objc_setAssociatedObject(alertView, &fooKey, myDictionary, OBJC_ASSOCIATION_RETAIN);
In this case, myDictionary will be retained for the life of the alertView and will be released when the alertView is deallocated.
To retrieve your associated object later, use the logically named objc_getAssociatedObject.
NSDictionary *myDictionary = objc_getAssociatedObject(alertView, &fooKey);
It returns nil if there was no association set. To break the association, just use objc_setAssociatedObject to associate a new value for the same object and key; nil can be used to break the association without associating a new object.
Other values for the type of association besides OBJC_ASSOCIATION_RETAIN are listed here.
I wrote a (well-tested) category on NSObject that gives every object the capability to easily store data.
Just put the code in a header and implementation file and import it in any of your projects. Or put it in a static library. Mac OS X 10.6+ and iOS (version?) only.
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface NSObject (CCFoundation)
- (id)associativeObjectForKey: (NSString *)key;
- (void)setAssociativeObject: (id)object forKey: (NSString *)key;
#end
#pragma mark -
#implementation NSObject (CCFoundation)
static char associativeObjectsKey;
- (id)associativeObjectForKey: (NSString *)key {
NSMutableDictionary *dict = objc_getAssociatedObject(self, &associativeObjectsKey);
return [dict objectForKey: key];
}
- (void)setAssociativeObject: (id)object forKey: (NSString *)key {
NSMutableDictionary *dict = objc_getAssociatedObject(self, &associativeObjectsKey);
if (!dict) {
dict = [[NSMutableDictionary alloc] init];
objc_setAssociatedObject(self, &associativeObjectsKey, dict, OBJC_ASSOCIATION_RETAIN);
} [dict setObject: object forKey: key];
}
#end
Simply put, every object becomes an easy-to-use dictionary (thanks to NSMutableDictionary) as soon as you need one. The dictionary is released when the object is and the dictionary's objects are released when the dictionary is released. It's amazing how Apple made this simple.
Warning 1: The code above is ARC enabled. It's well-tested and is used in several shipped products. I didn't see yet any memory leaks or performance issues.
Warning 2: Rename the methods as you wish, but if you choose to keep the name, make sure you add a prefix. This is a category on a root object, people. Some class somewhere is using this method name and you don't want to interfere! My static library which I include in every project uses the method names associativeCCObjectForKey: and setAssociativeCCObject:forKey:.
I hope this helps anybody wanting to have a simple userInfo-like feature on every object. You're welcome! :-)
If you are on > iOS 4.0 (for blocks) and you only want one or two buttons, you could use this category I made:
https://github.com/rsaunders100/UIAlertView-Blocks
It bypasses the need to add user info since you put your click handeling function straight into the alert. e.g.
#import UIAlertView+Blocks.h
...
...
NSString* myUserInfo = #"example";
[UIAlertView displayAlertWithTitle:#"Example Alert View With Blocks"
message:nil
leftButtonTitle:#"Ok"
leftButtonAction:^{
NSLog(#"%#", myUserInfo);
}
rightButtonTitle:nil
rightButtonAction:nil];
It works for me if i give it a literal c string such as "key" instead of creating a static char
Instead, taking into account what Anomie says, you could instead do this:
objc_setAssociatedObject ( alert , (const void*)0x314 , notification , OBJC_ASSOCIATION_RETAIN ) ;
This works for anything which allows you to use arbitrary void* (observation, this function, etc) and requires no extra static variable tricks. Also, (const void*)0x314 is ALWAYS 0x314, no matter what the compiler does.
Also, Anomie, you just saved me a LOT of work in an app I'm working on right now. Thanks!
I've come up with a simpler solution that may fit in some circumstances. Because you get the NSAlertView context when the delegate gets called, I use the actual address of the object to make a tag (NSString*) which I then use to store custom values in a global or object specific NSDictionary. Here is an example:
+(NSString*)GetTag:(id)ObjectIn
{
return [NSString stringWithFormat:#"Tag-%i",(int)ObjectIn];
}
In the Delegate:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString* MyID = [CommandManager GetTag:alertView];
[CurrentActiveAlerts removeObjectForKey:MyID];
}
Calling:
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:title_text
message:#""
delegate:self
cancelButtonTitle:nil
otherButtonTitles:button_text ,nil];
CurrentActiveAlerts[[CommandManager GetTag:myAlert]] = CommandToRun;
[myAlert show];
[myAlert release];
The keys will end up looking like "Tag-226811776". Hope this helps.

Why doesn’t ABPersonViewController show any properties besides the name?

For some reason, ABPersonViewController is unwilling to display any properties aside from the name, no matter what properties are set for it to display.
I’m unable to use the AddressBookUI controllers to let a user select a contact to display, since my UI has custom requirements, otherwise I’d go that route (as Apple does in their sample project.)
Here’s the code that doesn’t work — am I missing something?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// ABContact is of my own creation
ABContact *contact = [self contactAtIndexPath: indexPath];
ABPersonViewController *viewController = [[ABPersonViewController alloc] init];
// This returns a valid ABRecordRef, as indicated by the fact that the
// controller does display the name of this contact when it is loaded
viewController.displayedPerson = contact.record;
// Copied directly from Apple’s QuickContacts sample project
NSArray *displayedItems = [NSArray arrayWithObjects:[NSNumber numberWithInt:kABPersonPhoneProperty],
[NSNumber numberWithInt:kABPersonEmailProperty],
[NSNumber numberWithInt:kABPersonBirthdayProperty], nil];
viewController.displayedProperties = displayedItems;
[self.navigationController pushViewController: viewController animated: YES];
[viewController release];
}
ABContact is from Erica Sadun's ObjC wrapper (ABContactsHelper), right?
I'm using it too and found that for some reason, Apple's ABPersonViewController somehow treats supplied ABRecordRef as contact.record differently than if you directly use C functions.
Thus this:
ABContact *person = ...;
ABPersonViewController *personVC = ...;
personVC.displayedPerson = person.record;
will load almost nothing into the controller. Sometimes first/last name, sometimes not even that. However, if you do this:
ABContact *person = ...;
ABPersonViewController *personVC = ...;
ABAddressBookRef addressBook = ABAddressBookCreate();
personVC.displayedPerson = ABAddressBookGetPersonWithRecordID(addressBook, person.recordID);
then it will load everything.
Previous answer says that ABAddressBookCreate is required for the constants to have a value, but this is already done in [ABContactsHelper addressBook] call (first thing I call in my code). This it's really puzzling where it gets lost. But the previous does work, consistently.
The key is not to release the ABAddressBookRef before displaying the view controller. I create a static instance in an +initialize method and keep it around.
The value of these constants is undefined until one of the following functions has been called: ABAddressBookCreate, ABPersonCreate, ABGroupCreate.
The above is come from apple document. I think those property constants are not valid for above reason.
This list of properties only affects displaying "properties choosing dialog" (after person has been choosen). Person contact's properties like first, middle, last names, company, etc. are always the same (in both dialogs). If you include kABPersonFirstNameProperty, kABPersonMiddleNameProperty, etc. keys in the list they are ignored, only kABPersonPhoneProperty, kABPersonEmailProperty, etc. have effect.

I have a Objective C function that takes any type of object by reference. But when i pass a NSMutableArray My function does not recognise It

I have a function That takes by reference any kind of object
-(BOOL)RemoteCall:(id**)DataClass;
in the implementation i use [*DataClass isMemberOfClass:[NSMutableArray class] to find out the type of the object. The problem is it does not work with NSMUtableArrays Does anybody have a solution to this problem ? Here is the relevant code:
Implementation:
-(BOOL)RemoteCall:(id**)DataClass
{
if([*DataClass isMemberOfClass:[NSMutableArray class] ] == YES)
{
NSMutableArray * SW =(NSMutableArray *)*DataClass;
//do something with SW
DataClass= (id**)SW;
return TRUE;
}
}
Any help and I mean anything at all will be appreciated, I'm stuck.
Method Call:
NSMutableArray * channelArray = [[NSMutableArray alloc]init]
Services * serv = [[Services alloc] init];
return [serv RemoteCall:&channelArray];
Pass by reference in Objective-C is almost never the right way.
There are a number of problems with that code.
(id**) is a pointer to a pointer to a pointer to an object. Probably not at all what you want.
YES and NO are BOOL return types; not TRUE
there is no reason in that code to be returning something by reference.
method names start with lower case letters. Arguments do, too.
There will never be an instance of NSMutableArray in an application; just subclasses
You can't tell the difference between a mutable and immutable array in the first place; check for isKindOfClass: or isMemberOfClass: for an NSMutableArray won't do you much good (it is useful, but misleading).
This is better:
-(BOOL)remoteCall: (id) dataThing
{
if([dataThing isKindOfClass:[NSMutableArray class]] == YES)
{
NSMutableArray *swArray = dataThing; // not strictly necessary, but good defensive practice
//do something with swArray
return YES;
}
return NO;
}
To be called like:
NSMutableArray * channelArray = [[NSMutableArray alloc]init]; // you'll need to release this somewhere
Services * serv = [[Services alloc] init];
return [serv remoteCall:channelArray];
Since you don't return a different array in remoteCall:, channelArray's contents will be manipulated by the method and the YES/NO return value.
If there is some reason why the above seemingly won't work for you, please explain why.
Note: The code obviously requires an NSMutableArray if you are going to muck with the contents. The isKindOfClass: could be checking for NSMutableArray or NSArray and it wouldn't matter either way. When using arrays in your code and requiring a mutable array, it is up to you to make sure the data flow is correct such that you don't end up w/an immutable array where you need a mutable array.
If you don't need to reassign your variable, then don't use this. id or NSObject * is just fine and works by reference anyway. id * or NSObject ** would be references. id ** doesn't make sense at all here.
Also, learn naming conventions (like upper/lowercase).
NSArray is a class cluster. That means that every NSArray instance is actually an instance of some subclass. Only isKindOfClass: is useful for class-membership testing with class clusters.
Also... thats horrible code - please accept this:
-(BOOL)remoteCall:(id)dataClass {
if([dataClass isKindOfClass:[NSMutableArray class]]) {
NSMutableArray *sw =(NSMutableArray *)dataClass;
return YES;
}
}
that should work.
Constructive critisism of coding: You need to adhere to coding conventions. Although your code will work... its not brilliant to read and theres a lot of unnecessary *s and such.
Function names should be camel coded with a preceeding lower-case letter as should variable names. Passing (id) into a function doesn't require *s at all. Objects you pass into a function only available throughout the scope of the method anyway and that method doesn't own it, I'm not sure what you're trying to do with all the extra *s, but just treat objects you pass into the method as if you don't own them. :)
As Eiko said before, i'd use just id and not double pointers to ID.
I'm also pretty sure that isMemberOfClass is your Problem. isMember does not check for inheritance, so you're only asking for Top level Classes. isKindOfClass is probably the better choice, as there is no guarantee that Apple doesn't use an internal subclass of NSMutableArray internally. Check the Apple Docs.
i'd write it as such:
-(BOOL)RemoteCall:(id)dataClass
{
if([dataClass isKindOfClass:[NSMutableArray class] ] == YES)
{
NSMutableArray * SW =(NSMutableArray *)dataClass;
//do something with SW
return TRUE;
}
}

iPhone SDK: updating objects in an NSArray

I have an NSArray of (Product) objects that are created by parsing an XML response from a server.
In the object, it has images, and text, and ints, URLs. etc.
There are 2 requests to the server
1: list of matching products from a search - small amount of detail
2: product details: the full details.
When the second request is parsed I am trying to update the existing object in the array.
- (void) setProduct:(Product *) _product atIndex: (int) index
{
[_product retain];
[productList replaceObjectAtIndex:index withObject:_product];
}
This doesn't seem to work as when I call update and table reloadData, the new values are not present.
Should I remove the object in the array first?
replaceObjectAtIndex: is a method of NSMutableArray. So you would need to do make your productLists a NSMutableArray to use it.
-(void)updateprevious:(int)index withArg2:(NSString *)date
{
NSLog(#"%#",date);
NSLog(#"%d",index);
for (int i=0;i < index; i++)
{
[final_X replaceObjectAtIndex:i withObject:#""];
}
}
You'll have to post more code from your data source methods. What you are doing here should work fine.
Your "retain" method is unnecessary, you're leaking _product.