storing address book records - objective-c

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.

Related

How can I sort or add an item to a UITableView the opposite way?

So, I have an UITableView which holds entries for an app I am making. The entriesViewController is its own class, with a .xib file. I have a button that adds a new item.
It does this with the following code:
-(IBAction)newItem:(id)sender {
LEItem *newItem = [[LEItemStore sharedStore] createItem];
NSLog(#"New Item = %#", newItem);
[TableView reloadData];
}
Now this works, and adds the item, however it puts it at the bottom of the list. Since this app logs things for days, I do not want the items in this order. The newest items should be placed at the top of the list. How do I do this? I didn't see any easy way to add items to the table view at the top, but I might be missing something pretty basic.
This doesn't seem like it should be hard, I am probably just overlooking something.
Ideas are welcome.
Edit:
Here is LEItem Store:
//
// LEItemStore.m
//
// Created by Josiah Bruner on 10/16/12.
// Copyright (c) 2012 Infinite Software Technologies. All rights reserved.
//
#import "LEItemStore.h"
#import "LEItem.h"
#implementation LEItemStore
+ (LEItemStore *)sharedStore
{
static LEItemStore *sharedStore = nil;
if (!sharedStore)
sharedStore = [[super allocWithZone:nil] init];
return sharedStore;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [self sharedStore];
}
-(id)init
{
self = [super init];
if (self) {
NSString *path = [self itemArchivePath];
allItems = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (!allItems)
{
allItems = [[NSMutableArray alloc] init];
}
}
return self;
}
- (NSArray * )allItems
{
return allItems;
}
-(LEItem *)createItem
{
LEItem *p = [LEItem addNewItem];
[allItems addObject:p];
return p;
}
- (void)removeItem:(LEItem *)p
{
[allItems removeObjectIdenticalTo:p];
}
-(void)moveItemAtIndex:(int)from toIndex:(int)to
{
if (from == to) {
return;
}
LEItem *p = [allItems objectAtIndex:from];
[allItems removeObjectAtIndex:from];
[allItems insertObject:p atIndex:to];
}
- (NSString *)itemArchivePath {
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"item.archive"];
}
-(BOOL)saveChanges {
NSString *path = [self itemArchivePath];
return [NSKeyedArchiver archiveRootObject:allItems toFile:path];
}
#end
It looks like the simplest solution would be to modify -[LEItemStore createItem] to this:
-(LEItem *)createItem {
LEItem *p = [LEItem addNewItem];
[allItems insertObject:p atIndex:0];
return p;
}
You can do it even without rearrange the array internally.If you implement the data source and you define this method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
Assuming that in your array the oldest objects are at the lowest indexes,supposing that your table view has M rows, return a cell with the format of the object at index M-rowIndex-1.
Unless I'm missing something, after you create the new item, instead of using
[allItems addObject:p];
you just need:
[allItems insertObject:p atIndex:0];
Do you have any type of createdDate or other sortable property on the item? Simply sort your retained list of items (or NSFetchedResultsController) or whatever you are binding to by that property.
You can override the comparison mechanism in your LEItem class, and have it compare dates easily:
-(NSComparisonResult)compare:(LEItem*)otherItem {
return [self.dateCreated compare:otherItem.dateCreated];
}
Then, it's just a matter of using sortArrayUsingSelector: with the selector compare:.

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

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.

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"];
}

cancel button on search bar does not cancel correctly

I have a search bar, i can search now, but when I enter a text to search, and click the cancel button. It does not give me back my first stage, meaning full of the items in the table.
For example: I search the item with word: a, it gives me all the a items, yes, it is right now, but when i hit the cancel button, i want the programme gives me all the items exist, not just a items.
Here is the code: please help me out. Thank you so much.
- (void)searchBarCancelButtonClicked:(UISearchBar *)aSearchBar
{
searchBar.text = #"";
[searchBar resignFirstResponder];
letUserSelectRow = YES;
searching = NO;
self.tableView.scrollEnabled = YES;
NSLog(#"what text after cancel now: %#", searchBar.text);
[self.tableView reloadData];
}
- (NSMutableArray *) searchTableView {
NSString *searchText = searchBar.text;
NSLog(#"search text: %#", searchText);
NSMutableArray *resultArray = [[NSMutableArray alloc] init];
NSMutableArray *tempArr = [[NSMutableArray alloc] init];
for (NSDictionary *dTemp in arrayData)
{
NSString *tempStr = [dTemp objectForKey:#"url"];
NSLog(#"sTemp string: %#",[ NSString stringWithFormat:#"%#", tempStr]);
NSRange titleResultsRange = [tempStr rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (titleResultsRange.length > 0)
{
NSLog(#"1 count :%d", [resultArray count]);
[resultArray addObject:dTemp];
NSLog(#"2 count :%d", [resultArray count]);
[tempArr addObject:resultArray];
[resultArray release];
resultArray = [NSMutableArray new];
}
}
if (resultArray != nil) {
[resultArray release];
}
return tempArr;
}
- (void)searchBar:(UISearchBar *)aSearchBar textDidChange:(NSString *)searchText
{
NSLog(#"what text after cancel now: %#", searchBar.text);
if([searchText length] > 0) {
[sortedArray removeAllObjects];
searching = YES;
letUserSelectRow = YES;
self.tableView.scrollEnabled = YES;
NSMutableArray *searchArray = [self searchTableView];
sortedArray = [[NSMutableArray alloc] initWithArray:searchArray copyItems:YES];
for (int i = 0; i<[sortedArray count]; i++) {
NSLog(#"this is the search array: %#", [[sortedArray objectAtIndex:i] class]);
}
NSLog(#"sorted array: %d", [sortedArray count]);
}
else {
searching = NO;
letUserSelectRow = NO;
self.tableView.scrollEnabled = NO;
}
[self.tableView reloadData];
}
You don't need to override any of UISearchBar methods to accomplish this. The new way of doing this relies on the UISearchDisplay controller instead (specifically on shouldReloadTableForSearchString).
Declare your view controller to conform to UISearchDisplayDelegate protocol, and keep two instance variables: your model as NSArray (all data) and a filtered array as NSMutableArray (a subset of your data). The code you presently have in "searchTableView" would filter the content of the model and place it into the filtered NSMutableArray. Then you would override the following UITableView methods: -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section and -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath. In each, before returning, make a comparison to determine whether your tableView argument is equal to self.searchDisplayController.searchResultsTableView. If it is, the user is looking at the filtered list and your should use the content of the filtered NSMutableArray to create the view, otherwise, the user is looking at the whole data set and you should use the content of the NSArray that holds your model. Take a look at the following Apple code for a simple example of what I described:
http://developer.apple.com/library/ios/#samplecode/TableSearch/Introduction/Intro.html

How to Fix EXC_BAD_ACCESS on NSArray Property?

This is yet another EXC_BAD_ACCESS question. Although I've done my homework and am certain that I am not over-releasing my NSArray.
So here is a snippet of my code:
tableData = [NSDictionary dictionaryWithJSONString:JSONstring error:&error];
//Collect Information from JSON String into Dictionary. Value returns a mutli
dimensional NSDictionary. Eg: { value => { value => "null"}, etc }
NSMutableArray *t_info = [[NSMutableArray alloc] init];
for(id theKey in tableData)
{
NSDictionary *get = [tableData objectForKey:theKey];
[t_info addObject:get];
[get release];
} // converting into an NSArray for use in a UITableView
NSLog(#"%#", t_info);
//This returns an Array with the NSDictionary's as an Object in each row. Returns fine
if (tvc == nil)
{
tvc = [[tableViewController alloc] init]; //Create Table Controller
tableView.delegate = tvc;
tableView.dataSource = tvc;
tvc.tableView = self.tableView;
tvc.tableData = t_info; //pass our data to the tvc class
[tvc.tableView reloadData];
}
...
Now in my TableViewController Class:
#implementation tableViewController
#synthesize tableData, tableView;
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [tableData count]; //Returns X Amount Fine.
}
- (UITableViewCell *)tableView:(UITableView *)the_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *MyIdentifier = [NSString stringWithFormat:#"MyIdentifier"];
UITableViewCell *cell = [the_tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
}
NSLog(#"%#", tableData); //** CRASHES!!**
cell.textLabel.text = #"This is a test";
return cell;
}
If I were to comment out that NSLog, it'll work fine and return "this is a test" on each table row.
This one has really got me stumped, all the articles I have around about this problem is generally related to retain/memory issues.
Also, another important point.
If I were to pass through my original (NSDictionary) tableData from my first class code and run the same script in my tableViewController - I can NSLog the object perfectly fine.
The only time you need to release an object is if you have explicitly allocated it by way of new, alloc, or copy.
NSMutableArray *t_info = [[NSMutableArray alloc] init];
for(id theKey in tableData)
{
NSDictionary *get = [tableData objectForKey:theKey];
[t_info addObject:get];
[get release];
}
You shouldn't be releasing get here. By doing this, you're releasing the reference that the tableData dictionary is holding onto, which is bad. My guess is that this is what is causing the problem that you're encountering.
If I'm not mistaken, the reason why [tableData count] returns the expected value is because the array is still holding onto the references that have been released.