I have a tableview which is sectioned alphabetically. I have an NSMutableArray *drinks which is my original datasource which its being populated with NSMutableDictionary's from a plist. in my viewDidLoad method. I then iterate through my array and create the keys for the sections and the number of rows in each section in my viewWillAppear method
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.sections = [[NSMutableDictionary alloc] init];
BOOL found;
// Loop through the whiskys and create our keys
for (NSMutableDictionary *whisky in self.drinks)
{
NSString *c = [[whisky objectForKey:NAME_KEY] substringToIndex:1];
found = NO;
for (NSString *str in [self.sections allKeys])
{
if ([str isEqualToString:c])
{
found = YES;
}
}
if (!found)
{
[self.sections setValue:[[NSMutableArray alloc] init] forKey:c];
}
}
// Loop again and sort the whiskys into their respective keys
for (NSDictionary *whisky in self.drinks)
{
[[self.sections objectForKey:[[whisky objectForKey:NAME_KEY] substringToIndex:1]] addObject:whisky];
}
// Sort each section array
for (NSString *key in [self.sections allKeys])
{
[[self.sections objectForKey:key] sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:NAME_KEY ascending:YES]]];
}
[self.tableView reloadData];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [searchPaths lastObject];
NSString *writeableDBPath = [documentsDirectory stringByAppendingPathComponent:#"ScotchList.plist"];
NSMutableArray *tmpArray = [[NSMutableArray alloc]initWithContentsOfFile:writeableDBPath];
self.drinks = tmpArray;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
//Register for application exiting information so we can save data
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
}
All of this works fine and dandy. Then when I delete a row it works fine as well. How ever when I go to a new view it and come back to the tableview the deleted row reappears. So I need to update my original datasource (drinks) because everytime the viewAppears it pulls from the original datasource again. So I tried to get the item (whisky) at the row and remove it from the array. But it throws a sigabrt. This is my delete row method.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
//DELETE THE ROW FROM THE DATA SOURCE
[self.tableView beginUpdates];
[[self.sections valueForKey:[[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section]] removeObjectAtIndex:indexPath.row];
NSDictionary *whisky = [[self.sections valueForKey:[[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section]] objectAtIndex:indexPath.row];
[self.drinks removeObject:whisky];
if ([[self.sections valueForKey:[[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section]] count] > 0)
{
// Section is not yet empty, so delete only the current row.
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else
{
// Section is now completely empty, so delete the entire section.
[self.sections removeObjectForKey:[[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section]];
[tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]
withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView endUpdates];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
this is what the log shows:
2012-07-06 15:36:42.454 WhiskyBook[3275:907] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
*** First throw call stack:
(0x3998a17b 0x3494395b 0x398d83bd 0x3e6b5 0x32a0cd11 0x328f0d09 0x328f0cbb 0x328f0c95 0x328f09eb 0x328f1369 0x328ef791 0x328dd72d 0x328dd009 0x339a7603 0x339a7233 0x3995a873 0x3995a817 0x39959611 0x398d6be5 0x398d6a71 0x339a634b 0x329037f5 0x3ac09 0x33cc7b20)
libc++abi.dylib: terminate called throwing an exception
(lldb)
What am I doing wrong?
I found a solution to my problem. Instead of trying to remove the object from the original datasource inside of the commitEditingStyle method I just made an NSDictionary property for my master-viewController alloc'd and inited it in my viewDidLoad then assigned it the item that I want to be deleted inside of the commitEditingStyle method. Then inside of my viewWillDisappear method I removed it from the original datasource.
Related
When I searching and after filtered select in displays row that opens only the first letter DetailView (for example A letter).Others letters don't open. NSLog and breakpoint not helping. I don't understand what is the problem.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *keyTitle = cell.textLabel.text;
NSDictionary *peopleUnderLetter = [self.propertyList objectForKey:self.letters[indexPath.section]];
__block NSDictionary *selectedPerson = nil;
[peopleUnderLetter enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([key isEqualToString:keyTitle]) {
selectedPerson = obj;
*stop = YES;
}
}];
if (selectedPerson) {
DetailViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"DetailViewController"];
// Push the view controller.
[self.navigationController pushViewController:vc animated:YES];
[vc setDictionaryGeter:selectedPerson];
}
}
#pragma mark Search Display Delegate Methods
-(void)searchDisplayController:(UISearchController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
}
-(BOOL)searchDisplayController:(UISearchController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[filteredNames removeAllObjects];
if (searchString.length > 0) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains [search] %#", self.searchBar.text];
for (NSString *letter in letters) {
NSArray *matches = [[self.propertyList[letter] allKeys]filteredArrayUsingPredicate:predicate];
[filteredNames addObjectsFromArray:matches];
}
}
return YES;
}
after selecting every cell must open the detailviewcontroller with their data.Search bar shows every letter after filtered.But when I click cell doesn't open detail view (except first cell) .
Hard to tell without seeing the whole file but it looks like your problem may be this line. You are pulling the object out of self.propertyList using the section not the row. Which makes sense as to why it works only on the first row, which would have a row of 0 and an section of 0
`NSDictionary *peopleUnderLetter = [self.propertyList objectForKey:self.letters[indexPath.section]];`
Try changing it to :
`NSDictionary *peopleUnderLetter = [self.propertyList objectForKey:self.letters[indexPath.row]];`
I'm trying to use a global NSMutableDictionary from a dispatch queue. However, the items keep coming back NULL.
What I'm trying to do is access an external json file with a dispatch_queue, then populate a UITableView with this info.
Here's what I have
vc.h:
#interface viewcontroller {
NSMutableDictionary *jsonArray;
}
vc.m:
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) //1
#define jsonTest [NSURL URLWithString:#"http://www.sometest.com/test.php"]
-(void)viewDidLoad {
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:
jsonTest];
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:YES];
// if I run the log here, I can access jsonArry and the log prints correctly
NSLog(#"City: %#", [jsonArray objectForKey:#"city"];
});
}
-(NSMutableDictionary *)fetchedData:(NSData *)responseData {
NSError *error;
jsonArray = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
return jsonArray;
}
/********************* Table formatting area **********************/
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.ipTable) {
if ([ipArray count] == 0){
return 1;
} else { // meta table
return [ipArray count];
}
} else { // IP Meta Data
return [jsonArray count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.myTable) {
NSString *CellIdentifier = NULL;
if ([ipArray count] == 0) {
CellIdentifier = #"No Cells";
} else {
CellIdentifier = #"IP Cell";
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if ([ipArray count] == 0)
{
[cell.textLabel setText:NSLocalizedString(#"None Found", nil)];
return cell;
} else {
IPAddr *theip = [ipArray objectAtIndex: [indexPath row]];
NSString *theipname = [theip ipName];
if ([theipname isEqualToString:#""]) {
[cell.textLabel setText: [theip ipNum]];
[cell.detailTextLabel setText:NSLocalizedString(#"noName", nil)];
} else {
[cell.textLabel setText: [theip ipName]];
[cell.detailTextLabel setText: [theip ipNum]];
}
return cell;
}
} else { // meta table
static NSString *CellIdentifier = #"metaCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// jsonArray content would go here to fill the cells.
/******************** something here to fill the cells using jsonArray ********************/
return cell;
}
} // END UITAbleViewCell
If I access the jsonArray inside the queue, it returns fine and prints the log for the city.
However, if I try to use it outside the queue, it returns NULL.
I'm trying to figure out what is happening, any ideas?
I need to use jsonArray in different methods in the same view, so I need it to be global.
I am fairly sure that the problem is that the data source methods (numberOfRowsInSection,
cellForRowAtIndexPath) are called before the background thread has finished and
filled the jsonArray. Therefore you have to reload the table view when the background
thread has finished:
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(kBgQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:jsonTest];
NSError *error;
NSArray *tmpArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
dispatch_sync(dispatch_get_main_queue(), ^{
// Assign new data to data source and reload the table view:
jsonArray = tmpArray;
[self.metaTableView reloadData];
});
});
}
So the table view would be empty initially, and reloaded later when the data has
arrived.
Try to call the other method(which is using your jsonarray) through nsnotification...I am not sure there might some other ideas/ways of doing this.But i am presenting what i have in my mind.
Put this code inside your fetchedData method,
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(someMethod:) name:#"JSonDownloaded" object: jsonArray];
[[NSNotificationCenter defaultCenter] postNotificationName:#"JSonDownloaded" object: jsonArray];
-(void)someMethod:(NSNotification *)nspk
{
NSLog(#"%#",nspk.object);
//Only after this you can able to access the jsonArray.
}
Don't forget to unregister the observer.
jsonArray is just an instance variable, but not a property. Thus, assigning an object to it does not retain it, and the object may be released as soon as the program returns to the run loop.
I suggest replacing the iVar by #property (strong) NSMutableDictionary *jsonArray; and #synthesize jsonArray;, and assigning the object to it by self.jsonArray = ...
EDIT (see comment of Martin R below):
Thus, if you are not using ARC, assigning an object to it does not retain it, and the object may be released as soon as the program returns to the run loop.
In this case, I suggest replacing the iVar by #property (retain) NSMutableDictionary *jsonArray; and #synthesize jsonArray;, and assigning the object to it by self.jsonArray = ...
My app is only crashing in a specific way. Here's a break down of what's going on.
I can type in text in a UITextView and tap a button that saves the text and adds a row to a UITableView in another UIViewController. I can then tap on the desired cell from the UITableView and that UIViewController will dismiss and the text will appear again on the main UIViewController.
I have another button that simply clears out the UITextView so I can type in new text.
If I view the text from an added row and then tap the "Add" button to input new text and then tap the "Save" button my app crashes.
Here's some of the code:
didSelectRow Code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Setting the text stored in an array into a NSString here
_displayString = [_savedNoteArray objectAtIndex:indexPath.row];
[self dismissViewControllerAnimated:YES completion:nil];
}
save button code:
- (IBAction)saveNote
{
if (_noteView.aTextView.text == nil)
{
[_noteArray addObject:#""];
Note * tempNote = [[Note alloc] init];
_note = tempNote;
[_savedNotesViewController.savedNoteArray addObject:tempNote];
NSIndexPath * tempNotePath = [NSIndexPath indexPathForRow: [_savedNotesViewController.savedNoteArray count]-1 inSection:0];
NSArray * tempNotePaths = [NSArray arrayWithObject:tempNotePath];
[_savedNotesViewController.noteTableView insertRowsAtIndexPaths:tempNotePaths withRowAnimation:NO];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AddNote" object:nil];
}
else
{
[_noteArray addObject:self.noteView.aTextView.text];
Note * tempNote = [[Note alloc] init];
_note = tempNote;
[_savedNotesViewController.savedNoteArray addObject:tempNote];
NSIndexPath * tempNotePath = [NSIndexPath indexPathForRow:[_savedNotesViewController.savedNoteArray count]-1 inSection:0];
NSArray * tempNotePaths = [NSArray arrayWithObject:tempNotePath];
//**** This is where the app is crashing *****
[_savedNotesViewController.noteTableView insertRowsAtIndexPaths:tempNotePaths withRowAnimation:NO];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AddNote" object:nil];
}
Note * myNote = [Note sharedNote];
myNote.noteOutputArray = _noteArray;
}
add butt code (makes a new UITextView):
- (IBAction)addButtonTapped
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"AddNote" object:nil];
}
in my viewWillAppear to show the selected row text I do this:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.noteView.aTextView.text = _savedNotesViewController.displayString;
}
note code (singleton class):
static Note * sharedNote = nil;
- (id)initWithNote:(NSString *)newNote
{
self = [super init];
if (nil != self)
{
self.note = newNote;
}
return self;
}
+ (Note *) sharedNote
{
#synchronized(self)
{
if (sharedNote == nil)
{
sharedNote = [[self alloc] init];
}
}
return sharedNote;
}
When the app crashes I get this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Note isEqualToString:]: unrecognized selector sent to instance 0x8875f20'
Stepping through my code, the text is being added to the array, but when it comes time to insertRowsAtIndexPaths the app blows up.
Any advice is much appreciated!
* EDIT // TableView Code **
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_savedNoteArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
//I have a feeling this could be where an issue is.
NSString * cellString = [_savedNoteArray objectAtIndex:indexPath.row];
cell.textLabel.text = cellString;
return cell;
}
One potential issue (which may not be your crash, but will cause issues regardless) is that you are storing Note objects in the savedNoteArray BUT you are trying to use them as strings (your code below):
//Setting the text stored in an array into a NSString here
_displayString = [_savedNoteArray objectAtIndex:indexPath.row];
Then you assign that displayString to a UITextView's text property (which is supposed to be an NSString*):
self.noteView.aTextView.text = _savedNotesViewController.displayString;
The short form of this issue can be summarized as...
Note *note = [[NSNote alloc] init];
[array addObject:note];
textView.text = array[0];
This will clearly cause issues. You're basically assigning a 'Note' object to something that is supposed to be a string.
This probably leads into the crash that you're experiencing in the cellForRowAtIndexPath: method of your table view data source. Are you using Note objects there as well, or are you properly assigning NSStrings to views?
This app is a table view with a tab bar controller. I am logging the count of the array: arrayOfFavourites and even though i add an object is continues to have a nil value, my relating code, all objects shown are allocated and initialized in the code (previous or present) some are instances and some are properties:
ListViewController.m:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"TOUCHED CELL!");
// Push the web view controller onto the navigation stack - this implicitly
// creates the web view controller's view the first time through
[[self navigationController] pushViewController:webViewController animated:YES];
// Grab the selected item
entry = [[channel items] objectAtIndex:[indexPath row]];
if (!entry) {
NSLog(#"!entry");
}
// Construct a URL with the link string of the item
NSURL *url = [NSURL URLWithString:[entry link]];
// Construct a request object with that URL
NSURLRequest *req = [NSURLRequest requestWithURL:url];
// Load the request into the web view
[[webViewController webView] loadRequest:req];
// Take the cell we pressed
// IMPORTANT PART
CELL = [tableView cellForRowAtIndexPath:indexPath];
[webViewController setItem:entry];
webViewController = nil;
webViewController = [[WebViewController alloc] init];
[entry release];
}
WebViewController.m:
You shake to favorite a cell
-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
cellToPassOn = nil;
NSLog(#"Favouriting"); // YES I KNOW SPELLING
// This is pretty simple, what we do is we take the cell we touched and take its title and link
// then put it inside an array in the Favourites class
Favourites *fav = [[Favourites alloc] init];
ListViewController *list = [[ListViewController alloc] init];
[self setCellToPassOn: [list CELL]];
if (!item) {
NSLog(#"NILLED ITEM");
}
[[fav arrayOfFavourites] addObject:[item autorelease]];
[fav setCell: cellToPassOn];
[fav release];
[list release];
item = nil;
}
Favourites.m:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
arrayOfFavourites = [[NSMutableArray alloc] init];
NSLog(#"ROWS NO.");
NSLog(#"%i", [arrayOfFavourites count]);
return [arrayOfFavourites count];
}
Why are you inializing the array in tableview:numberOfRowsInSection ? This will cause the array to be reset each time table view is reloaded. This could be your issue.
you are allocating your array in -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
try to allocate it somewhere else.
You could allocate the arrayOfFavorites in the tableView:numberOfRowsInSectionMethod, but then you first need to check if it is nil.
if( !arrayOfFavorites )
arrayOfFavoriges = [[NSMutableArray alloc] init];
You should release it then in the dealloc method: [arrayOfFavorites release].
-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
cellToPassOn = nil;
NSLog(#"Favouriting"); // YES I KNOW SPELLING
// HERE creation of a Brand NEW empty Favourites instance
Favourites *fav = [[Favourites alloc] init];
// HERE creation of a Brand NEW empty ListViewController instance
ListViewController *list = [[ListViewController alloc] init];
// HERE we hope that the ListViewController as CELL other then nil when it is Brand NEW
[self setCellToPassOn: [list CELL]];
if (!item) {
NSLog(#"NILLED ITEM");
}
[[fav arrayOfFavourites] addObject:[item autorelease]];
[fav setCell: cellToPassOn];
[fav release];
// HERE the fav instance get deallocated and don't exist anymore
[list release];
// HERE the list instance get deallocated and don't exist anymore
item = nil;
}
In this code list and fav exist only in the body of this method, attempt to get to the value they have hold done to will failed, because list and fav doesn't exist outside that method.
I'm trying to access the array (data source) in my NSTableView to allow replacing the string that is in the row with a new string.The app consist of an NSTextfield for data entry and a button to add the entry so that it's displayed in the NSTableView. What I want is to be able to double click the NSTableView and replace what ever string is there with a new string, but I'm not sure how to do this.Here is my code so far
#implementation AppController
-(id)init
{
[super init];
[tableView setDataSource:self];
[tableView setDelegate:self];
array = [[NSMutableArray alloc ] init];
//NSLog(#"this is my delegate %#",[tableView delegate]);
return self;
}
-(IBAction)addItem:(id)sender
{
inputString = [textField stringValue];
[array addObject:inputString];
[tableView reloadData];
return;
}
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [array count];
}
- (id) tableView:(NSTableView *)aTableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex
{
//NSLog(#"this is the object %#",[array objectAtIndex:rowIndex]);
return [array objectAtIndex:rowIndex];
}
-(IBAction) replaceItem:(id)sender
{
NSString *newString = [[NSString alloc]init];
NSLog(#"The selected row %d",[tableView selectedRow]);
newString = [textField stringValue];
[array addObject:newString];
[array replaceObjectAtIndex:[tableView selectedRow ] withObject: newString];
NSLog(#"this is the new sting %#",newString);
[tableView reloadData];
}
#end
I think you're looking for these datasource and delegate methods:
-tableView:setObjectValue:forTableColumn:row:
-tableView:shouldEditTableColumn:row: