Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Home isEqualToString:] - ios

I am fetching data from a web service:
//temp1 is NSDictionary
//responseArr1 is NSMutableArray
for(temp1 in responseArr1).......,
{
quesTxt = [[temp1 objectForKey:#"question_text"] stringByReplacingOccurrencesOfString:#"%PROFILENAME%" withString:del.onlyFirstName];
quesID = [temp1 objectForKey:#"idlife_stage_question"];
Home *objHome=[[Home alloc] init];
objHome.homeQuesTxt = quesTxt;
objHome.homeQuesID = quesID;
[quesArray addObject:objHome];
//[quesArray addObject:[[temp1 objectForKey:#"question_text"] stringByReplacingOccurrencesOfString:#"%PROFILENAME%" withString:del.onlyFirstName]];//this works fine
}
All the date when i try to populate in picker view it gives exception.
picker view delegate:
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
UILabel *retval = (id)view;
if (!retval) {
retval= [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, [pickerView rowSizeForComponent:component].width, [pickerView rowSizeForComponent:component].height)];
}
retval.text = [quesArray objectAtIndex:row];.........// **EXCEPTION HERE**...
retval.font = [UIFont boldSystemFontOfSize:14];
retval.numberOfLines = 3;
return retval;
}
sample of my web service:
{
"idlife_stage_question" = 35;
"life_stage_idlife_stage" = 1;
"profile_idprofile" = 0;
"question_text" = "When %PROFILENAME% first smiled?";..//%PROFILENAME% replaces with user name which i have
sequence = 34;
}
Home is a subclass of NSObject
Please help.

retval.text = [quesArray objectAtIndex:row];
Here you are accessing quesArray.
Which is as : [quesArray addObject:objHome];
And objHome is : Home *objHome=[[Home alloc] init];
So you error is here, you tried to put the object into the retval which expects an NSString.
You need to use something as :
retval.text = [[quesArray objectAtIndex:row] homeQuesTxt]; //or anyother property that you want to show in text

retval.text = [quesArray objectAtIndex:row];
Your retval.text is a string and you are assigning it an object
You could do like this
Home *newHome=[quesArray objectAtIndex:row];
retval.text=newHome.homeQuesTxt;

The quesArray is populated with Home Objects
Home *home = quesArray[row];
retval.text = home.homeQuesTxt;

Related

iOS Replace Keyboard with UIPickerView then Back to Keyboard

I have a textfield in my app in which a user can choose a Group they belong to, along with several other fields. When they get to the Group TextField, it pops up a UIPickerView that has several choices that have already been created, along with an option to "Create New Group". When that option is chosen, I want the UIPickerView to go away, and have the keyboard pop back up so they can type again. I can get the picker view to appear and to go away, but can't get the keyboard back. Here is code so far.
-(void)addPickerView{
__block NSMutableArray *arr = [[NSMutableArray alloc] init];
PFQuery *rejectedNumber = [PFQuery queryWithClassName:#"Group"];
[rejectedNumber orderByAscending:#"GroupName"];
[rejectedNumber findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!objects) {
// Did not find any UserStats for the current user
NSLog(#"NotFound");
} else {
pickerArray = objects;
[myPickerView reloadAllComponents];
}
}];
//pickerArray = GoGo;
self.theView.signUpView.additionalField.delegate = self;
[self.theView.signUpView.additionalField setPlaceholder:#"Choose Group"];
myPickerView = [[UIPickerView alloc]init];
myPickerView.dataSource = self;
myPickerView.delegate = self;
myPickerView.showsSelectionIndicator = YES;
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc]
initWithTitle:#"Done" style:UIBarButtonItemStyleDone
target:self action:#selector(finishIt)];
UIToolbar *toolBar = [[UIToolbar alloc]initWithFrame:
CGRectMake(0, self.view.frame.size.height-
self.theView.signUpView.additionalField.frame.size.height-50, 320, 50)];
[toolBar setBarStyle:UIBarStyleBlackOpaque];
NSArray *toolbarItems = [NSArray arrayWithObjects:
doneButton, nil];
[toolBar setItems:toolbarItems];
self.theView.signUpView.additionalField.inputView = myPickerView;
self.theView.signUpView.additionalField.inputAccessoryView = toolBar;
}
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component{
return [pickerArray count] + 1;
}
#pragma mark- Picker View Delegate
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:
(NSInteger)row inComponent:(NSInteger)component{
if (row == 0)
[self.theView.signUpView.additionalField.inputView removeFromSuperview];
[self.theView.signUpView.additionalField becomeFirstResponder];
}
else {
[self.theView.signUpView.additionalField setText:self.theGroup];
}
}
- (NSString*)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
NSString *groupName = #"Create New Group";
if (row != 0)
{
PFObject *object = pickerArray[row - 1]; // -1 to handle the array index
self.theGroup = object[#"GroupName"];
groupName = object[#"GroupName"];
}
return groupName;
}
First thing i noticed is, you missed a braces here after if condition:
if (row == 0)
[self.theView.signUpView.additionalField.inputView removeFromSuperview];
[self.theView.signUpView.additionalField becomeFirstResponder];
}
Instead use this:
if (row == 0) {
[self.theView.signUpView.additionalField resignFirstResponder];
self.theView.signUpView.additionalField.inputView = nil;
[self.theView.signUpView.additionalField becomeFirstResponder];
}

UIPopoverController is not so with a color

Does anyone know why is appearing white part? My View is already gray, but gets two white pieces
whites: Arrow and final Popover!
[UPDATE]
this is the code that calls the popover and makes the arrow points to the button that was clicked!
- (void) buttonFilter {
if (viewFilter == #"Artistas") {
content = [self.storyboard instantiateViewControllerWithIdentifier:#"TipoArtistaViewController"]; // MUDAR PARA O NOVO FILTRO DE ARTISTAS
} else if (viewFilter == #"Músicas") {
content = [self.storyboard instantiateViewControllerWithIdentifier:#"CategoriaViewController"];
}
[self callFilter:btnFilter Filter:content];
}
- (void)callFilter:(id)sender Filter:(UIViewController *) content{
self.currentPop = popoverController;
popoverController = [[WYPopoverController alloc] initWithContentViewController:content];
UIButton * bt = (UIButton * )sender;
UIView *view = [bt valueForKey:#"view"];
popoverController.popoverContentSize = CGSizeMake(320, 180);
popoverController.delegate = self;
[popoverController presentPopoverFromRect:view.bounds inView:view permittedArrowDirections:WYPopoverArrowDirectionAny animated:YES];
}
the next is where to mount the session:
//extend and collpase
- (void)setupViewController {
categoriaBD = [categoriaDAO selectCategoria];
self.data = [[NSMutableArray alloc] init];
for (int i = 0; i < [categoriaBD count]; i++)
{
NSMutableDictionary * teste = [categoriaBD objectForKey:[NSString stringWithFormat:#"%i", i]];
ID = [[teste objectForKey:#"1"] integerValue];
subcategoriaBD = [categoriaDAO selectSubCategoriaByCategoriaID:ID];
NSMutableArray* section = [[NSMutableArray alloc] init];
for (int j = 0; j < [subcategoriaBD count]; j++)
{
NSMutableDictionary * subCat = [subcategoriaBD objectForKey:[NSString stringWithFormat:#"%i", j]];
[section addObject:[NSString stringWithFormat:[subCat objectForKey:#"1"]]];
}
[self.data addObject:section];
}
self.headers = [[NSMutableArray alloc] init];
for (int i = 0; i < [categoriaBD count]; i++)
{
NSString *inStr = [NSString stringWithFormat: #"%i", (int)i];
nomeCategoria = [categoriaBD objectForKey:inStr];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, 310, 40)];
[label setText:[nomeCategoria objectForKey:#"2"]];
UIView* header = [[UIView alloc] init];
[header setBackgroundColor:[UIColor colorWithRed:(226/255.0) green:(226/255.0) blue:(226/255.0) alpha:1]];
[header addSubview:label];
[self.headers addObject:header];
}
}
You can to create a custom UIPopoverBackgroundView subclass that sets the properties of the arrow you want.
popoverController.popoverBackgroundViewClass = [MyPopoverBackgroundView class];

UITableView sorting

I've been brought in on this project where the previous developers made custom table cells and headers by using xib files and then registering the nibs like so:
[self.accountTable registerNib:[UINib nibWithNibName:kNonATITableViewCellLandscapeNib bundle:[NSBundle mainBundle]] forCellReuseIdentifier:kNonATITableViewCellLandscapeIdentifier];
[self.accountTable registerNib:[UINib nibWithNibName:kNonATITableHeaderLandscapeNib bundle:[NSBundle mainBundle]] forCellReuseIdentifier:kNonATITableHeaderLandscapeId];
The header files have buttons in them and uiimageviews. The buttons are for sorting, the uiimageviews for an arrow icon to show you the direction of the sort (asc, desc). All the buttons and imageviews are IBOutlets. All the buttons are linked to an IBAction:
- (IBAction)sortButtonTouched:(id)sender;
The file also has two other properties:
#property (nonatomic, assign) SortType currentSortingOption;
#property (nonatomic, strong) UIButton* btnLastTouched;
Here is sortButtonTouched:
- (IBAction)sortButtonTouched: (UIButton*) buttonTouched {
if (!self.btnLastTouched) {
self.btnLastTouched = buttonTouched;
}
NSString* strFieldToSort;
UIImageView* ivSortImage;
NSArray* arrSortIcons = [[NSArray alloc] initWithObjects:self.ivAccountSort,self.ivNameSort, self.ivAddressSort, self.ivCitySort, self.ivZipSort, self.ivLastCallSort, self.ivMileageSort, nil];
//get the image for the button selected
if (buttonTouched.tag == 0) {
strFieldToSort = #"customerNumber";
ivSortImage = self.ivAccountSort;
} else if (buttonTouched.tag == 1) {
strFieldToSort = #"customerName";
ivSortImage = self.ivNameSort;
} else if (buttonTouched.tag == 2) {
strFieldToSort = #"address";
ivSortImage = self.ivAddressSort;
} else if (buttonTouched.tag == 3) {
strFieldToSort = #"city";
ivSortImage = self.ivCitySort;
} else if (buttonTouched.tag == 4) {
strFieldToSort = #"zip";
ivSortImage = self.ivZipSort;
} else if (buttonTouched.tag == 5) {
strFieldToSort = #"lastCallDate";
ivSortImage = self.ivLastCallSort;
} else if (buttonTouched.tag == 6) {
strFieldToSort = #"mileage";
ivSortImage = self.ivMileageSort;
}
//set the sort option and add icon
if (!self.currentSortingOption) {
self.currentSortingOption = SORT_ASC;
[ivSortImage setImage:[UIImage imageNamed:Ascending_Icon]];
} else {
if (![self.btnLastTouched isEqual:buttonTouched]) {
self.currentSortingOption = SORT_ASC;
[ivSortImage setImage:[UIImage imageNamed:Ascending_Icon]];
} else {
if (self.currentSortingOption == SORT_ASC) {
self.currentSortingOption = SORT_DESC;
[ivSortImage setImage:[UIImage imageNamed:Descending_Icon]];
} else {
self.currentSortingOption = SORT_ASC;
[ivSortImage setImage:[UIImage imageNamed:Ascending_Icon]];
}
}
}
//show and hide
for(int i=0; i<arrSortIcons.count; i++) {
UIImageView* ivThisImage = [arrSortIcons objectAtIndex:i];
if (buttonTouched.tag == i) {
[UIView animateWithDuration:.25 animations:^(void) {
ivThisImage.alpha = 1.0;
}];
} else {
[UIView animateWithDuration:.25 animations:^(void) {
ivThisImage.alpha = 0.0;
}];
}
}
//call back to routing view controller and sort results based on sort order and field selected
NSDictionary* dictUserData = [[NSDictionary alloc] initWithObjectsAndKeys:
#"Sort Non-ATI", #"Action",
strFieldToSort, #"Field To Sort",
[NSNumber numberWithLong:self.currentSortingOption], #"Sortng Option",
nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"rvc" object:self userInfo:dictUserData];
self.btnLastTouched = buttonTouched;
}
And the notification fires this method:
- (void) sortNonATIResults : (NSDictionary*) dictSortParams {
if (self.arrNonATIResults.count > 0) {
NSString* sortKey = [dictSortParams objectForKey:#"Field To Sort"];
//change the field to sort to match the customerInfo object properties...
NSNumber* numSortType = [dictSortParams objectForKey:#"Sortng Option"];
BOOL isAsc = YES;
if ([numSortType intValue] == 2) {
isAsc = NO;
}
NSSortDescriptor* sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:isAsc];
NSArray* arrSortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
NSArray* arrSortedNonATIResults = (NSArray*)[self.arrNonATIResults sortedArrayUsingDescriptors:arrSortDescriptors];
self.arrNonATIResults = [arrSortedNonATIResults mutableCopy];
self.arrDatasource = self.arrNonATIResults;
[self.accountTable reloadData];
}
}
There are two problems right now. The icons are not showing up if the notification is sent. Comment out the notification and they function as expected. The other problem is that the property currentSortingOption doesn't retain it's value. I think both issues are related but I am not 100% sure. When the tableview is reloaded, does the header get instantiated again? This would make sense to me since then the uiimageviews would be reset with no image and the property would lose it's value and reset to 0 (it is the value of a typedef).
So, I am correct, how can I resolve this and if not, what could be causing the problems?
Thanks
OK, sorry for posting and then solving my problem right away, I guess sometimes you just need to write out the problem to find the solution. All I needed to do was not reload the table but just reload the rows. Here's the updated method:
(void) sortNonATIResults : (NSDictionary*) dictSortParams {
if (self.arrNonATIResults.count > 0) {
NSString* sortKey = [dictSortParams objectForKey:#"Field To Sort"];
//change the field to sort to match the customerInfo object properties...
NSNumber* numSortType = [dictSortParams objectForKey:#"Sortng Option"];
BOOL isAsc = YES;
if ([numSortType intValue] == 2) {
isAsc = NO;
}
NSSortDescriptor* sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:isAsc];
NSArray* arrSortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
NSArray* arrSortedNonATIResults = (NSArray*)[self.arrNonATIResults sortedArrayUsingDescriptors:arrSortDescriptors];
self.arrNonATIResults = [arrSortedNonATIResults mutableCopy];
self.arrDatasource = self.arrNonATIResults;
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *indexPathArray = [[NSMutableArray alloc] init];
for (NSInteger section = 0; section < [self.accountTable numberOfSections]; ++section)
{
for (NSInteger row = 0; row < [self.accountTable numberOfRowsInSection:section]; ++row)
{
[indexPathArray addObject:[NSIndexPath indexPathForRow:row inSection:section]];
}
}
[self.accountTable reloadRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone];
[self.accountTable scrollsToTop];
});
}
}

iOS NSFetchedResultsController: Is it possible to use same cache for the same fetch in different View Controllers?

I have two different view controllers displaying almost the same fetch request. One of them displays more info than the other and is also used for editing of the data. The second one is just for display.
Is there a way to speed things up, since both are displaying exactly the same data? I'm looking for something in my code which makes the whole reorder slow after displaying the second view controller for the first time. In other words: Initially the reordering in my main view controller is very fast. Then you just display the second view controller and switch back and then the reordering in the main view controller gets slow. I have reason to assume that it is because they are using the same fetch.
I'm initiating the fetch request in both viewDidLoad methods, as in the following code snippets. The first one is my main view controller, and also the first one displayed when starting the app:
- (void)setupFetchedResultsController
{
self.managedObjectContext = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).managedObjectContext;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"MainCategory"];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"position" ascending:YES]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:#"MainCategoryCache"];
}
And this is my second one:
- (void)setupFetchedResultsController
{
self.managedObjectContext = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).managedObjectContext;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"MainCategory"];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"position" ascending:YES]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:#"NetCache"];
}
Here are the view did load and cellForRowAtIndexPath of my main view controller:
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupFetchedResultsController];
//Edit/Done button
UIBarButtonItem *editButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(#"Edit", nil) style:UIBarButtonItemStyleBordered target:self action:#selector(editTable:)];
[self.navigationItem setRightBarButtonItem:editButton];
//Budget at bottom
self.sumTitleLabel.text = [NSString stringWithFormat:#"%#%#:",NSLocalizedString(#"Budget", nil),NSLocalizedString(#"PerMonth", nil)];
self.sumLabel.text = [[DatabaseFetches budgetPerMonthForManagedObjectContext:self.managedObjectContext] getLocalizedCurrencyString];
//Layout
self.view.backgroundColor = [UIColor clearColor];
[self styleTableView];
[self styleBudgetTotal];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Data model and cell setup
static NSString *CellIdentifier = #"MainCategoryCell";
MainCategoryTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
MainCategory *mainCategory = [self.fetchedResultsController objectAtIndexPath:indexPath];
//Clear background color
cell.backgroundColor = [UIColor clearColor];
//Setup the cell texts
cell.title.text = mainCategory.name;
int numberOfSubcategories = [[mainCategory getNumberOfSpendingCategories] integerValue];
if (numberOfSubcategories == 1) {
cell.subcategories.text = [NSString stringWithFormat:#"%i %#", numberOfSubcategories, NSLocalizedString(#"subcategory", nil)];
} else {
cell.subcategories.text = [NSString stringWithFormat:#"%i %#", numberOfSubcategories, NSLocalizedString(#"subcategories", nil)];
}
cell.costs.text = [[mainCategory getMonthlyCostsOfAllSpendingCategories] getLocalizedCurrencyString];
//Delegation
cell.title.delegate = self;
//Format text
cell.title.font = [Theme tableCellTitleFont];
cell.title.textColor = [Theme tableCellTitleColor];
cell.title.tag = indexPath.row;
cell.subcategories.font = [Theme tableCellSubTitleFont];
cell.subcategories.textColor = [Theme tableCellSubTitleColor];
cell.costs.font = [Theme tableCellValueFont];
cell.costs.textColor = [Theme tableCellValueColor];
//Icon
UIImage *icon;
if(!mainCategory.icon){
icon = [UIImage imageNamed:#"DefaultIcon.png"];
} else {
icon = [UIImage imageNamed:mainCategory.icon];
}
[cell.iconButton setImage:icon forState: UIControlStateNormal];
cell.icon.image = icon;
cell.iconButton.tag = indexPath.row;
//Background image
cell.cellBackground.image = [[UIImage imageNamed:#"content-bkg"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
//Selection
//cell.title.highlightedTextColor = cell.title.textColor;
cell.subcategories.highlightedTextColor = cell.subcategories.textColor;
cell.costs.highlightedTextColor = cell.costs.textColor;
return cell;
}
Complete second view controller:
#interface NetIncomeViewController()
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
#end
#implementation NetIncomeViewController
#synthesize incomeTitleLabel = _incomeTitleLabel;
#synthesize incomeValueTextField = _incomeValueTextField;
#synthesize incomeBackground = _incomeBackground;
#synthesize incomeValueBackground = _incomeValueBackground;
#synthesize netIncomeTitleLabel = _netIncomeTitleLabel;
#synthesize netIncomeValueLabel = _netIncomeValueLabel;
#synthesize netIncomeBackground = _netIncomeBackground;
#synthesize netIncomeValueBackground = _netIncomeValueBackground;
#synthesize managedObjectContext = _managedObjectContext;
#pragma mark Initializer and view setup
- (void)setupFetchedResultsController
{
self.managedObjectContext = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).managedObjectContext;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"MainCategory"];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"position" ascending:YES]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
}
#pragma mark View lifecycle
- (void)awakeFromNib{
[super awakeFromNib];
//Title (necessary here because otherwise the tab bar would be only localized after first click
//on the respective tab bar and not from beginning
self.title = NSLocalizedString(#"Net Income", nil);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateNetIncome];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupFetchedResultsController];
//Edit/Done button
UIBarButtonItem *editButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(#"Edit", nil) style:UIBarButtonItemStyleBordered target:self action:#selector(editIncome:)];
[self.navigationItem setRightBarButtonItem:editButton];
//Get or set and get the gross income
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber *grossIncome = [defaults objectForKey:#"income"];
if (!grossIncome) {
[defaults setDouble:0 forKey:#"income"];
[defaults synchronize];
grossIncome = [defaults objectForKey:#"income"];
}
self.incomeValueTextField.enabled = NO;
self.incomeValueTextField.delegate = self;
self.incomeValueTextField.keyboardType = UIKeyboardTypeDecimalPad;
self.incomeValueTextField.text = [grossIncome getLocalizedCurrencyString];
[self styleTableViewAndIncome];
}
- (void)styleTableViewAndIncome
{
self.view.backgroundColor = [UIColor clearColor];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.backgroundColor = [UIColor clearColor];
self.incomeTitleLabel.textColor = [Theme budgetValueTitleColor];
self.incomeTitleLabel.font = [Theme budgetValueTitleFont];
self.incomeValueTextField.textColor = [Theme budgetValueColor];
self.incomeValueTextField.font = [Theme budgetValueTitleFont];
self.netIncomeTitleLabel.textColor = [Theme budgetValueTitleColor];
self.netIncomeTitleLabel.font = [Theme budgetValueTitleFont];
self.netIncomeValueLabel.textColor = [Theme budgetValueColor];
self.netIncomeValueLabel.font = [Theme budgetValueFont];
self.incomeTitleLabel.text = [NSString stringWithFormat:#"%#:",NSLocalizedString(#"Income", nil)];
self.netIncomeTitleLabel.text = [NSString stringWithFormat:#"%#%#:",NSLocalizedString(#"Net", nil),NSLocalizedString(#"PerMonth", nil)];
self.incomeBackground.image = [[UIImage imageNamed:#"content-bkg"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
self.incomeValueBackground.image = [[UIImage imageNamed:#"price-bkg"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
self.netIncomeBackground.image = [[UIImage imageNamed:#"content-bkg"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
}
#pragma mark Table view delegate methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CategoryCostCell";
CategoryCostTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor clearColor];
// Configure the cell layout
MainCategory *mainCategory = [self.fetchedResultsController objectAtIndexPath:indexPath];
// Configure the cell layout
cell.categoryBackground.image = [[UIImage imageNamed:#"content-bkg"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
if(!mainCategory.icon){
cell.categoryImage.image = [UIImage imageNamed:#"DefaultIcon.png"];
} else {
cell.categoryImage.image = [UIImage imageNamed:mainCategory.icon];
}
cell.categoryName.text = mainCategory.name;
NSNumber *expenditures = [mainCategory getMonthlyCostsOfAllSpendingCategories];
double expendituresDouble =-[expenditures doubleValue];
if (expendituresDouble == 0) {
expenditures = [NSNumber numberWithDouble: 0];
} else {
expenditures = [NSNumber numberWithDouble: expendituresDouble];
}
cell.categoryCosts.text = [expenditures getLocalizedCurrencyString];
//Format the text
cell.categoryName.font = [Theme tableCellSmallTitleFont];
cell.categoryName.textColor = [Theme tableCellSmallTitleColor];
cell.categoryCosts.font = [Theme tableCellSmallValueFont];
cell.categoryCosts.textColor = [Theme tableCellSmallValueColor];
return cell;
}
#pragma mark TextField delegate methods
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
if(self.editing){
return YES;
} else {
return NO;
}
}
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
textField.text = [NSString stringWithFormat:#"%.2f",[NSNumber getUnLocalizedCurrencyDoubleWithString:textField.text]];
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
textField.text = [[NSNumber numberWithDouble:[textField.text doubleValue]] getLocalizedCurrencyString];
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
//Handle the enter button
[textField resignFirstResponder];
[self endEdit];
//Since this method is called AFTER DidEndEditing, we have to call editTable or in this case since we have put all the endEdit code in a seperate method this method directly in order to format the table back from edit mode to normal.
return YES;
}
#pragma mark Edit/Add/Delete action
- (IBAction) editIncome:(id)sender
{
if(self.editing)
{
[self endEdit];
} else {
[self beginEdit];
}
}
- (void) beginEdit
{
//Change to editing mode
[super setEditing:YES animated:YES];
//Exchange the edit button with done button
[self.navigationItem.rightBarButtonItem setTitle:NSLocalizedString(#"Done", nil)];
[self.navigationItem.rightBarButtonItem setStyle:UIBarButtonItemStyleDone];
self.incomeValueTextField.borderStyle = UITextBorderStyleRoundedRect;
self.incomeValueTextField.enabled = YES;
self.incomeValueBackground.hidden = YES;
self.incomeValueTextField.textColor = [Theme budgetValueTitleColor];
}
- (void) endEdit
{
//Change to editing no
[super setEditing:NO animated:YES];
//Remove Done button and exchange it with edit button
[self.navigationItem.rightBarButtonItem setTitle:NSLocalizedString(#"Edit", nil)];
[self.navigationItem.rightBarButtonItem setStyle:UIBarButtonItemStylePlain];
self.incomeValueTextField.borderStyle = UITextBorderStyleNone;
self.incomeValueTextField.enabled = NO;
self.incomeValueBackground.hidden = NO;
self.incomeValueTextField.textColor = [Theme budgetValueColor];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setDouble:[NSNumber getUnLocalizedCurrencyDoubleWithString:self.incomeValueTextField.text] forKey:#"income"];
[self updateNetIncome];
//[defaults synchronize];
}
#pragma mark Net income
- (void) updateNetIncome {
double grossIncome = [NSNumber getUnLocalizedCurrencyDoubleWithString:self.incomeValueTextField.text];
double budget = [[DatabaseFetches budgetPerMonthForManagedObjectContext:self.managedObjectContext] doubleValue];
double netIncome = grossIncome - budget;
if(netIncome >0){
self.netIncomeValueBackground.image = [[UIImage imageNamed:#"price-bkg_green"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
} else if(netIncome <0) {
self.netIncomeValueBackground.image = [[UIImage imageNamed:#"price-bkg_red"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
} else {
self.netIncomeValueBackground.image = [[UIImage imageNamed:#"price-bkg"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
}
self.netIncomeValueLabel.text = [[NSNumber numberWithDouble:netIncome] getLocalizedCurrencyString];
}
#end
It looks like you're doing UIImage creation in your cellForRowAtIndexPath method, which would cause a slowdown.
Better to create the background UIImage as an instance variables and set its image in the viewDidLoad, then reference it in the cellForRowAtIndexPath method. Like so:
//Declare the instance variable
UIImage *backgroundImage
//Set it in viewDidLoad
self.backgroundImage = [[UIImage imageNamed:#"content-bkg"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
//Assign it in cellForRowAtIndexPath
cell.backgroundImage = self.backgroundImage;
As for the icon, which has to be set for each row, you'll want to move the actual image loading off the main thread. You can do this either with dispatch_async() or NSOperationQueue. (pulled from here: Understanding dispatch_async)
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
UIImage *icon;
if(!mainCategory.icon){
icon = [UIImage imageNamed:#"DefaultIcon.png"];
} else {
icon = [UIImage imageNamed:mainCategory.icon];
}
dispatch_async(dispatch_get_main_queue(), ^(void){
[cell.iconButton setImage:icon forState: UIControlStateNormal];
cell.icon.image = icon;
cell.iconButton.tag = indexPath.row;
});
});
Notice how the imageNamed method is called in the background thread, but the actual assignment takes place on the main thread. You have to do it this way because you're not allowed to update UI elements anywhere except the main thread.

Custom UIPickerView crashes when datasource does not have data

I have a screen with two text fields (category, subcategory), each hooked up to a custom UIPickerView. The options present in the subcategory view depend on the category selected as the value of the first field.
If the user has not selected a category, selecting the subcategory field brings up the standard keyboard (this behavior is fine).
If the user selects a category and then interacts with the subcategory field everything works fine.
The problem happens when the user puts in a category, brings up the subcategory picker, and then goes back and clears the category field. At this point, if the user selects the subcategory field, the picker will appear without any data and interacting with it will cause the app to crash.
The error text:
*** Assertion failure in -[UITableViewRowData rectForRow:inSection:], /SourceCache/UIKit_Sim/UIKit-2380.17/UITableViewRowData.m:1630
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'request for rect at invalid index path (<NSIndexPath 0x719eb60> 2 indexes [0, 0])'
*** First throw call stack:
(0x1cc3012 0x1100e7e 0x1cc2e78 0xb96665 0x22df20 0xf12de 0x481086 0x480f7a 0xa440d 0xa69eb 0x30f85a 0x30e99b 0x3100df 0x312d2d 0x312cac 0x30aa28 0x77972 0x77e53 0x55d4a 0x47698 0x1c1edf9 0x1c1ead0 0x1c38bf5 0x1c38962 0x1c69bb6 0x1c68f44 0x1c68e1b 0x1c1d7e3 0x1c1d668 0x44ffc 0x2acd 0x29f5)
libc++abi.dylib: terminate called throwing an exception
Here is my code:
- (IBAction)showYourPicker:(id)sender {
isCategoryPicker = true;
// create a UIPicker view as a custom keyboard view
UIPickerView* pickerView = [[UIPickerView alloc] init];
[pickerView sizeToFit];
pickerView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
pickerView.delegate = self;
pickerView.dataSource = self;
pickerView.showsSelectionIndicator = YES;
self.catPickView = pickerView; //UIPickerView
categoryField.inputView = pickerView;
// create a done view + done button, attach to it a doneClicked action, and place it in a toolbar as an accessory input view...
// Prepare done button
UIToolbar* keyboardDoneButtonView = [[UIToolbar alloc] init];
keyboardDoneButtonView.barStyle = UIBarStyleBlack;
keyboardDoneButtonView.translucent = YES;
keyboardDoneButtonView.tintColor = nil;
[keyboardDoneButtonView sizeToFit];
UIBarButtonItem* doneButton = [[UIBarButtonItem alloc] initWithTitle:#"Done"
style:UIBarButtonItemStyleBordered target:self
action:#selector(pickerDoneClicked:)];
[keyboardDoneButtonView setItems:[NSArray arrayWithObjects:doneButton, nil]];
// Plug the keyboardDoneButtonView into the text field...
categoryField.inputAccessoryView = keyboardDoneButtonView;
}
- (IBAction)showYourSubPicker:(id)sender {
isCategoryPicker = false;
WCSharedCache *sharedManager = [WCSharedCache sharedManager];
BOOL iLLAllowIt = false;
for(int i = 0; i < [sharedManager.categories count]; i++) {
if([[[sharedManager categories] objectAtIndex:i] isEqualToString:[categoryField text]]) {
iLLAllowIt = true;
}
}
if(!iLLAllowIt) {
return;
}
// create a UIPicker view as a custom keyboard view
UIPickerView* pickerView = [[UIPickerView alloc] init];
[pickerView sizeToFit];
pickerView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
pickerView.delegate = self;
pickerView.dataSource = self;
pickerView.showsSelectionIndicator = YES;
self.subCatPickView = pickerView; //UIPickerView
subcategoryField.inputView = pickerView;
// create a done view + done button, attach to it a doneClicked action, and place it in a toolbar as an accessory input view...
// Prepare done button
UIToolbar* keyboardDoneButtonView = [[UIToolbar alloc] init];
keyboardDoneButtonView.barStyle = UIBarStyleBlack;
keyboardDoneButtonView.translucent = YES;
keyboardDoneButtonView.tintColor = nil;
[keyboardDoneButtonView sizeToFit];
UIBarButtonItem* doneButton = [[UIBarButtonItem alloc] initWithTitle:#"Done"
style:UIBarButtonItemStyleBordered target:self
action:#selector(pickerDoneClicked:)];
[keyboardDoneButtonView setItems:[NSArray arrayWithObjects:doneButton, nil]];
// Plug the keyboardDoneButtonView into the text field...
subcategoryField.inputAccessoryView = keyboardDoneButtonView;
}
- (void) pickerDoneClicked: (id) picker {
if(isCategoryPicker) {
[categoryField resignFirstResponder];
} else {
[subcategoryField resignFirstResponder];
}
}
- (void) pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
WCSharedCache *sharedManager = [WCSharedCache sharedManager];
if(isCategoryPicker) {
[categoryField setText:[[sharedManager categories] objectAtIndex:row]];
} else {
#try {
int idx = 0;
for(int i = 0; i < [sharedManager.categories count]; i++) {
if([[[sharedManager categories] objectAtIndex:i] isEqualToString:[categoryField text]]) {
idx = i;
break;
}
}
[subcategoryField setText:[[[sharedManager subcategories]objectAtIndex:idx] objectAtIndex:row]];
} #catch (NSException *e) {
NSLog(#"Exception: %#",e);
[subcategoryField setText:#"" ];
}
}
}
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
WCSharedCache *sharedManager = [WCSharedCache sharedManager];
if(isCategoryPicker) {
return [[sharedManager categories]count];
} else {
for(int i = 0; i < [sharedManager.categories count]; i++) {
if([[[sharedManager categories] objectAtIndex:i] isEqualToString:[categoryField text]]) {
return [[[sharedManager subcategories] objectAtIndex:i] count];
}
}
}
return 0;
}
- (NSString *)pickerView: (UIPickerView *)pickerView titleForRow: (NSInteger)row forComponent:(NSInteger)component
{
WCSharedCache *sharedManager = [WCSharedCache sharedManager];
if(isCategoryPicker) {
return [[sharedManager categories] objectAtIndex:row];
} else {
for(int i = 0; i < [sharedManager.categories count]; i++) {
if([[[sharedManager categories] objectAtIndex:i] isEqualToString:[categoryField text]]) {
return [[[sharedManager subcategories] objectAtIndex:i] objectAtIndex:row];
}
}
}
return #"";
}
Is there a method I can implement, or a try-catch, or revival of original keyboard, or somewhere I can prevent the user from hitting this field if there is no data any of those would work. The data for categories is in an NSArray. The data for subcategories is in a two-dimensional NSArray indexed off of the index of the associated category.
If fixed this problem by changing the default value in:
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
WCSharedCache *sharedManager = [WCSharedCache sharedManager];
if(isCategoryPicker) {
return [[sharedManager categories]count];
} else {
for(int i = 0; i < [sharedManager.categories count]; i++) {
if([[[sharedManager categories] objectAtIndex:i] isEqualToString:[categoryField text]]) {
return [[[sharedManager subcategories] objectAtIndex:i] count];
}
}
}
return 1;
}
from zero to one. Apparently, UIPickerView (at least in the implementation above) cannot handle scrolling without throwing an exception if the number of rows in the component is 0. This is curious, because according the Apple's documentation of the UIPickerView class, the default value for this method is 0.

Resources