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

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:.

Related

Remove object from an array stored in a singleton

Im working with a singleton to store some data, her's the implementation
static ApplicationData *sharedData = nil;
#implementation ApplicationData
#synthesize list;
+ (id)sharedData
{
static dispatch_once_t dis;
dispatch_once(&dis, ^{
if (sharedData == nil) sharedData = [[self alloc] init];
});
return sharedData;
}
- (id)init
{
if (self = [super init])
{
list = [[NSMutableArray alloc]init];
}
return self;
}
if list have less than 3 (2<) object i the app crash with "index 0 beyond bounds for empty array"
// NSMutableArray *anArray = [[NSMutableArray alloc]initWithObjects:#"", nil];
while ([[[ApplicationData sharedData]list] lastObject] != nil)
{
File *file = [[[ApplicationData sharedData]list] lastObject];
BOOL isDir;
if (![[NSFileManager defaultManager] fileExistsAtPath:file.filePath isDirectory:&isDir])
{
NSMutableDictionary *tmpDic = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:file.fileName,file.filePath,logEnteryErrorfileNotFoundDisplayName,[formatter stringFromDate:[NSDate date]], nil] forKeys:[NSArray arrayWithObjects:logShredFileName,logShredFilePath,logShredStatue,logShredDate, nil]];
[logArray addObject:tmpDic];
errorOccured = YES;
[[[ApplicationData sharedData]list] removeLastObject];
continue;
}
... other code
}
if i use the anArray that work perfectly.
what is the problem ?
That's totally weird, you've probably did something else to achieve this. Why don't you use - (void)removeAllObjects?
Maybe you remove objects in the while cycle the last line, ie:
while ([[[ApplicationData sharedData]list] count] != 0)
{
// remove object from list
// ...
[[[ApplicationData sharedData]list] removeLastObject];
}
And just a note, you don't need to check if (sharedData == nil) in sharedData as far as it's guaranteed to be executed only once. (unless you do something outside to your static variable, but that's not how it's supposed to be done I believe)

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

Something is wrong with singleton...unable adding a child because it is nil

I use a singleton the first time and I don't really know how to implement it...
Ok I need to explain some things:
In Hexagon.h (which inherits from CCNode) I want to create multiple sprites (here referred to as "hexagons"). However, they are not added to the scene yet. They are being added in the HelloWorldLayer.m class by calling Hexagon *nHex = [[Hexagon alloc]init]; . Is that correct ? Is it then iterating through the for loop and creating all hexagons or only one ?
Well anyways, I have a singleton class which has to handle all the public game state information but retrieving is not possible yet.For instance I cannot retrieve the value of existingHexagons, because it returns (null) objects. Either I set the objects wrongly or I am falsely retrieving data from the singleton. Actually, I would even appreciate an answer for one of these questions. Please help me. If something is not clear, please add a comment and I'll try to clarify it.
What I have right now is the following:
GameStateSingleton.h
#import <Foundation/Foundation.h>
#interface GameStateSingleton : NSObject{
NSMutableDictionary *existingHexagons;
}
+(GameStateSingleton*)sharedMySingleton;
-(NSMutableDictionary*)getExistingHexagons;
#property (nonatomic,retain) NSMutableDictionary *existingHexagons;
#end
GameStateSingleton.m
#import "GameStateSingleton.h"
#implementation GameStateSingleton
#synthesize existingHexagons;
static GameStateSingleton* _sharedMySingleton = nil;
+(GameStateSingleton*)sharedMySingleton
{
#synchronized([GameStateSingleton class])
{
if (!_sharedMySingleton)
[[self alloc] init];
return _sharedMySingleton;
}
return nil;
}
+(id)alloc
{
#synchronized([GameStateSingleton class])
{
NSAssert(_sharedMySingleton == nil, #"Attempted to allocate a second instance of a singleton.");
_sharedMySingleton = [super alloc];
return _sharedMySingleton;
}
return nil;
}
-(id)init {
self = [super init];
if (self != nil) {
}
return self;
}
#end
Hexagon.m
-(CCSprite *)init{
if( (self=[super init])) {
NSString *mainPath = [[NSBundle mainBundle] bundlePath];
NSString *levelConfigPlistLocation = [mainPath stringByAppendingPathComponent:#"levelconfig.plist"];
NSDictionary *levelConfig = [[NSDictionary alloc] initWithContentsOfFile:levelConfigPlistLocation];
NSString *currentLevelAsString = [NSString stringWithFormat:#"level%d", 1];
NSArray *hexPositions;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
hexPositions = [[levelConfig valueForKey:currentLevelAsString] valueForKey:#"hexpositionIpad"];
}
else{
hexPositions = [[levelConfig valueForKey:currentLevelAsString] valueForKey:#"hexpositionIphone"];
}
NSString *whichType = [NSString stringWithFormat:#"glass"];
CGSize screenSize = [CCDirector sharedDirector].winSize;
if ([whichType isEqualToString:#"stone"]){
hexagon = [CCSprite spriteWithFile:#"octagonstone.png"];
}else if([whichType isEqualToString: #"glass"]){
hexagon = [CCSprite spriteWithFile:#"octagoncolored1.png"];
}else if([whichType isEqualToString: #"metal"]){
hexagon = [CCSprite spriteWithFile:#"octagonmetal.png"];
}
NSMutableDictionary *eHexagons =[[GameStateSingleton sharedMySingleton] getExistingHexagons];
for (int i=0;i < [hexPositions count];i++){
CGPoint location = CGPointFromString([hexPositions objectAtIndex:i]);
CGPoint nLocation= ccp(screenSize.width/2 + 68 * location.x,screenSize.height/2 + 39 * location.y);
NSString *aKey = [NSString stringWithFormat:#"hexagon%d",i];
hexagon =[CCSprite spriteWithFile:#"octagoncolored1.png"];
hexagon.position = nLocation;
[eHexagons setObject:hexagon forKey:aKey];
[self addChild:[eHexagons valueForKey:aKey] z:3];
[[GameStateSingleton sharedMySingleton]setExistingHexagons:eHexagons];
}
NSLog(#"these are the existinghexagons %#", existingHexagons);
//This returns a dictionary with one (null) object
}
return hexagon;
}
HelloWorldLayer.m -> -(id)init method
Hexagon *nHex = [[Hexagon alloc]init];
First of all, it returns null because the existingHexagons array has never been initialized in the first place. Go to the init function of your singleton and add:
existingHexagons = [[NSMutableArray alloc]init];
As for your For Loop question, I did not get it. I recommend making one StackOverflow question per query instead of putting two in one.

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

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.