How to implement sort functionality same as AddressBook? - objective-c

In my app, I have list of contacts which are displayed in ascending order.When user clicks on any alphabet say 'b' then the list should scrolls to the contact starting from 'b'.Is this built-In functionality of AddressBook?Can anyone knows how I can achieve this?
Thanks in advance!

My pretty dirty method. It sorts by email, first name and last name omitting middle name cause I didn't needed that one. Oh and it finds only those contacts which have email address. You can avoid that if you slightly edit code starting with if (ABMultiValueGetCount(emailRef))
Your view controller:
- (NSArray *)sortedContactsFromPeople:(CFArrayRef)people {
NSMutableArray *contacts = [NSMutableArray array];
for (int i = 0; i < CFArrayGetCount(people); i++) {
ABRecordRef record = CFArrayGetValueAtIndex(people, i);
ABMultiValueRef emailRef = ABRecordCopyValue(record, kABPersonEmailProperty);
CFStringRef email;
if (ABMultiValueGetCount(emailRef)) {
BOOL hasValidEmail = NO;
for (int j = 0; j < ABMultiValueGetCount(emailRef); j++) {
if (!hasValidEmail) {
email = ABMultiValueCopyValueAtIndex(emailRef, j);
if ([Validator validateEmail:(NSString *)email] == kValNoErr)
hasValidEmail = YES;
else
CFRelease(email);
}
}
if (hasValidEmail) {
CFStringRef name = ABRecordCopyValue(record, kABPersonFirstNameProperty);
CFStringRef lastname = ABRecordCopyValue(record, kABPersonLastNameProperty);
NSData *contactImageData = (NSData*)ABPersonCopyImageData(record);
UIImage *img = [[[UIImage alloc] initWithData:contactImageData] autorelease];
[contactImageData release];
if (lastname == nil)
lastname = (CFStringRef)#"";
if (name == nil)
name = (CFStringRef)#"";
Contact *contact = [[[Contact alloc] initWithName:(NSString *)name
lastname:(NSString *)lastname
email:(NSString *)email
profileIcon:img] autorelease];
if (![(NSString *)lastname isEqualToString:#""])
contact.sortChar = [(NSString *)lastname substringToIndex:1];
else if (![(NSString *)name isEqualToString:#""])
contact.sortChar = [(NSString *)name substringToIndex:1];
else if (![(NSString *)email isEqualToString:#""])
contact.sortChar = [(NSString *)email substringToIndex:1];
contact.idNumber = ABRecordGetRecordID(record);
[contacts addObject:contact];
if (lastname)
CFRelease(lastname);
if (name)
CFRelease(name);
CFRelease(email);
}
}
CFRelease(emailRef);
}
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:#"sortChar" ascending:YES selector:#selector(caseInsensitiveCompare:)];
[contacts sortUsingDescriptors:[NSArray arrayWithObject:descriptor]];
return contacts;
}
- (void)initBaseValues {
sections = [[NSMutableDictionary alloc] init];
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
NSInteger section = 0;
NSString *prevChar = nil;
NSArray *contacts = [self sortedContactsFromPeople:people];
for (int i = 0; i < contacts.count; i++) {
Contact *contact = [contacts objectAtIndex:i];
BOOL sectionExists = NO;
if ([prevChar isEqualToString:contact.sortChar])
sectionExists = YES;
if (!sectionExists) {
[sections setObject:[NSMutableArray array] forKey:[NSString stringWithFormat:#"%d", section]];
section++;
}
[prevChar autorelease];
prevChar = [contact.sortChar copy];
[[sections objectForKey:[NSString stringWithFormat:#"%d", section-1]] addObject:contact];
}
if (prevChar != nil)
[prevChar release];
CFRelease(people);
CFRelease(addressBook);
}
Contact.h
#interface Contact : NSObject {
NSString *name;
NSString *lastname;
NSString *email;
UIImage *profileIcon;
NSInteger idNumber;
}
#property (nonatomic, copy) NSString *name;
#property (nonatomic, copy) NSString *lastname;
#property (nonatomic, copy) NSString *email;
#property (nonatomic, retain) UIImage *profileIcon;
#property (nonatomic) NSInteger idNumber;
#property (nonatomic, copy) NSString *sortChar;
- (id)initWithName:(NSString *)name_
lastname:(NSString *)lastname_
email:(NSString *)email_
profileIcon:(UIImage *)profileIcon_;
#end
Doh! I wasn't vigilant enough, to read the whole thing carefully. :) Try creating NSMutableDictionary and each time headerForSection: method is being called store it's offset in the dictionary with appropriate letter as key. Then when user selects "B" letter send your UITableView setContentOffset:animated: method with appropriate offset taken from that dictionary.

Related

How to pass NSArray from an NSObject class to a UIViewController class?

I am new to Objective-C. I am trying to create a weather app where I parsed data from open weather map. I have stored the parsed data to an array. Now want to access the array value from other class but getting null value.
Can anyone help me?
What I have tried:
Here is my NSObject class where I am storing data and trying to send that to view controller:
- (void)getCurrentWeather:(NSString *)query
{
NSString *const BASE_URL_STRING = #"http://api.openweathermap.org/data/2.5/weather?q=";
NSString *const API_KEY = #"&APPID=APIKEYSTRING";
NSString *weatherURLText = [NSString stringWithFormat:#"%#%#%#",
BASE_URL_STRING, query,API_KEY];
NSURL *weatherURL = [NSURL URLWithString:weatherURLText];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:weatherURL];
[self performSelectorOnMainThread:#selector(fetchedDataSmile | :) withObject:data waitUntilDone:YES];
});
}
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
NSString* cityName = [json objectForKey:#"name"];
int currentTempCelsius = (int)[[[json objectForKey:#"main"] objectForKey:#"temp"] intValue] - ZERO_CELSIUS_IN_KELVIN;
int maxTemp = (int)[[[json objectForKey:#"main"] objectForKey:#"temp_max"] intValue] - ZERO_CELSIUS_IN_KELVIN;
int minTemp = (int)[[[json objectForKey:#"main"] objectForKey:#"temp_min"] intValue] - ZERO_CELSIUS_IN_KELVIN;
NSString *weatherDescription = [[[json objectForKey:#"weather"] objectAtIndexBlush | :O ] objectForKey:#"description"];
weatherArray = [[NSMutableArray alloc] initWithObjects:cityName, weatherDescription,
[NSString stringWithFormat:#"%d", currentTempCelsius],
[NSString stringWithFormat:#"%d", maxTemp],
[NSString stringWithFormat:#"%d", minTemp],nil];
I have NSObject.h file as:
#interface WeatherData : NSObject
#property (nonatomic) NSString *weatherDescription;
#property (strong, nonatomic) NSString *currentTemp;
#property (nonatomic) int maxTempCelsius;
#property (nonatomic) int minTempCelsius;
#property (nonatomic, retain) NSMutableArray *weatherArray;
- (void)getCurrentWeather:(NSString *)query;
#end
In my view controller:
.h file:
#property (nonatomic, retain) NSMutableArray *weatherResultArray;
.m file:
-(void)searchButtonClicked:(UIButton*)sender
{
[self.view endEditing:YES];
WeatherData *weather = [[WeatherData alloc] init];
[weather getCurrentWeather:_textField.text];
self.weatherResultArray = weather.weatherArray;
//temperatureLabel.text = [NSString stringWithFormat:#"%d°",weather.currentTempCelsius];
}
I just want to show the results in UILabel.
Have you tried returning NSMutable array in this method
- (NSMutableArray*)getCurrentWeather:(NSString *)query
instead of this,
- (void)getCurrentWeather:(NSString *)query
This would be the easiest way to verify and also value can be retrieved in single statement as:
self.weatherResultArray = [weather getCurrentWeather:_textField.text];
One more thing, Don't forget to allocate and initialise your weatherResultArray as:
self.weatherResultArray = [[NSMutableArray alloc]init];
In NSObject class, define a weather protocol.
//NSObject.h file
#protocol WeatherDelegate<NSObject>
-(void)getWeatherData:(YourNSObjectClass*)viewController getWeatherData:(NSMutableArray*)array;
#end
//NSObject.m file, in
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
NSString* cityName = [json objectForKey:#"name"];
int currentTempCelsius = (int)[[[json objectForKey:#"main"] objectForKey:#"temp"] intValue] - ZERO_CELSIUS_IN_KELVIN;
int maxTemp = (int)[[[json objectForKey:#"main"] objectForKey:#"temp_max"] intValue] - ZERO_CELSIUS_IN_KELVIN;
int minTemp = (int)[[[json objectForKey:#"main"] objectForKey:#"temp_min"] intValue] - ZERO_CELSIUS_IN_KELVIN;
NSString *weatherDescription = [[[json objectForKey:#"weather"] objectAtIndexBlush | :O ] objectForKey:#"description"];
weatherArray = [[NSMutableArray alloc] initWithObjects:cityName, weatherDescription,
[NSString stringWithFormat:#"%d", currentTempCelsius],
[NSString stringWithFormat:#"%d", maxTemp],
[NSString stringWithFormat:#"%d", minTemp],nil];
id<WeatherDelegate> strongDelegate = self.delegate;
if ([strongDelegate respondsToSelector:#selector(getWeatherData:getWeatherData:)])
{
[strongDelegate getWeatherData:self getWeatherData:weatherArray];
}
}
In yourViewController class,Add this WeatherData protocol and add the delegate function in .m file to fetch the data.
#interface yourViewControllerClass()<WeatherDelegate>
{
YourNSObjectClass *nsClass;
NSMutableArray *dataArray;
}
-(void)getWeatherData:(YourNSObjectClass*)viewController getWeatherData:(NSMutableArray*)array{
dataArray = [[NSMutableArray alloc]initWithArray:array];
}
-(void)searchButtonClicked:(UIButton*)sender
{
[self.view endEditing:YES];
WeatherData *weather = [[WeatherData alloc] init];
[weather getCurrentWeather:_textField.text];
self.weatherResultArray = dataArray;
//temperatureLabel.text = [NSString stringWithFormat:#"%d°",weather.currentTempCelsius];
}

Sqlite create database

I'm new to sqlite, and i've been for a couple of days trying to follow a tutorial, but it do not want to add my database and table. It gives me the alert that i've made if the table is not added. "the TABLE has not been created"
i do not get any xcode errors, so it could be something else than this code i guess. anybody have any suggestions?
viewcontroller.h
#property (retain, nonatomic) NSString *databaseName, *tableName;
#property (readwrite, nonatomic) int numberOfRows;
#property (readwrite, nonatomic) NSMutableArray *dataList;
#property (readwrite, nonatomic) BOOL table_ok, db_open_status;
#property (retain, nonatomic) NSArray *my_columns_names;
viewcontroller.h
[super viewDidLoad];
dataList = [[NSMutableArray alloc]init];
numberOfRows = 0;
databaseName = #"mysampledatabase";
tableName = #"mypeople";
db_open_status = NO;
table_ok = NO;
my_columns_names = [[NSArray alloc]initWithObjects:#"hometeam",#"awayteam",#"homeplayers",#"homefouls",#"awayplayers",#"awayfouls",#"period",#"time",#"homescore",#"awayscore", nil];
if ([self openDBWithSQLName:databaseName]) {
db_open_status = YES;
if (![self createTable:tableName WithCoulumns:my_columns_names]) {
UIAlertView *av = [[UIAlertView alloc]initWithTitle:#"Warning" message:#"the TABLE has not been created" delegate:self cancelButtonTitle:#"ok" otherButtonTitles:nil, nil];
[av show];
} else {
table_ok = YES;
}
}
-(BOOL)openDBWithSQLName:(NSString *)sqlname{
BOOL is_Opened = NO;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *my_sqlfile = [[paths objectAtIndex:0] stringByAppendingPathComponent:sqlname];
if (sqlite3_open([my_sqlfile UTF8String], &dbname) ==SQLITE_OK) {
is_Opened = YES;
}
return is_Opened;
}
-(BOOL)createTable:(NSString *)tablename WithCoulumns:(NSArray *)columnNames{
BOOL has_beencreated = NO;
NSString *fieldset = #"";
char *err;
for (int a=0; a<[columnNames count]; a++){
NSString *columnSet = [NSString stringWithFormat:#"'%#' TEXT", [columnNames objectAtIndex:a]];
fieldset = [fieldset stringByAppendingString:columnSet];
if (a<([columnNames count]-1)) {
fieldset = [fieldset stringByAppendingString:#" ,"];
}
}
NSString *sql = [NSString stringWithFormat:#"CREATE TABLE IF NOT EXISTS '%#' (ID INTEGER PRIMARY KEY AUTOINCREMENT,%#", tableName, fieldset];
if (sqlite3_exec(dbname, [sql UTF8String], NULL, NULL, &err) != SQLITE_OK) {
sqlite3_close(dbname);
} else {
has_beencreated = YES;
}
return has_beencreated;
}
I think I figured out the issue with the "the TABLE has not been created" message you receive (I did, as well!). In the createTable: method just before the assignment of *sql if you add this code:
fieldset=[fieldset stringByAppendingString:#")"];
the fiedlset is fulfilled with a closing bracket that it misses.
You can check it yourself by putting a breakpoint a little below this at:
has_beencreated = YES;
run the app and check the variable &err
Hope it helps!

Converting NSObject to NSDictionary

Hello I a class of type NSObject:
ProductDetails *details = [[ProductDetails alloc] init];
details.name = #"Soap1";
details.color = #"Red";
details.quantity = 4;
I want to pass the "details" object to a dictionary.
I did,
NSDictionary *dict = [NSDictionary dictionaryWithObject:details forKey:#"details"];
I am passing this dict to another method which performs a check on JSONSerialization:
if(![NSJSONSerialization isValidJSONObject:dict])
And I am getting a crash on this check. Am I doing anything wrong here? I know that the details I am getting is a JSON object and I am assigning it to the properties in my ProductDetails class.
Please help me. I am a noob in Objective-C.
I now tried:
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:(NSData*)details options:kNilOptions error:&error];
All I need here is an easy way to convert details to NSData.
I noticed that I have an array inside my object may be thats why all the ways I tried is throwing an exception. However since this question is becoming to big, I have started an another question thread for it where I have displayed the data I am getting inside the object - https://stackoverflow.com/questions/19081104/convert-nsobject-to-nsdictionary
This may well be the easiest way to achieve it. Do import #import <objc/runtime.h> in your class file.
#import <objc/runtime.h>
ProductDetails *details = [[ProductDetails alloc] init];
details.name = #"Soap1";
details.color = #"Red";
details.quantity = 4;
NSDictionary *dict = [self dictionaryWithPropertiesOfObject: details];
NSLog(#"%#", dict);
//Add this utility method in your class.
- (NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
NSDictionary *details = {#"name":product.name,#"color":product.color,#"quantity":#(product.quantity)};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:details
options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string
error:&error];
if (! jsonData) {
NSLog(#"Got an error: %#", error);
} else {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
Second part's source: Generate JSON string from NSDictionary in iOS
As mmackh said, you want to define a custom method for your ProductDetails object that will return a simple NSDictionary of values, e.g.:
#implementation ProductDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"color" : self.color,
#"quantity" : #(self.quantity)};
}
...
Let's assume that we added manufacturer property to our ProductDetails, which referenced a ManufacturerDetails class. We'd just write a jsonObject for that class, too:
#implementation ManufacturerDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"address1" : self.address1,
#"address2" : self.address2,
#"city" : self.city,
...
#"phone" : self.phone};
}
...
And then change the jsonObject for ProductDetails to employ that, e.g.:
#implementation ProductDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"color" : self.color,
#"quantity" : #(self.quantity),
#"manufacturer" : [self.manufacturer jsonObject]};
}
...
If you have potentially nested collection objects (arrays and/or dictionaries) with custom objects that you want to encode, you could write a jsonObject method for each of those, too:
#interface NSDictionary (JsonObject)
- (id)jsonObject;
#end
#implementation NSDictionary (JsonObject)
- (id)jsonObject
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj respondsToSelector:#selector(jsonObject)])
[dictionary setObject:[obj jsonObject] forKey:key];
else
[dictionary setObject:obj forKey:key];
}];
return [NSDictionary dictionaryWithDictionary:dictionary];
}
#end
#interface NSArray (JsonObject)
- (id)jsonObject;
#end
#implementation NSArray (JsonObject)
- (id)jsonObject
{
NSMutableArray *array = [NSMutableArray array];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj respondsToSelector:#selector(jsonObject)])
[array addObject:[obj jsonObject]];
else
[array addObject:obj];
}];
return [NSArray arrayWithArray:array];
}
#end
If you do something like that, you can now convert arrays or dictionaries of your custom objects object into something that can be used for generating JSON:
NSArray *products = #[[[Product alloc] initWithName:#"Prius" color:#"Green" quantity:3],
[[Product alloc] initWithName:#"Accord" color:#"Black" quantity:1],
[[Product alloc] initWithName:#"Civic" color:#"Blue" quantity:2]];
id productsJsonObject = [products jsonObject];
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:productsJsonObject options:0 error:&error];
If you're simply trying to save these objects in a file, I'd suggest NSKeyedArchiver and NSKeyedUnarchiver. But if you need to generate JSON objects for your own private classes, you can do something like the above might work.
In .h File
#import <Foundation/Foundation.h>
#interface ContactDetail : NSObject
#property (nonatomic) NSString *firstName;
#property (nonatomic) NSString *lastName;
#property (nonatomic) NSString *fullName;
#property (nonatomic) NSMutableArray *mobileNumbers;
#property (nonatomic) NSMutableArray *Emails;
#property (assign) bool Isopen;
#property (assign) bool IsChecked;
-(NSDictionary *)dictionary;
#end
in .m file
#import "ContactDetail.h"
#import <objc/runtime.h>
#implementation ContactDetail
#synthesize firstName;
#synthesize lastName;
#synthesize fullName;
#synthesize mobileNumbers;
#synthesize Emails;
#synthesize IsChecked,Isopen;
//-(NSDictionary *)dictionary {
// return [NSDictionary dictionaryWithObjectsAndKeys:self.fullName,#"fullname",self.mobileNumbers,#"mobileNumbers",self.Emails,#"emails", nil];
//}
- (NSDictionary *)dictionary {
unsigned int count = 0;
NSMutableDictionary *dictionary = [NSMutableDictionary new];
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
id value = [self valueForKey:key];
if (value == nil) {
// nothing todo
}
else if ([value isKindOfClass:[NSNumber class]]
|| [value isKindOfClass:[NSString class]]
|| [value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSMutableArray class]]) {
// TODO: extend to other types
[dictionary setObject:value forKey:key];
}
else if ([value isKindOfClass:[NSObject class]]) {
[dictionary setObject:[value dictionary] forKey:key];
}
else {
NSLog(#"Invalid type for %# (%#)", NSStringFromClass([self class]), key);
}
}
free(properties);
return dictionary;
}
#end
if any crash ,You check the property (NSMutableArray,NSString,etc ) in else if condition inside of for.
In Your Controller, in any func...
-(void)addItemViewController:(ConatctViewController *)controller didFinishEnteringItem:(NSMutableArray *)SelectedContact
{
NSLog(#"%#",SelectedContact);
NSMutableArray *myData = [[NSMutableArray alloc] init];
for (ContactDetail *cont in SelectedContact) {
[myData addObject:[cont dictionary]];
}
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:myData options:NSJSONWritingPrettyPrinted error:&error];
if ([jsonData length] > 0 &&
error == nil){
// NSLog(#"Successfully serialized the dictionary into data = %#", jsonData);
NSString *jsonString = [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
NSLog(#"JSON String = %#", jsonString);
}
else if ([jsonData length] == 0 &&
error == nil){
NSLog(#"No data was returned after serialization.");
}
else if (error != nil){
NSLog(#"An error happened = %#", error);
}
}
Try this:
#import <objc/runtime.h>
+ (NSDictionary *)dictionaryWithPropertiesOfObject:(id)obj {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] ? [obj valueForKey:key] : #"" forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
The perfect way to do this is by using a library for serialization/deserialization
many libraries are available but one i like is
JagPropertyConverter
https://github.com/jagill/JAGPropertyConverter
it can convert your Custom object into NSDictionary and vice versa
even it support to convert dictionary or array or any custom object within your object (i.e Composition)
JAGPropertyConverter *converter = [[JAGPropertyConverter alloc]init];
converter.classesToConvert = [NSSet setWithObjects:[ProductDetails class], nil];
//For Object to Dictionary
NSDictionary *dictDetail = [converter convertToDictionary:detail];
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:dictDetail options:NSJSONWritingPrettyPrinted error:&error];
You can convert object (say modelObject) to dictionary at runtime with the help of objc/runtime.h class but that has certain limitations and is not recommended.
Considering MVC, mapping logic should be implemented in Model class.
#interface ModelObject : NSObject
#property (nonatomic) NSString *p1;
#property (nonatomic) NSString *p2;
-(NSDictionary *)dictionary;
#end
#import "ModelObject.h"
#implementation ModelObject
-(NSDictionary *)dictionary
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:self.p1 forKey:#"p1"];// you can give different key name here if you want
[dict setValue:self.p2 forKey:#"p2" ];
return dict;
}
#end
Uses:
NSDictionary *modelObjDict = [modelObj dictionary];
Try using
NSDictionary *dict = [details valuesForAttributes:#[#"name", #"color"]];
And compare what the dictionary contains. Then try to convert it to JSON. And look at the JSON spec - what data types can go into a JSON encoded file?
You also can use the NSObject+APObjectMapping category which is available on GitHub: https://github.com/aperechnev/APObjectMapping
It's a quit easy. Just describe the mapping rules in your class:
#import <Foundation/Foundation.h>
#import "NSObject+APObjectMapping.h"
#interface MyCustomClass : NSObject
#property (nonatomic, strong) NSNumber * someNumber;
#property (nonatomic, strong) NSString * someString;
#end
#implementation MyCustomClass
+ (NSMutableDictionary *)objectMapping {
NSMutableDictionary * mapping = [super objectMapping];
if (mapping) {
NSDictionary * objectMapping = #{ #"someNumber": #"some_number",
#"someString": #"some_string" };
}
return mapping
}
#end
And then you can easily map your object to dictionary:
MyCustomClass * myObj = [[MyCustomClass alloc] init];
myObj.someNumber = #1;
myObj.someString = #"some string";
NSDictionary * myDict = [myObj mapToDictionary];
Also you can parse your object from dictionary:
NSDictionary * myDict = #{ #"some_number": #123,
#"some_string": #"some string" };
MyCustomClass * myObj = [[MyCustomClass alloc] initWithDictionary:myDict];
Swift
Now the swift is very popular and most of the SDK's are written in Objective C, we need to convert NSObject to NSDictionary, With the Help of #thatzprem Answer, I wrote an extension for Swift which will convert our NSObject into NSDictionary, then we can use that NSDictionary to simple Dictionary or JSON Object or other purpose. I hope so this will help out the Swift User.
extension NSObject {
func convertNSObjectToNSDictionary() -> [AnyHashable : Any]? {
var dict: [AnyHashable : Any] = [:]
var count: UInt32 = 0
let properties = class_copyPropertyList(type(of: self), UnsafeMutablePointer<UInt32>(mutating: &count)) //as? objc_property_t
for i in 0..<Int(count) {
var key: String? = nil
if let property = properties?[i] as? objc_property_t {
key = String(utf8String: property_getName(property))
}
//dict[key] = (obj as? NSObject)?.value(forKey: key ?? "")
dict[key] = (self).value(forKey: key ?? "")
}
free(properties)
return dict
}
}

List of class properties in Objective-C

Is there a way to get an array of class properties of certain kind? For example if i have interface like this
#interface MyClass : NSObject
#property (strong,nonatomic) UILabel *firstLabel;
#property (strong,nonatomic) UILabel *secondLabel;
#end
can i get the reference to those labels in implementation without knowing their name?
#implementation MyClass
-(NSArray*)getListOfAllLabels
{
?????
}
#end
I know i can do it easily with [NSArray arrayWithObjects:firstLabel,secondLabel,nil], but i would like to do it with some kind of class enumeration like for (UILabel* oneLabel in ???[self objects]???)
So more precisely, you want dynamic, runtime observaion of the properties, if I got it correctly. Do something like this (implement this method on self, the class you want to introspect):
#import <objc/runtime.h>
- (NSArray *)allPropertyNames
{
unsigned count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableArray *rv = [NSMutableArray array];
unsigned i;
for (i = 0; i < count; i++)
{
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[rv addObject:name];
}
free(properties);
return rv;
}
- (void *)pointerOfIvarForPropertyNamed:(NSString *)name
{
objc_property_t property = class_getProperty([self class], [name UTF8String]);
const char *attr = property_getAttributes(property);
const char *ivarName = strchr(attr, 'V') + 1;
Ivar ivar = object_getInstanceVariable(self, ivarName, NULL);
return (char *)self + ivar_getOffset(ivar);
}
Use it like this:
SomeType myProperty;
NSArray *properties = [self allPropertyNames];
NSString *firstPropertyName = [properties objectAtIndex:0];
void *propertyIvarAddress = [self getPointerOfIvarForPropertyNamed:firstPropertyName];
myProperty = *(SomeType *)propertyIvarAddress;
// Simpler alternative using KVC:
myProperty = [self valueForKey:firstPropertyName];
Hope this helps.
use attributeKeys method of NSObject.
for (NSString *key in [self attributeKeys]) {
id attribute = [self valueForKey:key];
if([attribute isKindOfClass:[UILabel class]])
{
//put attribute to your array
}
}
Check out this link. It is an objective c wrapper over objective C runtime.
You can use code like below
uint count;
objc_property_t* properties = class_copyPropertyList(self.class, &count);
NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++)
{
const char* propertyName = property_getName(properties[i]);
[propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]];
}
free(properties);
You must include the runtime headers
#import<objc/runtime.h>
uint propertiesCount;
objc_property_t *classPropertiesArray = class_copyPropertyList([self class], &propertiesCount);
free(classPropertiesArray);
The answer by #user529758 won't work with ARC and it won't list the properties of any ancestor classes.
To fix this, you need to traverse up the class hierarchy, and use the ARC-compatible [NSObject valueForKey:] to get the property values.
Person.h:
#import <Foundation/Foundation.h>
extern NSMutableArray *propertyNamesOfClass(Class klass);
#interface Person : NSObject
#property (nonatomic) NSString *name;
#end
Person.m:
#import "Person.h"
#import <objc/runtime.h>
NSMutableArray *propertyNamesOfClass(Class klass) {
unsigned int count;
objc_property_t *properties = class_copyPropertyList(klass, &count);
NSMutableArray *rv = [NSMutableArray array];
for (unsigned int i = 0; i < count; i++)
{
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[rv addObject:name];
}
free(properties);
return rv;
}
#implementation Person
- (NSMutableArray *)allPropertyNames {
NSMutableArray *classes = [NSMutableArray array];
Class currentClass = [self class];
while (currentClass != nil && currentClass != [NSObject class]) {
[classes addObject:currentClass];
currentClass = class_getSuperclass(currentClass);
}
NSMutableArray *names = [NSMutableArray array];
[classes enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Class currentClass, NSUInteger idx, BOOL *stop) {
[names addObjectsFromArray:propertyNamesOfClass(currentClass)];
}];
return names;
}
- (NSString*)description {
NSMutableArray *keys = [self allPropertyNames];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithCapacity:keys.count];
[keys enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
properties[key] = [self valueForKey:key];
}];
NSString *className = NSStringFromClass([self class]);
return [NSString stringWithFormat:#"%# : %#", className, properties];
}
Student.h:
#import "Person.h"
#interface Student : Person
#property (nonatomic) NSString *studentID;
#end
Student.m:
#import "Student.h"
#implementation Student
#end
main.m:
#import <Foundation/Foundation.h>
#import "Student.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
// insert code here...
Student *student = [[Student alloc] init];
student.name = #"John Doe";
student.studentID = #"123456789";
NSLog(#"student - %#", student);
}
return 0;
}
The solution of serhats is great unfortunately it doesn't work for iOS (as you mentioned) (and this question is tagged for iOS). A workaround would be to get a NSDictionary representation of the object and then access it normally as key-value pairs. I would recommend a category for NSObject:
Header-File:
#interface NSObject (NSDictionaryRepresentation)
/**
Returns an NSDictionary containing the properties of an object that are not nil.
*/
- (NSDictionary *)dictionaryRepresentation;
#end
Implementation-File:
#import "NSObject+NSDictionaryRepresentation.h"
#import <objc/runtime.h>
#implementation NSObject (NSDictionaryRepresentation)
- (NSDictionary *)dictionaryRepresentation {
unsigned int count = 0;
// Get a list of all properties in the class.
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithCapacity:count];
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
NSString *value = [self valueForKey:key];
// Only add to the NSDictionary if it's not nil.
if (value)
[dictionary setObject:value forKey:key];
}
free(properties);
return dictionary;
}
#end
Borrowed from this article: http://hesh.am/2013/01/transform-properties-of-an-nsobject-into-an-nsdictionary/
This way you could do something similar as serhats mentioned:
for (NSString *key in objectDic.allKeys) {
if([objectDic[key] isKindOfClass:[UILabel class]])
{
//put attribute to your array
}
}

Obj-C easy method to convert from NSObject with properties to NSDictionary?

I ran across something that I eventually figured out, but think that there's probably a much more efficient way to accomplish it.
I had an object (an NSObject which adopted the MKAnnotation protocol) that had a number of properties (title, subtitle,latitude,longitude, info, etc.). I needed to be able to pass this object to another object, which wanted to extract info from it using objectForKey methods, as an NSDictionary (because that's what it was getting from another view controller).
What I ended up doing was create a new NSMutableDictionary and use setObject: forKey on it to transfer each piece of vital info, and then I just passed on the newly created dictionary.
Was there an easier way to do this?
Here's the relevant code:
// sender contains a custom map annotation that has extra properties...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetailFromMap"])
{
DetailViewController *dest =[segue destinationViewController];
//make a dictionary from annotaion to pass info
NSMutableDictionary *myValues =[[NSMutableDictionary alloc] init];
//fill with the relevant info
[myValues setObject:[sender title] forKey:#"title"] ;
[myValues setObject:[sender subtitle] forKey:#"subtitle"];
[myValues setObject:[sender info] forKey:#"info"];
[myValues setObject:[sender pic] forKey:#"pic"];
[myValues setObject:[sender latitude] forKey:#"latitude"];
[myValues setObject:[sender longitude] forKey:#"longitude"];
//pass values
dest.curLoc = myValues;
}
}
Thanks in advance for your collective wisdom.
Here's what I came up with, thanks to the folks, below...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetailFromMap"])
{
DetailViewController *dest =[segue destinationViewController];
NSArray *myKeys = [NSArray arrayWithObjects:
#"title",#"subtitle",#"info",#"pic",#"latitude",#"longitude", nil];
//make a dictionary from annotaion to pass info
NSDictionary *myValues =[sender dictionaryWithValuesForKeys:myKeys];
//pass values
dest.curLoc = myValues;
}
}
And a even simpler fix, as seen below...
Using valueForKey instead of object for key to retrieve the information.
Sure thing! Use the objc-runtime and KVC!
#import <objc/runtime.h>
#interface NSDictionary(dictionaryWithObject)
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id) obj;
#end
#implementation NSDictionary(dictionaryWithObject)
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
#end
And you would use like this:
MyObj *obj = [MyObj new];
NSDictionary *dict = [NSDictionary dictionaryWithPropertiesOfObject:obj];
NSLog(#"%#", dict);
This is an old post and Richard J. Ross III's answer is really helpful, but in case of custom objects (an custom class has another custom object in it). However, sometimes properties are other objects and so forth, making the serialization a bit complicated.
Details * details = [[Details alloc] init];
details.tomato = #"Tomato 1";
details.potato = #"Potato 1";
details.mangoCount = [NSNumber numberWithInt:12];
Person * person = [[Person alloc]init];
person.name = #"HS";
person.age = #"126 Years";
person.gender = #"?";
person.details = details;
For converting these type of objects (multiple custom objects) into dictionary, I had to modify Richard J. Ross III's Answer a little bit.
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
Class classObject = NSClassFromString([key capitalizedString]);
if (classObject) {
id subObj = [self dictionaryWithPropertiesOfObject:[obj valueForKey:key]];
[dict setObject:subObj forKey:key];
}
else
{
id value = [obj valueForKey:key];
if(value) [dict setObject:value forKey:key];
}
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
I hope it will help someone. Full credit goes to Richard J. Ross III.
If the properties had the same names as the keys used to access the dictionary then you could have just used KVC and had valueForKey: instead of objectForKey.
For example given this dictionary
NSDictionary *annotation = [[NSDictionary alloc] initWithObjectsAndKeys:
#"A title", #"title", nil];
and this Object
#interface MyAnnotation : NSObject
#property (nonatomic, copy) NSString *title;
#end
it wouldn't matter if I had an instance of the dictionary or MyAnnotation I could call
[annotation valueForKey:#"title"];
Obviously that works the other way as well e.g.
[annotation setValue:#"A title" forKey:#"title"];
To complete the method of Richard J. Ross, this one works with NSArray of custom object.
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
Class classObject = NSClassFromString([key capitalizedString]);
id object = [obj valueForKey:key];
if (classObject) {
id subObj = [self dictionaryWithPropertiesOfObject:object];
[dict setObject:subObj forKey:key];
}
else if([object isKindOfClass:[NSArray class]])
{
NSMutableArray *subObj = [NSMutableArray array];
for (id o in object) {
[subObj addObject:[self dictionaryWithPropertiesOfObject:o] ];
}
[dict setObject:subObj forKey:key];
}
else
{
if(object) [dict setObject:object forKey:key];
}
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
There are so many solutions and nothing worked for me as I had a complex nested object structure. This solution takes things from Richard and Damien but improvises as Damien's solution is tied to naming keys as class names.
Here is the header
#interface NSDictionary (PropertiesOfObject)
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj;
#end
Here is the .m file
#implementation NSDictionary (PropertiesOfObject)
static NSDateFormatter *reverseFormatter;
+ (NSDateFormatter *)getReverseDateFormatter {
if (!reverseFormatter) {
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
reverseFormatter = [[NSDateFormatter alloc] init];
[reverseFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
[reverseFormatter setLocale:locale];
}
return reverseFormatter;
}
+ (NSDictionary *)dictionaryWithPropertiesOfObject:(id)obj {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
id object = [obj valueForKey:key];
if (object) {
if ([object isKindOfClass:[NSArray class]]) {
NSMutableArray *subObj = [NSMutableArray array];
for (id o in object) {
[subObj addObject:[self dictionaryWithPropertiesOfObject:o]];
}
dict[key] = subObj;
}
else if ([object isKindOfClass:[NSString class]]) {
dict[key] = object;
} else if ([object isKindOfClass:[NSDate class]]) {
dict[key] = [[NSDictionary getReverseDateFormatter] stringFromDate:(NSDate *) object];
} else if ([object isKindOfClass:[NSNumber class]]) {
dict[key] = object;
} else if ([[object class] isSubclassOfClass:[NSObject class]]) {
dict[key] = [self dictionaryWithPropertiesOfObject:object];
}
}
}
return dict;
}
#end
You also can use the NSObject+APObjectMapping category which is available on GitHub: https://github.com/aperechnev/APObjectMapping
It's a quit easy. Just describe the mapping rules in your class:
#import <Foundation/Foundation.h>
#import "NSObject+APObjectMapping.h"
#interface MyCustomClass : NSObject
#property (nonatomic, strong) NSNumber * someNumber;
#property (nonatomic, strong) NSString * someString;
#end
#implementation MyCustomClass
+ (NSMutableDictionary *)objectMapping {
NSMutableDictionary * mapping = [super objectMapping];
if (mapping) {
NSDictionary * objectMapping = #{ #"someNumber": #"some_number",
#"someString": #"some_string" };
}
return mapping
}
#end
And then you can easily map your object to dictionary:
MyCustomClass * myObj = [[MyCustomClass alloc] init];
myObj.someNumber = #1;
myObj.someString = #"some string";
NSDictionary * myDict = [myObj mapToDictionary];
Also you can parse your object from dictionary:
NSDictionary * myDict = #{ #"some_number": #123,
#"some_string": #"some string" };
MyCustomClass * myObj = [[MyCustomClass alloc] initWithDictionary:myDict];