NSMutableArray shows some hex content instead of actual values [duplicate] - objective-c

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
NSMutableArray Not showing actual values when NSLog in iphone application
I have NSMutableArray initialised as follows:
- (id)init
{
self = [super init];
if (self)
{
mySpotsArray = [[NSMutableArray alloc]init];
}
return self;
}
The array stores data of NSTable as follows:
//-----------------------------------------
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [mySpotsArray count];
[self saveMySpots];
}
//-----------------------------------------
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
Spot *sp = [mySpotsArray objectAtIndex:row];
NSString *identifier = [tableColumn identifier];
return [sp valueForKey:identifier];
[self saveMySpots];
}
//-----------------------------------------
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
Spot *sp = [mySpotsArray objectAtIndex:row];
NSString *identifier = [tableColumn identifier];
[sp setValue:object forKey:identifier];
[self saveMySpots];
}
I am adding and deleting objects like this:
//-----------------------------------------
- (IBAction)addSpot:(id)sender
{
[mySpotsArray addObject:[[Spot alloc]init]];
[mySpotsTable reloadData];
[self saveMySpots];
}
//-----------------------------------------
- (IBAction)deleteSpot:(id)sender
{
NSInteger row = [mySpotsTable selectedRow];
[mySpotsTable abortEditing];
if (row !=-1)
{
[mySpotsArray removeObjectAtIndex: row];
}
[mySpotsTable reloadData];
[self saveMySpots];
}
I am saving and loading array content like this:
//-----------------------------------------
- (void) saveMySpots
{
savedSpots = [NSUserDefaults standardUserDefaults];
encodedMySpotsArrayObject = [NSKeyedArchiver archivedDataWithRootObject: mySpotsArray];
[savedSpots setObject: encodedMySpotsArrayObject forKey:[NSString stringWithFormat:#"MySpotsArrayKey"]];
}
//-----------------------------------------
- (void) loadMySpots
{
savedSpots = [NSUserDefaults standardUserDefaults];
decodedMySpotsArrayObject = [savedSpots objectForKey: [NSString stringWithFormat:#"MySpotsArrayKey"]];
mySpotsArray = [NSKeyedUnarchiver unarchiveObjectWithData: decodedMySpotsArrayObject];
}
Objects are encoded and decoded like this:
//-----------------------------------------
- (id)init
{
self = [super init];
if (self)
{
_mySpot = #"My Spot";
_localOffset = 0;
}
return self;
}
//-----------------------------------------
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject: self.mySpot forKey:#"MySpotKey"];
[encoder encodeInt: self.localOffset forKey:#"LocalOffsetKey"];
}
//-----------------------------------------
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if( self != nil )
{
self.mySpot = [decoder decodeObjectForKey:#"MySpotKey"];
self.localOffset = [decoder decodeIntForKey:#"LocalOffsetKey"];
}
return self;
}
Everything is properly defined in their respective .h like this
NSMutableArray *mySpotsArray;
NSUserDefaults *savedSpots;
NSData *encodedMySpotsArrayObject;
NSData *decodedMySpotsArrayObject;
All works perfect on UI level, i.e. the table is properly displayed, added, deleted, saved and loaded. But when I am trying to NSLog like this:
NSLog(#"%#", mySpotsArray);
I get this:
2012-09-19 13:41:25.372 Spot[1541:303] (
"<Spot: 0x100674ab0>",
"<Spot: 0x100674c20>",
"<Spot: 0x100675040>"
)
I've also tried this:
NSString *strData = [[NSString alloc]initWithData: decodedMySpotsArrayObject encoding:NSUTF8StringEncoding];
NSLog(#"strData: %#", strData);
and I get this:
2012-09-19 13:41:25.371 Spot[1541:303] strData: (null)
I simply need to access NSMutableArray content and then convert it to strings. The actual content what I see on UI is a table with 2 columns and 3 rows:
Yerevan 2
London -1
Los Angeles -9
What am I doing wrong?
Thanks

I think this is the expected behavior.
How does the debugger know what it should print to the console? If you want it to print something else, e.g. some property on the Spot object, you need to provide Spot with a description method.
For example:
#import <Foundation/Foundation.h>
#interface Foo:NSObject {
NSString *_bar;
}
#property (nonatomic, copy) NSString *bar;
#end
#implementation Foo
#synthesize bar = _bar;
- (NSString *)description {
return self.bar;
}
#end
int main(int argc, char *argv[]) {
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
NSMutableArray *objs = [[NSMutableArray alloc] init];
Foo *myFoo = [Foo new];
myFoo.bar = #"Mine";
Foo *yourFoo = [Foo new];
yourFoo.bar = #"Yours";
[objs addObject:myFoo];
[objs addObject:yourFoo];
NSLog(#"Objs = %#",objs);
[p release];
}
prints this to the console:
2012-09-19 06:57:10.375 Untitled 2[59494:707] Objs = (
Mine,
Yours
)
But without the description method, this is what prints to the console:
2012-09-19 07:01:12.542 Untitled 2[59853:707] Objs = (
"<Foo: 0x7f9773c080c0>",
"<Foo: 0x7f9773c0a9b0>"
)

Add this method in your Spot class
- (NSString *)description
{
NSString *str = [[NSString alloc] initWithFormat:#"%# \n %# \n%#",Var1,Var2,var3];
return str;
}
replace Var1, Var2 and Var3 with your original variable name.

Related

NSUserDefaults returning nil for some strings in array

I have a custom Object "Woman". I am trying to store the following values in it and save it in a mutable array using NSUserDefaults. The code I use for such is below. I also am using NSCoding in the object.
if (women ==nil) {
women =[[NSMutableArray alloc] init];
}
NSString *string =[NSString stringWithFormat:#"%f", interval];
//store to woman object
Woman* woman = [[Woman alloc] initWithFull:nameOfGirl withdate2:perfectdate withintervalLength:string withperiodLength:[NSString stringWithFormat:#"432000"] withpmsLength:[NSString stringWithFormat:#"432000"]];
[women addObject:woman];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:women] forKey:#"women"];
I use this code to retrieve it:
//pull women from archive
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults valueForKey:#"women"];
if (dataRepresentingSavedArray != nil)
{
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
women = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
women = [[NSMutableArray alloc] init];
}
The result is in the screenshot. What is interesting to me is that the first string makes it but the other ones don't. :
EDIT: Here is my custom class.
.h:
#import <Foundation/Foundation.h>
#interface Woman : NSObject <NSCoding>
#property (nonatomic,strong) NSString *girlname;
#property (nonatomic,strong) NSDate *date2;
#property (nonatomic,strong) NSString *intervalLength;
#property (nonatomic,strong) NSString *periodLength;
#property (nonatomic, strong) NSString *pmsLength;
- (id)initWithFull:(NSString *)girlname withdate2:(NSDate *)date2 withintervalLength:(NSString *)intervalLength withperiodLength:(NSString *)periodLength withpmsLength:(NSString *)pmsLength;
- (id)initWithNoInterval:(NSString *)girlname withdate2:(NSDate *)date2 withperiodLength:(NSString *)periodLength withpmsLength:(NSString *)pmsLength;
- (id)initWithIntervalnoPMSPeriod:(NSString *)girlname withdate2:(NSDate *)date2 withintervalLength:(NSString *)intervalLength;
- (void) encodeWithCoder:(NSCoder*)encode;
- (id) initWithCoder:(NSCoder*)decode;
#end
And the .m:
#import "Woman.h"
#implementation Woman
-(id)initWithFull:(NSString *)girlname withdate2:(NSDate *)date2 withintervalLength:(NSString *)intervalLength withperiodLength:(NSString *)periodLength withpmsLength:(NSString *)pmsLength {
self = [super init];
self.girlname = girlname;
self.date2 = date2;
self.intervalLength = intervalLength;
self.pmsLength = pmsLength;
self.periodLength = periodLength;
return self;
}
-(id)initWithIntervalnoPMSPeriod:(NSString *)girlname withdate2:(NSDate *)date2 withintervalLength:(NSString *)intervalLength {
self = [super init];
self.girlname = girlname;
self.date2 = date2;
self.intervalLength = intervalLength;
return self;
}
-(id)initWithNoInterval:(NSString *)girlname withdate2:(NSDate *)date2 withperiodLength:(NSString *)periodLength withpmsLength:(NSString *)pmsLength {
self = [super init];
self.girlname = girlname;
self.date2 = date2;
self.pmsLength = pmsLength;
self.periodLength = periodLength;
return self;
}
- (id)initWithCoder:(NSCoder *)coder {
if (self = [super init]) {
self.girlname = [coder decodeObjectForKey:#"girlname"];
self.date2 = [coder decodeObjectForKey:#"date2"];
self.intervalLength = [coder decodeObjectForKey:#"intervalLength"];
self.pmsLength = [coder decodeObjectForKey:#"pmsLength"];
self.periodLength = [coder decodeObjectForKey:#"periodLength"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:_girlname forKey:#"girlname"];
[coder encodeObject:_date2 forKey:#"date2"];
[coder encodeBool:_intervalLength forKey:#"intervalLength"];
[coder encodeBool:_pmsLength forKey:#"pmsLength"];
[coder encodeBool:_periodLength forKey:#"periodLength"];
}
#end
Here is also a breakpoint screenshot which shows that the newest object (index 2) has values before it is stores in NSDefaults.
UPDATE: After switching "nameofgirl" and the interval string, the interval string worked, but nameofgirl returned nil. So it's only the first two values working for some reason.
intervalLength, pmsLength and periodLength are NSString objects. Use encodeObject: to encode them.

Custom sections for enumerated objects of array in NSOutlineView

I am trying to create a NSOutlineVew with a custom header group (parent node) for listed objects. (NOTE: I have cell-based NSOutlineView). For example it look like as the Xcode "Navigator" or Numbers sidebar. I used the default groups for the separation properties per category, but it's looks like not as what I want. I need a parent node (cell), which I'll can visually adjust (add a controls elements and image).
I tried to do this by passing an array of objects to NSDictionary, giving each group a certain specific key. And a result, via NSLog everything is displayed correctly, but the transfer of this variable as the source for the program NSOulineView fails.
ProjectViewController.h
#interface ProjectViewController : NSViewController <NSOutlineViewDataSource, NSObject> {
IBOutlet NSOutlineView *outlineView;
FSEntity *content;
}
#property (readonly, assign) NSMutableArray *objects;
#end
ProjectViewController.m
#implementation ProjectViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
// Setting default path to the local file or directory
NSString *home = NSHomeDirectory();
NSURL *url = [[NSURL alloc] initFileURLWithPath:home];
content = [[FSEntity alloc] initWithURL:url];
[self defineContentNSOutlineView];
NSLog(#"Array: %#",_objects);
// Basic сonfiguration an instance NSOutlineView
[self configurationNSOutlineView];
} return self;
}
#synthesize objects = _objects;
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
return (item == nil) ? [content.children objectAtIndex:index] : [((FSEntity *)item).children objectAtIndex:index];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
return (item == nil) ? content.children.count > 0 : ((FSEntity *)item).children.count > 0;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
return (item == nil) ? content.children.count : ((FSEntity *)item).children.count;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
if ([item isKindOfClass:[FSEntity class]]) {
return [((FSEntity *)item) title];
}
return nil;
}
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
if ([cell isKindOfClass:[ImageAndTextCell class]]) {
ImageAndTextCell *textField = (ImageAndTextCell *)cell;
[textField setImage:[item icon]];
}
}
- (void)defineContentNSOutlineView {
NSMutableArray *objects = [NSMutableArray arrayWithObjects:[NSDictionary dictionaryWithObjectsAndKeys:#"FINDER", #"title", [NSArray arrayWithObjects:[NSDictionary dictionaryWithObject:content.children forKey:#"title"], nil], #"children",[NSNumber numberWithBool:YES], #"header", nil], nil];
_objects = objects;
}
- (void)configurationNSOutlineView {
[outlineView sizeLastColumnToFit];
[outlineView setFloatsGroupRows:NO];
[outlineView reloadData];
[outlineView expandItem:nil expandChildren:YES];
}
#end
To easier would imagine how it would look, I showed it on the scheme:
+--------------------------------------------+
| ▼ FINDER FILES ₪ ✱ |
| 03143553.file |
| ▶ Desktop |
| ▶ Documents |
| ▶ Downloads |
| ▶ Movies |
| ▶ Music |
| ▶ Pictures |
+--------------------------------------------+
and what I have now (NSOulineView without using NSTreeController);
+--------------------------------------------+
| 03143553.file |
| ▶ Desktop |
| ▶ Documents |
| ▶ Downloads |
| ▶ Movies |
| ▶ Music |
| ▶ Pictures |
+--------------------------------------------+
I know about the example Apple "SourceView", but I don't know how to add to the created group, array of objects (files and folders), NSTreeContoller display only the first elements of the hierarchy (without includes):
+--------------------------------------------+
| ▼ FINDER FILES |
| 03143553.file |
| Desktop |
| Documents |
| Downloads |
| Movies |
| Music |
| Pictures |
+--------------------------------------------+
Modified method of SourceView example:
- (void)addFinderSection {
[self addFolder:#"FINDER FILES"];
NSError *error = nil;
NSEnumerator *urls = [[[NSFileManager defaultManager] contentsOfDirectoryAtURL:self.url includingPropertiesForKeys:[NSArray arrayWithObjects: nil] options:(NSDirectoryEnumerationSkipsHiddenFiles) error:&error] objectEnumerator];
for (NSURL *url in urls) {
BOOL isDirectory;
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDirectory]) {
if (isDirectory) {
[self addChild:[url path] withName:NO selectParent:YES];
} else {
[self addChild:[url path] withName:NO selectParent:YES];
}
}
}
[self selectParentFromSelection];
}
This method displays only the first objects, as shown it on the latter scheme.
And one more question, as I said before, how to add to the node ** "FINDER FILES" ** button to the right side of the cell.
Can you help me with this? I know, maybe is not so hard, but I just began learn Objective-C and I don't know how to do this. Thanks.
Based on the starting code you provided, I was able to get something working, using Cell-based NSOutlineViews. The code you posted seemed incomplete, so it was hard to know what exactly you wanted to get out of the missing FSEntity class. I just used foundation classes: NSArray for lists of nodes (i.e. root nodes and children), NSDictionaries for each node itself (both root nodes and sub-nodes), and NSURLs as the file-system reference. I tried to stay as close to your original code as possible. In the end, it looked something like this:
ProjectViewController.h
#interface ProjectViewController : NSViewController <NSOutlineViewDataSource, NSObject>
{
IBOutlet NSOutlineView *outlineView;
NSURL *content;
}
#end
ProjectViewController.m
#import "ProjectViewController.h"
#interface ProjectViewController () {
NSMutableArray* _objects;
}
#property (nonatomic, retain, readwrite) NSMutableArray* objects;
#end
#implementation ProjectViewController
#synthesize objects = _objects;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Initialization code here.
// Setting default path to the local file or directory
NSString *home = NSHomeDirectory();
content = [[NSURL alloc] initFileURLWithPath:home];
[self defineContentNSOutlineView];
NSLog(#"Array: %#",_objects);
// Basic сonfiguration an instance NSOutlineView
[self configurationNSOutlineView];
}
return self;
}
// nodes have 3 keys: title, url, icon
- (NSArray*)p_childrenForNode: (NSMutableDictionary*)node {
if (nil == node)
return self.objects;
NSArray* retVal = nil;
if (nil == (retVal = [node valueForKey: #"children"]))
{
NSMutableArray* children = [NSMutableArray array];
for (NSURL* urlInDir in [[NSFileManager defaultManager] contentsOfDirectoryAtURL: [node objectForKey: #"url"]
includingPropertiesForKeys: [NSArray arrayWithObjects: NSURLNameKey, NSURLEffectiveIconKey, nil]
options: 0
error: NULL])
{
id name = [urlInDir getResourceValue: &name forKey: NSURLNameKey error: NULL] ? name : #"<Couldn't get name>";
id icon = [urlInDir getResourceValue: &icon forKey: NSURLEffectiveIconKey error: NULL] ? icon : nil;
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: urlInDir, #"url", name, #"title", nil];
if (icon)
[dict setObject: icon forKey: #"icon"];
[children addObject: dict];
}
retVal = children;
if (children)
[node setValue: children forKey: #"children"];
}
return retVal;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
NSMutableDictionary* itemDict = (NSMutableDictionary*)item;
NSArray* children = [self p_childrenForNode: itemDict];
return children.count > index ? [[[children objectAtIndex: index] retain] autorelease] : nil;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
NSMutableDictionary* itemDict = (NSMutableDictionary*)item;
NSArray* children = [self p_childrenForNode: itemDict];
return children.count > 0;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
NSMutableDictionary* itemDict = (NSMutableDictionary*)item;
NSArray* children = [self p_childrenForNode: itemDict];
NSInteger retVal = children.count;
return retVal;
}
- (id)outlineView:(NSOutlineView *)pOutlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
NSImage* icon = [item objectForKey: #"icon"];
NSString* title = [item objectForKey: #"title"];
id value = nil;
if (icon) {
[icon setSize: NSMakeSize(pOutlineView.rowHeight - 2, pOutlineView.rowHeight - 2)];
NSTextAttachment* attachment = [[[NSTextAttachment alloc] init] autorelease];
[(NSCell *)[attachment attachmentCell] setImage: icon];
NSMutableAttributedString *aString = [[[NSAttributedString attributedStringWithAttachment:attachment] mutableCopy] autorelease];
[[aString mutableString] appendFormat: #" %#", title];
value = aString;
} else {
value = title;
}
return value;
}
- (void)defineContentNSOutlineView {
// Make root object
NSMutableDictionary* rootObj = [NSMutableDictionary dictionaryWithObjectsAndKeys:
#"FINDER", #"title",
content, #"url",
nil];
id icon = [content getResourceValue: &icon forKey: NSURLEffectiveIconKey error: NULL] ? icon : nil;
if (icon)
[rootObj setObject: icon forKey: #"icon"];
// Set it
self.objects = [NSMutableArray arrayWithObject: rootObj];
}
- (void)configurationNSOutlineView {
[outlineView sizeLastColumnToFit];
[outlineView setFloatsGroupRows:NO];
[outlineView reloadData];
[outlineView expandItem:nil expandChildren:YES];
}
#end
I posted the whole, working sample project to GitHub.

Load data into tableview form cells

I have a program where a UITableView contains custom cells loaded from nibs. These cells have textfields and a UIImage. I've been passing the information they contain to a custom class and encoding/decoding the class for data persistence. When I want to load the data, I put the information from the class into the cell. This works fine for 1 cell, but not for more than one. I've checked, and the classes are being written to file correctly.
This is my retrieval method:
//Fills an array if the file exists, otherwise returns nil
- (NSMutableArray*) findFile: (NSString *) add
{
if ([[NSFileManager defaultManager] fileExistsAtPath:[self saveFilePath:add]])
{
NSString *temp = [add stringByAppendingString:#"dat"];
namesIndexer = [[NSMutableArray alloc] initWithContentsOfFile:[self saveFilePath:temp]];
if (namesIndexer == nil) return nil;
NSMutableArray *thing = [NSMutableArray new];
for (NSString *place in namesIndexer)
{
temp = [add stringByAppendingString:place];
PTextHolder *p = [NSKeyedUnarchiver unarchiveObjectWithFile:[self saveFilePath:temp]];
[thing addObject:p];
}
return thing;
}
else
{
return nil;
}
}
Note that this is in a different class, and it calls the method from the holder.
//Returns a cell to be used at a row, populates it from the holder object
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *personCellId = #"personID";
UINib *nib = [UINib nibWithNibName:#"PersonCell" bundle:nil];
[tableView registerNib:nib forCellReuseIdentifier:personCellId];
PersonCell *cell = [tableView dequeueReusableCellWithIdentifier:personCellId];
cell.owner = tableView;
if (mineTable == nil) mineTable = tableView;
cell.delegated = formDataStorage;
[formDataStorage putWhatShouldBeInThisCellForThisRowInIt:cell:(int*)indexPath.row];
cell.currentRow = [[NSNumber alloc] initWithInt:indexPath.row];
return cell;
}
Here's the method it calls:
- (void) putWhatShouldBeInThisCellForThisRowInIt: (PersonCell *) someCell: (int *) someRow
{
if ((NSUInteger) someRow >= cake.count)
{
NSLog(#"The cake has been undercooked");
return;
}
PTextHolder *temp = [cake objectAtIndex:(NSUInteger) someRow];
someCell.firstName.text = temp.first;
someCell.lastName.text = temp.last;
someCell.middleName.text = temp.middle;
someCell.suffixName.text = temp.suffix;
someCell.email.text = temp.email;
someCell.theSignature.image = temp.sig;
}
Anything look wrong here/would cause only one cell to be loaded?
I would check first the number of items in the array with
[array count]
, if the number of items is equal to 1, then the problem is as you guessed with the encoding/decoding.
If not, your code is right and the problem is with your code to load the cells.
By the way, why dont you store your array of "cellInfoClass" directly using:
[NSKeyedArchiver archiveRootObject:array toFile:filePath]
and retrieve directly the array.
I guess you already added the encoding/coding code to your class, if not is like that:
/**
* Returns an object initialized from data in a given unarchiver. (required)
*
* #param decoder: An unarchiver object.
*/
- (id)initWithCoder:(NSCoder *)coder {
if (self = [super init]) {
// If parent class also adopts NSCoding, replace [super init]
// with [super initWithCoder:decoder] to properly initialize.
[self setName:[coder decodeObjectForKey:#"name"]];
[self setId:[coder decodeIntForKey:#"id"]];
[self setDomain:[coder decodeObjectForKey:#"domain"]];
}
return self;
}
/**
* Encodes the receiver using a given archiver. (required)
* #param coder: An archiver object.
*/
- (void)encodeWithCoder:(NSCoder *)coder{
// If parent class also adopts NSCoding, include a call to
// [super encodeWithCoder:encoder] as the first statement.
[coder encodeObject:name forKey:#"name"];
[coder encodeInt:id forKey:#"id"];
[coder encodeObject:domain forKey:#"domain"];
}

Create index for UITableView from an NSArray

I've read that the best way of creating an index (the a-z at the side of a uitableview) is to set up an array of nsdictionaries, where each dictionary corresponds to a section, and a rowValue key contains an array of the rows.
NSDictionary
headerTitle => ‘A’
rowValues => {”Aardvark”, “Ape”, “Aquaman”}
NSDictionary
headerTitle => ‘B’
rowValues => {”Bat”, “Boot”, “Bubbles”} etc
But how can this be created from an array of all the row titles - {”Aardvark”, “Ape”, “Aquaman”, ”Bat”, “Boot”, “Bubbles”, "Cat", "Cabbage" etc} ...?
I recently had a similar objective and this is how I solved it. The advantage of this over Robin's solution is that it creates the index title array dynamically based on the content of your array and it won't include indices for empty sections (plus it's a little cleaner).
I created a category of NSMutableDictionary that takes an array of data as a parameter and returns an NSMutableDictionary (we'll call it indexDictionary and it should be an instance variable):
// if your data is static, you can call this in `viewDidLoad`
indexDictionary = [NSMutableDictionary createDictionaryForSectionIndex:arrayOfStrings];
The category method:
#implementation NSMutableDictionary (DictionaryForSectionIndex)
+(NSMutableDictionary *)createDictionaryForSectionIndex:(NSArray *)array
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (char firstChar = 'a'; firstChar <= 'z'; firstChar++)
{
//NSPredicates are fast
NSString *firstCharacter = [NSString stringWithFormat:#"%c", firstChar];
NSArray *content = [array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF beginswith[cd] %#", firstCharacter]];
NSMutableArray *mutableContent = [NSMutableArray arrayWithArray:content];
if ([mutableContent count] > 0)
{
NSString *key = [firstCharacter uppercaseString];
[dict setObject:mutableContent forKey:key];
NSLog(#"%#: %u", key, [mutableContent count]);
}
}
return dict;
}
#end
/*
**Input:**
{"Aardvark", "Cabbage", "Boot", "Eggs", "Ape", "Aquaman", "Elephant", "Cat", "Bat", "Bubbles"}
**Output:**
NSMutableDictionary
key => 'A'
object => {"Aardvark", "Ape", "Aquaman"}
key => 'B'
object => {"Bat", "Boot", "Bubbles"}
key => 'C'
object => {"Cat", "Cabbage"}
key => 'E'
object => {"Elephant", "Eggs"}
*/
Then I create an NSArray instance variable to sort and store all the keys from indexDictionary:
// this line should follow the creation of `indexDictionary`
sortedKeys = [[indexDictionary allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
// Output, in this case, is {'A', 'B', 'C', 'E'}
You now have everything you need to set up the index for your table. Implement the following methods (if something isn't self explanatory, just let me know):
//this code assumes `sortedKeys` is not empty
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return ([sortedKeys count]);
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSString *key = [sortedKeys objectAtIndex:section];
return [[indexDictionary valueForKey:key] count];
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return ([sortedKeys objectAtIndex:section]);
}
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return sortedKeys;
}
-(NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
return index;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// preceding code...
NSString *key = [sortedKeys objectAtIndex:indexPath.section];
NSArray *array = [indexDictionary objectForKey:key];
NSString *yourString = [array objectAtIndex:indexPath.row];
cell.textLabel.text = yourString;
// following code...
}
The result is a table with an index that skips letters that have no associated data.
#pragma mark -
#pragma mark View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *temp = [[NSMutableArray alloc] init];
NSMutableArray *temp2 = [[NSMutableArray alloc] init];
for(int i = 0; i < tableListArray.count; i++)
{
NSString *string = [tableListArray objectAtIndex:i];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:string forKey:#"Name"];
[dict setObject:[NSNumber numberWithInt:i] forKey:#"ID"];
NSString *firstString = [string substringToIndex:1];
if([temp2 containsObject:firstString] == NO || temp2.count == 0)
{
if(temp2.count != 0)
{
[temp addObject:temp2];
[temp2 release];
temp2 = [[NSMutableArray alloc] init];
}
[temp2 addObject:firstString];
}
[temp2 addObject:dict];
[dict release];
}
[temp addObject:temp2];
detailListArray = [[NSArray alloc] initWithArray:temp];
[temp release];
[temp2 release];
}
#pragma mark -
#pragma mark Table view data source
- (NSInteger)tableView:(UITableView *)tableView
sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
int i = 0;
for(NSArray *array in detailListArray)
{
NSString *string = [array objectAtIndex:0];
if([string compare:title] == NSOrderedSame)
break;
i++;
}
return i;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return detailListArray.count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSArray *array = [detailListArray objectAtIndex:section];
return [array objectAtIndex:0];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
NSMutableArray *titleArray = [NSMutableArray array];
[titleArray addObject:#"A"];
[titleArray addObject:#"B"];
[titleArray addObject:#"C"];
[titleArray addObject:#"D"];
[titleArray addObject:#"E"];
[titleArray addObject:#"F"];
[titleArray addObject:#"G"];
[titleArray addObject:#"H"];
[titleArray addObject:#"I"];
[titleArray addObject:#"J"];
[titleArray addObject:#"K"];
[titleArray addObject:#"L"];
[titleArray addObject:#"M"];
[titleArray addObject:#"N"];
[titleArray addObject:#"O"];
[titleArray addObject:#"P"];
[titleArray addObject:#"Q"];
[titleArray addObject:#"R"];
[titleArray addObject:#"S"];
[titleArray addObject:#"T"];
[titleArray addObject:#"U"];
[titleArray addObject:#"V"];
[titleArray addObject:#"W"];
[titleArray addObject:#"X"];
[titleArray addObject:#"Y"];
[titleArray addObject:#"Z"];
return titleArray;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSArray *array = [detailListArray objectAtIndex:section];
return (array.count - 1);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"CELL"] autorelease];
NSArray *array = [detailListArray objectAtIndex:indexPath.section];
NSDictionary *dict = [array objectAtIndex:indexPath.row + 1];
cell.textLabel.text = [dict objectForKey:#"Name"];
return cell;
}
#pragma mark -
#pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *array = [detailListArray objectAtIndex:indexPath.section];
NSDictionary *dict = [array objectAtIndex:indexPath.row + 1];
int entryID = [[dict objectForKey:#"ID"] intValue];
// Do what ever you want to do with the selected row here....
}
This is the code that I have used in one of the recent projects.
I was working on one project where I need dynamic solutions for same problem so I figure out this solutions which returns dictionary with only available sections. Hope it help someone.
-(NSMutableDictionary *)getSortedDataWithArray:(NSArray *)dataArray{
NSMutableDictionary *allDataDictionary = [[NSMutableDictionary alloc]init];
for(int i = 0 ; i< dataArray.count ;i++){
NSString *currentStr = [[[dataArray objectAtIndex:i] substringToIndex:1] uppercaseString];
NSArray *allKeysArray = allDataDictionary.allKeys;
if([allKeysArray containsObject:currentStr]){
NSMutableArray *currentArray = [[NSMutableArray alloc] initWithArray:[allDataDictionary valueForKey:currentStr]];
[currentArray addObject:[dataArray objectAtIndex:i]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF beginswith[c] %#" ,currentStr];
NSArray *finalArray = [currentArray filteredArrayUsingPredicate:predicate];
[allDataDictionary setValue:finalArray forKey:currentStr];
}
else{
NSMutableArray *finalArray = [[NSMutableArray alloc] initWithObjects:[dataArray objectAtIndex:i], nil];
[allDataDictionary setValue:finalArray forKey:currentStr];
}
}
return allDataDictionary;
}

storing address book records

I have creating an address book application . My AddressController.h class is ---
#interface AddressController : NSObject {
IBOutlet id nameField;
IBOutlet id addressField;
IBOutlet id tableView;
NSMutableArray *records;
}
- (IBAction)addRecord:(id)sender;
- (IBAction)deleteRecord:(id)sender;
- (IBAction)insertRecord:(id)sender;
#end
Implementation class is as follow:-
#implementation AddressController
- (id)init
{
records = [[NSMutableArray alloc] init];
return self;
}
- (NSDictionary *)createRecord
{
NSMutableDictionary *record = [[NSMutableDictionary alloc] init];
[record setObject:[nameField stringValue] forKey:#"Name"];
[record setObject:[addressField stringValue] forKey:#"Address"];
[record autorelease];
return record;
}
- (IBAction)addRecord:(id)sender
{
[records addObject:[self createRecord]];
[tableView reloadData];
}
- (IBAction)deleteRecord:(id)sender
{
int status;
NSEnumerator *enumerator;
NSNumber *index;
NSMutableArray *tempArray;
id tempObject;
if ( [tableView numberOfSelectedRows] == 0 )
return;
NSBeep();
status = NSRunAlertPanel(#"Warning!", #"Are you sure that you want to delete the selected record(s)?", #"OK", #"Cancel", nil);
if ( status == NSAlertDefaultReturn )
{
enumerator = [tableView selectedRowEnumerator]; //enumerator here gets indexes of selected rows
tempArray = [NSMutableArray array];
while ( (index = [enumerator nextObject]) )
{
tempObject = [records objectAtIndex:[index intValue]]; // we store selected rows in temporary array
[tempArray addObject:tempObject];
}
[records removeObjectsInArray:tempArray]; // we delete records from 'records' array which are present in temporary array
[tableView reloadData];
}
}
- (IBAction)insertRecord:(id)sender
{
int index = [tableView selectedRow];
[records insertObject:[self createRecord] atIndex:index];
[tableView reloadData];
}
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [records count];
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
id theRecord, theValue;
theRecord = [records objectAtIndex:rowIndex];
theValue = [theRecord objectForKey:[aTableColumn identifier]];
return theValue;
}
- (void)awakeFromNib
{
[tableView reloadData];
}
#end
I am able to add and delete records to/from address book. But when I start the application again all records are gone. I want to store records somewhere (like in user defaults ) so that when I start the application again existing records are shown in the address book.
I am not getting the idea how to do it using user defaults.
Please suggest solution.
Do not use user defaults for this. You may want to explore Core Data. Another option to explore is NSCoding.
NSCoding will have less of a learning curve, fairly simple to implement. Core Data is tougher to grasp, but would be the wiser choice in the long run.