I have a view where I programmatically create Search functionality. The App works fine in iOS 6 but crashes on devices running iOS 7. I get an error stating "Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'A nil string was passed for sorting.'
*** First throw call stack:".Is there anything wrong that I'm doing? Here's the code that executes in the viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
{
// Get the DBAccess object;
DBAccess *dbAccess = [[DBAccess alloc] init];
// Get array of products that do not have a barcode
productsWithNoBarcodeArray = [dbAccess getUserProductsWithNoBarcode];
// Close the database because we are finished with it
[dbAccess closeDatabase];
}
NSLog(#"Number of products with no barcodes is: %d",[productsWithNoBarcodeArray count]);
/***************************************Implementing Tableview Sections and Index*****************************/
self.retailers = [NSMutableArray arrayWithCapacity:1];
UILocalizedIndexedCollation *indexedCollation = [UILocalizedIndexedCollation currentCollation];
//Iterate over the retailers, populating their section number
for (Product *theRetailer in productsWithNoBarcodeArray)
{
NSInteger section = [indexedCollation sectionForObject:theRetailer collationStringSelector:#selector(sProductSearch)];
theRetailer.section = section;
}
//Get the count of the number of sections
NSInteger sectionCount = [[indexedCollation sectionTitles] count];
//Create an array to hold subarrays for the various sections
NSMutableArray *sectionsArray = [NSMutableArray arrayWithCapacity:sectionCount];
//Iterate over each section, creating each subarray
for (int i=0; i<= sectionCount; i++)
{
NSMutableArray *singleSectionArray = [NSMutableArray arrayWithCapacity:1];
[sectionsArray addObject:singleSectionArray];
}
//Iterate over the retailers, putting each retailer into the correct subarray
for (Product *theRetailer in productsWithNoBarcodeArray)
{
[(NSMutableArray *)[sectionsArray objectAtIndex:theRetailer.section] addObject:theRetailer];
}
//Iterate over each section array to sort items in the section
for (NSMutableArray *singleSectionArray in sectionsArray)
{
//Use the UILocalizedIndexedCollation sortedArrayFromArray: method to sort each array
NSArray *sortedSection = [indexedCollation sortedArrayFromArray:singleSectionArray collationStringSelector:#selector(sProductSearch)];
NSSortDescriptor * descriptor = [[NSSortDescriptor alloc] initWithKey:#"sItemDescription" ascending:NO]; // 1
NSArray *sortedByNameArray = [sortedSection sortedArrayUsingDescriptors:[NSArray arrayWithObject:descriptor]];
// [self.retailers addObject:sortedSection];
[self.retailers addObject:sortedByNameArray];
}
NSLog(#"The count on manualTap retailers is: %d",[self.retailers count]);
/***************************************END - Implementing Tableview Sections and Index*****************************/
/*********************Programmatically create a search bar***********************/
self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0.0f,0.0f, 320.0f, 44.0f)];
self.shoppingListTable.tableHeaderView = self.searchBar;
//Create and configure the search controller
self.searchController = [[UISearchDisplayController alloc]initWithSearchBar:self.searchBar contentsController:self];
self.searchController.searchResultsDataSource = self;
self.searchController.searchResultsDelegate = self;
bfiltered = NO;
searchBar.keyboardType = UIKeyboardTypeNumbersAndPunctuation; //UIKeyboardTypeNumberPad;
}
I happened into this when the collection that is being sorted by the UILocalizedIndexedCollation had multiple objects that returned nil when the selector was invoked on them.
In your code, this was the line that was breaking for me:
NSArray *sortedSection = [indexedCollation sortedArrayFromArray:singleSectionArray collationStringSelector:#selector(sProductSearch)];
My solution was to make sure that any objects that returned nil when the selector was invoked didn't get added to the array sent to the UILocalizedIndexedCollation.
For example, in your code, I did something like this:
for (Product *theRetailer in productsWithNoBarcodeArray)
{
if ([theRetailer valueForKey:#"sProductSearch"] != nil)
{
[(NSMutableArray *)[sectionsArray objectAtIndex:theRetailer.section] addObject:theRetailer];
}
}
Related
I have a button that whenever it is pressed is supposed to increment by one, which as a result will display a new string into a textfield. I have a Core Data manager class that contains a method which is supposed to retrieve my arrays, however whenever I press the button the app crashes. I'm left with an error stating ...
"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UsefulCodes rangeOfCharacterFromSet:]: unrecognized selector sent to instance 0x6000000a3f60'"
"UsefulCodes" is the name of my Core Data entity, and "codeName" and "codeDescription" are the attributes fro my presetList and codeDescArray respectively. I think its not working because I'm trying to put a Core Data object into a textfield, but all the objects in the arrays are strings, how would I get it to pass successfully into the textfield? I'll post relevant code below.
From CoreDataManager -
#implementation CoreDataManager
+(CoreDataManager*)sharedInstance {
static CoreDataManager *sharedObject;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedObject = [[CoreDataManager alloc] init];
});
return sharedObject;
}
-(void)storeListFirstTime {
//http://morsecode.scphillips.com/morse.html
NSArray* presetList = [NSArray arrayWithObjects:#"AS",
#"BCNU",
#"CL",
#"CT",
#"CUL",
#"K",
#"QSL",
#"QSL?",
#"QRX?",
#"QRV",
#"QRV?",
#"QTH",
#"QTH?",
#"R",
#"SN",
#"SOS",
#"73",
#"88",
nil];
NSArray* codeDescArray = [NSArray arrayWithObjects:#"Wait",
#"Be seeing You",
#"Going off air",
#"Start Copying",
#"See you later",
#"Over",
#"I acknowledge receipt",
#"Do you acknowledge",
#"Should I wait",
#"Ready to copy",
#"Are you ready to copy?",
#"My location is ...",
#"What is your location?",
#"Roger",
#"Understood",
#"Distress message",
#"Best regards",
#"Love and kisses",
nil];
//Saves the initial list of items
for(int i = 0; i < presetList.count; i++) {
NSManagedObjectContext *context = [self context];
UsefulCodes *codeObj = [NSEntityDescription insertNewObjectForEntityForName:#"UsefulCodes"
inManagedObjectContext:context];
codeObj.codeName = [presetList objectAtIndex:i];
codeObj.codeDescription = [codeDescArray objectAtIndex:i];
NSLog(#"CODEOBJ: %#", codeObj);
}
[self saveContext];
}
//THIS IS THE METHOD I AM CALLING FOR MY BUTTON METHOD
-(NSArray*)fetchAllRecords {
NSManagedObjectContext *context = [self context]; //this context is conected to persistant container
NSError *error = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"UsefulCodes"];
NSArray *records = [context executeFetchRequest:request
error:&error];
if(error == nil && records.count > 0) {
return [records copy];
}
else {
//Handle error by returning new array
return [NSArray new];
}
}
The above code just sets the initial values I hard-coded into my arrays (I'm only interested in getting the values from presetList array to display in textfield) as well as accommodating any new codes and descriptions I've added to any arrays. Now here is the method in my "MainViewController" responsible for cycling through my array.
In header file I declare a property called num.
#property(nonatomic)int num;
This is the method in implementation file.
- (void)getQuickMorseCode {
NSArray *array = [[CoreDataManager sharedInstance] fetchAllRecords];
if(_num == 0 || _num > 0) {
if(_num >= array.count){
_num = 0;
}
//GETTING THE SPECIFIC INDEX OF THE ARRAY, THIS IS WHERE THE PROBLEM LIES
self.morseTextfield.text = array[_num];
[self convertInput:self.morseTextfield.text];
[self buttonAppear];
_num++;
}
}
My goal is to display the strings that are in the presetList array in my CoreDataManager file, but I'm unable to get that to pass into my textfield. Any insight to my problem is welcomed.
Your fetchAllRecords method returns an array of UsefulCodes objects. So each element of array is a UsefulCodes object. You cannot therefore assign the element to the text property of a textfield. That's the cause of your error.
You need instead to extract the relevant string from the UsefulCodes object. From your other code, you store the presetList values in the codename attribute of the UsefulCodes objects:
codeObj.codeName = [presetList objectAtIndex:i];
So, reverse that process to obtain the relevant string from the codename attribute:
UsefulCodes *codeObj = (UsefulCodes *)array[_num];
NSString *presetListValue = codeObj.codeName;
self.morseTextfield.text = presetListValue;
I am trying to display sections and rows correctly for my uiTableView.
I have had great help from one contributor and am fairly close to fixing my issue. The Issue can be seen here. Its not far off being right, its just the sections that need to be sorted.
It is repeating the section titles instead of only showing it once. Im not sure exactly how to fix this.
// Find out the path of recipes.plist
NSString *path = [[NSBundle mainBundle] pathForResource:#"lawpolice" ofType:#"plist"];
// Load the file content and read the data into arrays
self.dataArray = [NSArray arrayWithContentsOfFile:path];
//Sort the array by section
self.sortedArray = [self.dataArray sortedArrayUsingDescriptors:#[
[NSSortDescriptor sortDescriptorWithKey:#"Section" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"Title" ascending:YES]]];
self.temp = [[NSMutableDictionary alloc] init];
for (NSDictionary *dict in self.sortedArray) {
NSMutableArray *array = self.temp[dict[#"Section"]];
// No items with the same section key stored yet, so we need to initialize a new array.
if (array == NULL) {
array = [[NSMutableArray alloc] init];
}
// Store the title in the array.
[array addObject:dict[#"Title"]];
// Save the array as the value for the section key.
[self.temp setObject:array forKey:dict[#"Section"]];
}
self.policePowers = [self.temp copy]; // copy returns an immutable copy of temp.
//Section for sorting
self.sectionArray = [self.sortedArray valueForKeyPath:#"Section"];
NSLog(#"%#", self.sectionArray);
//Title
self.namesArray = [self.sortedArray valueForKeyPath:#"Title"];
//Offence
self.offenseArray = [self.sortedArray valueForKeyPath:#"Offence"];
//Points to Prove
self.ptpArray = [self.sortedArray valueForKeyPath:#"PTP"];
//Action
self.actionsArray = [self.sortedArray valueForKeyPath:#"Actions"];
//Notes
self.notesArray = [self.sortedArray valueForKeyPath:#"Notes"];
//Legislation
self.legislationArray = [self.sortedArray valueForKeyPath:#"Legislation"];
//PNLD
self.pnldArray = [self.sortedArray valueForKeyPath:#"PNLD"];
//Image
self.imageString = [self.sortedArray valueForKeyPath:#"image"];
titleForHeaderInSection
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [self.sectionArray objectAtIndex:section];
}
numberOfSectionsInTableView
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.policePowers count];
}
numberOfRowsInSection
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSArray *sectionrows = self.policePowers[self.sectionArray[section]];
return [sectionrows count];
}
Update
To be clear, if two items have the same Section value, I want to automatically group them into an array and have that array mapped to the Section value at the end
NSDictionary dictionaryWithObjects:forKeys: basically loops through two arrays and maps the object in one array at the current index as the key for the object in the other array at the same index. When you're calling
self.policePowers = [NSDictionary dictionaryWithObjects:self.namesArray forKeys:self.sectionArray];
it therefore maps the items in self.sectionArray as the keys for the items in self.namesArray. Looking at your plist file, the "Title" keypath (which is mapped to self.namesArray) has a value of string, so your NSLog results make sense, as self.namesArray is an array of strings, not an array of arrays.
I'm not sure how you were supposed to get a result like
"Alcohol: Licensing/Drive unfit" = {
"Drive/attempt to drive/in charge whilst unfit or over",
"Drive/attempt to drive/in charge whilst unfit or over",
"Drive/attempt to drive/in charge whilst unfit or over",
}
Where is that array supposed to come from?
-- EDIT --
I don't think there's a concise way to accomplish what you want, so it'd have to be done manually. I haven't actually used [NSArray arrayWithContentsOfFile:path] before, so is self.dataArray an array of dictionaries with each item representing one of the items in the plist (Item 44, Item 45, etc)? If so, you could do something like this:
NSMutableDictionary *temp = [[NSMutableDictionary alloc] init];
for (NSDictionary *dict in self.dataArray) {
NSMutableArray *array = temp[dict[#"Section"]];
// No items with the same section key stored yet, so we need to initialize a new array.
if (array == null) {
array = [[NSMutableArray alloc] init];
}
// Store the title in the array.
[array addObject:dict[#"Title"]];
// Save the array as the value for the section key.
[temp setObject:array forKey:dict[#"Section"]];
}
self.policePowers = [temp copy]; // copy returns an immutable copy of temp.
-- EDIT AGAIN --
The app crashes because self.policePowers is an NSDictionary, not an NSArray; thus it doesn't have an objectAtIndex: method. If you're trying to get the section title, try this instead:
return [self.sectionArray objectAtIndex:section];
Furthermore, if you're working with a table view, I'd basically have self.sectionArray sorted whichever way you like, then whenever I needed to populate data in each section, I would use self.policePowers[self.sectionArray[section]] to return the array of titles mapped to that section title.
-- YET ANOTHER --
If you break it up into the following lines, where is the NSRangeException thrown? If you NSLog, do the results match what you expect?
NSString *title = self.sortedKeys[indexPath.section];
NSArray *array = self.policePowers[title];
NSString *value = array[indexPath.row];
I have a sequence of code. In it there are custom objects: Item and ItemList. The relevant members of these are Item.code and ItemList.ItemDictionary. Item.code is simply a NSString code (Like "AVK") that uniquely identifies an Item. These Items are separated into categories. There are 4 categories (Named "CatOne", etc). The ItemDictionary is a dictionary with the category names as keys and an NSMutableArray as the object; the array is filled with Item objects that are part of the category.
The basic problem is that when I try to access the Item.code, the string comes out as (null).
The functionality is that I have an array of updated Items (updatedItems) and I want to update the ItemList.ItemDictionary with these new Items.
The following are all properties of the object, and are synthesized in the main file.
#synthesize ItemListFromFile;
#synthesize upDatedItems;
#synthesize tempPassedManifest;
And the code:
-(id) upDatedItems:(NSArray *)newItems manifest:(Manifest *)manifest {
ItemListFromFile = [[ItemList alloc] init];
ItemListFromFile.ItemDictionary = [[NSMutableDictionary alloc] init];
ItemListFromFile = [NSKeyedUnarchiver unarchiveObjectWithFile:manifest.ItemListSavePath];
updatedItems = newItems
tempPassedManifest = manifest;
[self UpdateItemList];
return self;
}
-(void)UpdateItemList {
NSMutableArray *newItemArray = [[NSMutableArray alloc] init];
NSMutableArray *oldItemArray = [[NSMutableArray alloc] init];
// Go through each category. "i" is category Number
for (int i=1; i <= [Number of Categories]; i++)
{
NSString *currentCategoryName = [Get Category Name]; //This works...
// ********* Debug statements ************
// This Loop is where NSLog shows something's not right
// These conditions work fine.
for (int j = 0; j < [[ItemListFromFile.ItemDictionary objectForKey:currentCategoryName] count]; j++)
{
// This Log outputs (null) when it should output the code from the Item
NSLog(#"Code from File: %#", [[[ItemListFromFile.ItemDictionary objectForKey:currentCategoryName] objectAtIndex:j] code]);
}
// ************** Debug ******************
if ([[ItemListFromFile.ItemDictionary allKeys] containsObject:currentCategoryName])
{
[oldItemArray setArray:[ItemListFromFile.ItemDictionary objectForKey:currentCategoryName]];
for (Item *anItem in oldItemArray)
{
NSLog(#"OldItemArray Code: %#", anItem.code);
}
}
else
{
[oldItemArray removeAllObjects];
}
[newItemArray removeAllObjects];
// Go through each Item in category i.
for (NSString *currentCode in [array of codes in category i (this works)])
{
// There's two options of where to grab each Item from: either upDatedItems or oldItemArray
BOOL inOldArray = YES;
for (Item *anItem in upDatedItems)
{
if ([anItem.code isEqualToString:currentCode])
{
[newItemArray addObject:anItem];
inOldArray = NO;
NSLog(#"%# was in updatedItems", currentCode);
break;
}
}
if (inOldArray)
{
// Checking in old Item Array
for (Item *anItem in oldItemArray)
{
NSLog(#"Checking %# against %#", anItem.code, currentCode);
// (anItem.code is null)
if ([anItem.code isEqualToString:currentCode])
{
[newItemArray addObject:anItem];
NSLog(#"%# was in oldItems", currentCode);
break;
}
}
}
}
//We've created the array, so add it to dictionary
[ItemListFromFile.ItemDictionary setObject:newItemArray forKey:currentCategoryName];
NSLog(#"New Dance Array: %#", newDanceArray);
}
[NSKeyedArchiver archiveRootObject:ItemListFromFile toFile:tempPassedManifest.ItemListSavePath];
}
I've tried to be as thorough as possible, but do not hesitate to ask for clarification.
Update
I've been trying a lot of solutions to this, but nothing seems to be working. Things I've tried:
Creating an object that takes the place of *anItem, so instead of a for loop that loops through the contents of the array, loops from i=0..[Count], and then sets the object at that index to the item I created before the loop. I thought this might help so that I could allocate and initialize the Item.code before setting it to whatever the Item has.
Using
oldItemArray = [ItemListFromFile.ItemDictionary objectForKey:currentCategoryName];
instead of
[oldItemArray setArray:[ItemListFromFile.ItemDictionary objectForKey:currentCategoryName]];
I tried updating all of the items in the ItemList.ItemDictionary so that when it archives, all of the items are fresh for the next trial run of the code.
I have a tableview that contains multiple sections, grouped by an attribute of my object (date) I try to sort the tableview according to the value of date.I created a function for that , but I get an error :
- (void)sortObjectsDictionnary:(NSArray *)arrayObjects
{
//this is my nsdictionnary
[objects removeAllObjects]
//this is nsmutableaaray that contains dats
[objectsIndex removeAllObjects];
NSMutableSet *keys = [[NSMutableSet alloc] init];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy/MM/dd"];
for(int i=0;i<[arrayObjects count];i++){
Task *myTask=[arrayObjects objectAtIndex:i];
//curentsection contains my objects whith dates
NSMutableArray *currentSection = [objects objectForKey:taskDate];
if (currentSection == nil)
{
[keys addObject:taskDate];
currentSection = [[[NSMutableArray alloc] init] autorelease];
[objects setObject:currentSection forKey:taskDate];
}
// we add objet to the right section
[currentSection addObject:myTask];
}
[dateFormatter release];
for (id element in keys)
{
[objectsIndex addObject:element];
NSMutableArray *currentSection = [objects objectForKey:element];
//I get an error in this line
[currentSection sortUsingSelector:#selector(compare:)];
}
You haven't mentioned what the error message is and I am assuming the error message may be due to accessing NSMutableArray array object without initializing it. Try an alloc and init for array before
NSMutableArray *currentSection = [NSMutableArray]alloc]init];
currentSection = [objects objectForKey:element];
//I get an error in this line
[currentSection sortUsingSelector:#selector(compare:)];
Well it is purely a guess. Post your error message if this not works.
Bharath
I have a simple TTTableViewController to represent a set of companies. I would like to sort the TableView alphabetically using sections and the letter selector on the right side of the TableView.
Is there an easy way to do this using Three20?
Currently I don't have any separate datasource.
- (void)createModel {
NSMutableArray* itemsArray = [[NSMutableArray alloc] init];
for(IDCompany* company in companies) {
[itemsArray addObject:[TTTableSubtitleItem itemWithText:[company title] subtitle:[company companyDescription] URL:[company urlToDetailView]]];
}
self.dataSource = [TTListDataSource dataSourceWithItems:itemsArray];
[itemsArray release];
}
for starters you should use TTSectionedDataSource. It supports sections by having 2 NSMutableArray - one for sections represented by an array of strings and the other by array of arrays with the items of the table for each section.
Getting the letters is pretty simple too. UITableViewDataSource supports:
- (NSArray*)sectionIndexTitlesForTableView:(UITableView*)tableView;
and the base class in three20 supports extracting them by doing this:
- (NSArray*)sectionIndexTitlesForTableView:(UITableView*)tableView {
return [TTTableViewDataSource lettersForSectionsWithSearch:YES summary:NO];
}
The best solution for you would be to create a new DataSource and inherit it from TTSectionedDataSource, then implement something like this to build the sections:
self.items = [NSMutableArray array];
self.sections = [NSMutableArray array];
NSMutableDictionary* groups = [NSMutableDictionary dictionary];
for (NSString* name in _addressBook.names) {
NSString* letter = [NSString stringWithFormat:#"%C", [name characterAtIndex:0]];
NSMutableArray* section = [groups objectForKey:letter];
if (!section) {
section = [NSMutableArray array];
[groups setObject:section forKey:letter];
}
TTTableItem* item = [TTTableTextItem itemWithText:name URL:nil];
[section addObject:item];
}
NSArray* letters = [groups.allKeys sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
for (NSString* letter in letters) {
NSArray* items = [groups objectForKey:letter];
[_sections addObject:letter];
[_items addObject:items];
}
For a complete working solution refer to the TTCatalog samples under the three20 source and in there you will find MockDataSource.m that has this code.