dynamic UIPickerView race condition? - objective-c

I have a two level UIPickerView which means if user selects the first component, the second component will update its data.
The data should be look like this:
self.type = #[#"fruit", #"airlines"];
self.data = #[#[#"Apple", #"Orange"], #[#"Delta", #"United", #"American"]];
datasource and delegate method:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 2;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if(component == 0){
return self.data.count;
}else if(component == 1){
NSInteger row1 = [pickerView selectedRowInComponent:0];
return [self.data[row1] count];
}
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
if(component == 0){
[pickerView reloadComponent:1];
}
NSInteger row1 = [pickerView selectedRowInComponent:0];
NSInteger row2 = [pickerView selectedRowInComponent:1];
// here may crash, some time
NSLog(#"data: %#", self.data[row1][row2]);
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
if(component == 0){
return self.type[row];
}else if(component == 1){
NSInteger row1 = [pickerView selectedRowInComponent:0];
// here may also crash
return self.data[row1][row];
}
}
There are several places which may cause crashes(NSRangeException) in the above code as commented. However, they rarely happened. I haven't gotten this crash yet, but my users had according to Crashlytics report.
I add some codes to validate row1 and row2 before accessing self.data[row1][row2] and send to my server if fail. I found that the value of [pickerView selectedRowInComponent:0] may be incorrect some time. For example, when user changes the first component from "fruit" to "airlines" and select some item in second component, the value of selectedRowInComponent:0 may still be 0(index of "fruit").
I guess this is caused by race condition but how can I solve this?

It will crash if a component represents an array with three members (airlines) AND the selected row in that component is the last member and then you flip the component over to represent the two member array (fruit). The pickerview has retained this variable (selectedrowincomponent:x) and now wants to represent member #2 on a two member array (has members #0-1 only) -> NSRangeException. You need to alter the selectedRow in that component before you flip it to a different array with reload ..
I personally don't like using one picker to do two things, it's messy IMHO just write a different class to be delegate/dataSource for each, one can be a subclass of the other, then lazy load em as needed

Related

Selecting Objects from NSArray for further deleting with an IBAction

So I am trying to select objects from my array, to be able to delete them when I do an IBAction. I tried:
checking if the item is Selected:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (self.editEnabled) {
RDNote *selectedNote = [self.notes objectAtIndex:indexPath.row];
if (selectedNote.isSelected) {
selectedNote.selected = NO;
for (NSIndexPath *indexPathFromArray in self.indexPathsOfSelectedCells) {
if (indexPathFromArray.row == indexPath.row) {
[self.mutableCopy removeObject:indexPathFromArray];
}
}
} else {
selectedNote.selected = YES;
[self.indexPathsOfSelectedCells addObject:indexPath];
}
[self.collectionView reloadData];
IBAction:
- (IBAction)didTapTrashBarButton:(id)sender {
NSMutableArray *mutableNotes = [NSMutableArray arrayWithArray:self.notes];
for (NSIndexPath *indexPath in self.indexPathsOfSelectedCells) {
[mutableNotes removeObjectAtIndex:indexPath.row];
}
self.notes = [NSArray arrayWithArray:mutableNotes];
[self.collectionView performBatchUpdates:^{
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithArray:self.indexPathsOfSelectedCells]];
} completion:^(BOOL finished) {
self.indexPathsOfSelectedCells = nil;
[self activateEditMode:NO];
[self saveDataToFile:self.notes];
}];
}
But I am having issues with indexes e.g.:(some times shows me the Error that Object index 2 is not between [0..1]), and there are bugs while selecting multiple Object and Deleting them.
Please help me with an advice for some other method that I can use, a Code will be perfect! Thanks!
This problem arrises because:
Let say you have five objects in array 1,2,3,4,5
you are running a loop for removing object based on indexpath which are selected rows. Now your indexpath contains 1st row and 3rd row.
while executing it for the first time you will remove object 1. Now 2,3,4,5 will left in the array. Now second time your indexpath.row is 3rd. It will remove third object which is 4 but in actual array it was 3.
Your code is crashing sometimes because if you have selected first row and last row. In this case i have selected 1 and 5. Now my indexpaths array will say I have to delect objectsAtIndexes 1 and 5.
While executing the loop I'll remove object at Index 1. Now i will be left with 2,3,4,5. On second iteration it will say remove objectAtIndex 5 where as Index 5 doesn't exist because now we have 4 elements in array.
In such cases best way of doing this is try removing elements in array from the end like remove 5th element first and then go for other one. Run your loop in reverse order.
NSInteger i = [self.indexPathsOfSelectedCells count]-1;
while (i > 0){
NSIndexPath *indexPath = [self.indexPathsOfSelectedCells objectAtIndex:i];
[mutableNotes removeObjectAtIndex:indexPath.row];
i--;
}

UIPickerView with multiple components / 3rd component items based on component 1 and 2 selection

I just created a UIPickerView with three components. The user selects numbers in wheel 1 and 2 and based on those selections it will show either one of two possible arrays in wheel/component 3. I've got it to work fine with one exception:
Problem:
In order to have the 3rd component updated based on selection of component 1 and 2, the user has to actually physically select an item by moving the picker wheels. But there might be situation in which the user wants to use the default selection (in my case an NSString "0" in component 1 and 2) and does not touch those wheels. Therefore component 3 will not get updated.
Any idea how to address this problem? Is there a way I can update the UIPickerView before the user selects the item in the third component, in order to tricker the default selection in component 1 and 2 as an actual selection without having the user actually move those wheels?
Or lets formulate the question that way: can I have the default row of a UIPickerView automatically selected without selecting the particular row with the finger?
Additional explanation of what I try to accomplish:
when the user clicks in a textfield, an UIPickerView with three columns will open up. In the first wheel the user selects a number between 0 and 16, in the second he will select a fraction, and in the third a measuring unit. For example:
1 1/2 cups,
1 ounce,
1/2 cup,
etc.
Based on the number and fraction selection the unit names in the third picker wheel will either be shown in singular or plural spelling (1 cup vs. 2 cups). The whole thing works perfectly as long the first two wheels are touched and a physically selection is done. Problem: lets take for example that the user wants to select "1 ounce". He will move the first wheel to 1, will leave the second alone (as it is already positioned on the 0), and then choose "ounces". But with my code the third wheel will not update to singular spelling. ONLY, if I actually move the second wheel and go back to 0. Then the 3rd wheel instantly changes.
Thanks!!
Here is the code I have so far:
_countPickerData = #[#"0", #"1", #"2", #"3", #"4", #"5", #"6", #"7", #"8", #"9", #"10", #"11", #"12", #"13", #"14", #"15", #"16"];
_fractionPickerData = #[#"0", #"1/2", #"1/4", #"1/8", #"3/4", #"3/8", #"5/8", #"7/8"];
_singularUnitPickerData = #[#"NONE", #"ounce", #"cup", #"pound"];
_pluralUnitPickerData = #[#"NONE", #"ounces", #"cups", #"pounds"];
// The number of columns of data
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 3;
}
// The number of rows of data
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
switch (component) {
case 0: return [_countPickerData count];
case 1: return [_fractionPickerData count];
case 2:
if (([countHelper isEqualToString:#"1"] && [countFractionHelper isEqualToString:#"0"]) || ([countHelper isEqualToString:#"0"] && ([countFractionHelper isEqualToString:#"1/2" ] || [countFractionHelper isEqualToString:#"1/4"] || [countFractionHelper isEqualToString:#"1/8"])))
{
return [_singularUnitPickerData count];
}
else
{
return [_pluralUnitPickerData count];
}
}
return 0;
}
// The data to return for the row and component (column) that's being passed in
- (NSString*)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
switch (component)
{
case 0: return [_countPickerData objectAtIndex:row];
case 1: return [_fractionPickerData objectAtIndex:row];
case 2:
if (([countHelper isEqualToString:#"1"] && [countFractionHelper isEqualToString:#"0"]) || ([countHelper isEqualToString:#"0"] && ([countFractionHelper isEqualToString:#"1/2" ] || [countFractionHelper isEqualToString:#"1/4"] || [countFractionHelper isEqualToString:#"1/8"])))
{
pickedUnitHelper = [_singularUnitPickerData objectAtIndex:row];
return pickedUnitHelper;
}
else
{
pickedUnitHelper = [_pluralUnitPickerData objectAtIndex:row];
return pickedUnitHelper;
}
default:break;
}return nil;
}
// Preparing pickerView selection for textFieldIngredientUnit
NSInteger selectedRowUnit;
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
switch (component)
{
case 0:
countHelper = [_countPickerData objectAtIndex:row];
return;
case 1:
countFractionHelper = [_fractionPickerData objectAtIndex:row];
return;
case 2:
if (([countHelper isEqualToString:#"1"] && [countFractionHelper isEqualToString:#"0"]) || ([countHelper isEqualToString:#"0"] && ([countFractionHelper isEqualToString:#"1/2" ] || [countFractionHelper isEqualToString:#"1/4"] || [countFractionHelper isEqualToString:#"1/8"])))
{
pickedUnitHelper = [_singularUnitPickerData objectAtIndex:row];
return;
}
else
{
pickedUnitHelper = [_pluralUnitPickerData objectAtIndex:row];
return;
}
default:break;
}
}
Yes, you can automatically select a row of the picker without touching it with your finger. For example:
[self.pickerView selectRow:0 inComponent:2 animated:YES];
Call this before the view appears on the screen. Probably in viewWillAppear:
Update:
Markus and I checked this out over chat, and I came up with a solution. Basically, any time:
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
is called, if it is component 0 or 1, we need to reload component 2. We do this (within the didSelectRow method) by saying:
[pickerView reloadComponent:2];
There were a couple other minor things in his code that weren't working correctly, but for the purpose of this stack overflow question, this is all that needs to be explained.
I recently had the very same problem (uipickerview not selecting the first value). I tried to do..
[_myPicker selectRow:0 in Component:0 animated:YES];
But that still did not select the value, it turned the picker to whatever I chose in selectRow and Component, but did not send that value anywhere. So what I did was to create an extra row in my picker's array. My picker values where 1, 2, 3, and 4. So when I added the array it looked like this..
_myPickerArray = [[NSArray alloc] initWithObjects:#" ", #"1", #"2", #"3", #"4", nil];
Now the user must turn the wheel to select something. In your code you are checking if the input is a string of 1. If they select the space, then that condition will not be met. See if that works.

NSTableView crashs when changing data with selected row

In my cocoa app i have 2 NSTableViews. The AppDelegate is the datasource for both of them. I want to change the data of the second TableView depending on the selected row of the first TableView. For that i use an NSDictionary. I add an array for every entry in the data array of Table1.
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView{
if([table1 selectedRow] != -1 && [[aTableView identifier] isEqualToString:#"table2"]){
return [[dict objectForKey:[arrForTable1 objectAtIndex:[table1 selectedRow]]] count];
}
if([[aTableView identifier] isEqualToString:#"table1"]) return [arrForTable1 count];
return 0;
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex{
if([table1 selectedRow] != -1 && [[aTableView identifier] isEqualToString:#"table2"]){
return [[dict objectForKey:[arrForTable1 objectAtIndex:[table1 selectedRow]]] objectAtIndex:rowIndex];
}
if([[aTableView identifier] isEqualToString:#"table1"]){
return [arrForTable1 objectAtIndex:rowIndex];
}
return nil;
}
This code crashes when in table2 a row is selected and after that a switch in the table1 to a row with an empty array.
How can i fix it? I tried to [table2 deselectAll:self] in the - selectionShouldChangeInTableView: method but it doesn't work. It works to trigger the deselectAll: method with an button and that was ok.
Sounds like you might need to call [table2 reloadData] when your selection in table1 changes.

Create a sectioned UITableView using NSFetchedResultsController

I am trying to create a UITableView with two different sections. I know I can group them on an attribute of my managed object. For instance if I'd like to group them per name I'd do:
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_context
sectionNameKeyPath:#"name"
cacheName:#"uploadProperties"];
And I return the number of secionts like:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[_fetchedResultsController sections] count];
}
The problem though is that I do not want to group it per attribute (such as the name). I want to group them for specific values, namely one part that has pud_id = 0 and another section that has pud_id > 0.
How can I achieve this? Is it possible to use kind of a where clause? Or can I create a property on my managed object and use this in the sectionNameKeyPath such as:
- (BOOL) hasPudZero {
if (self.pud_id == 0)
return YES;
return NO;
}
??
Thanks for your input!
Perhaps you could create a transient attribute of type NSString that returns one of two strings?
- (NSString *)sectionIdentifier
{
[self willAccessValueForKey:#"sectionIdentifier"];
NSString *tmp = [self primitiveValueForKey:#"sectionIdentifier"];
[self didAccessValueForKey:#"sectionIdentifier"];
if (!tmp) {
if (self.pud_id == 0) {
tmp = #"SectionOne";
} else {
tmp = #"SectionTwo";
}
[self setPrimitiveValue:tmp forKey:#"sectionIdentifier"];
}
return tmp;
}
and then use sectionNameKeyPath:#"sectionIdentifier" when creating your NSFetchedResultsController. Make sure you set the primitive value of sectionIdentifier back to nil if the value of put_id changes.

Enter fractions in a uitextfield from 3 UIPickers?

So what I'm intending to do is instead of typing in decimals, I want to be able to enter whole numbers and fractions in a textfield. Or select them from a UIPicker.
Example: Feet, Inches, fractions.(2 - 4 1/2).
What's the best way to accomplish this? Please be detailed in your answer. Thank you in advance!
Update: Ok maybe a little more detail is needed.
I'm going to use the picker, and want to display 5 rows like this:
<Ft> <In>
0 0 S 0 0 S 1/16
1 1 P 1 1 P 1/8
2 2 A 2 2 A 3/16
3 3 C 3 3 C 1/4
4 4 E 4 4 E 5/16
ect...all the way to 9, and to 15/16 for the fractions. (Note the space between Ft and Inches and title for row).
Ok, So I was able to get 3 pickers to display on screen just how I wanted, my biggest issue is getting it to display in the textfield properly. So just how the example is above, ft has it's own picker, inches has it's own, and fractions has it's own, How would you get all three to display as "00ft 00 0/0in" in one text field?
UPDATE: the code below is correct, make sure you create an IBOutlet for your UIView that contains the pickers, and hook it up in IB.
.m file
feetArray = [[NSMutableArray alloc] init];
for(int feet = 0; feet <= 9; feet ++){
NSString *feetString = [NSString stringWithFormat:#"%d%", feet];
[feetArray addObject:feetString]; // Add the string.
}
inchArray = [[NSMutableArray alloc] init];
for(int inch = 0; inch <= 12; inch ++){
NSString *inchString = [NSString stringWithFormat:#"%d%", inch];
[inchArray addObject:inchString]; // Add the string.
}
fractionArray = [[NSMutableArray alloc] init];
for(int frac = 0; frac <= 15; frac ++){
NSString *fractionString = [NSString stringWithFormat:#"%d/16", frac];
[fractionArray addObject:fractionString]; // Add the string.
}
}
//How many rows in each Picker.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
if (pickerView.tag == 1) {
return 2;
}
if (pickerView.tag == 2) {
return 2;
}
return 1;
}
//Selects array for each picker.
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (pickerView.tag == 1) {
return [feetArray count];
}
if (pickerView.tag == 2) {
return [inchArray count];
}
return [fractionArray count];
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
if (pickerView.tag == 1) {
return [feetArray objectAtIndex:row];
}
if (pickerView.tag == 2) {
return [inchArray objectAtIndex:row];
}
return [fractionArray objectAtIndex:row];
}
//Formats the textfield based on the pickers.
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
NSString *result = [feetArray objectAtIndex:[feetPicker selectedRowInComponent:0]];
result = [result stringByAppendingFormat:#"%#ft", [feetArray objectAtIndex:[feetPicker selectedRowInComponent:1]]];
result = [result stringByAppendingFormat:#" %#", [inchArray objectAtIndex:[inchesPicker selectedRowInComponent:0]]];
result = [result stringByAppendingFormat:#"%#", [inchArray objectAtIndex:[inchesPicker selectedRowInComponent:1]]];
result = [result stringByAppendingFormat:#" %#in", [fractionArray objectAtIndex:[fractionPicker selectedRowInComponent:0]]];
RiseTextField.text = result;
}
By setting up UIPickerView object as the inputView, you won't need to worry about text input. Tapping on the text field would bring up the picker and make the input constrained just as you want it. You can either subclass or set them directly in your controller. I set it up as a subclass here. Its a rough implementation of what you need as I understand it. You can take a look and see if it helps.
I would advice to use a picker, it's way easier then typing fractions and will prevent the input of incorrect fractions.
There are two ways to get the fractions into your UIPicker, use the pickerView:titleForRow:forComponent: method of your UIPicker delegate to give the fraction as a string. You can use UTF8 fraction characters like ΒΌ with this function.
If is not giving enough flexibility. You could make images of the fractions and put the images into the picker by using the pickerView:viewForRow:forComponent:reusingView: method of your delegate.
The documentation will give you lots of information! Good luck!
It sounds like you want something like the date picker with multiple wheels to choose each part of the distance. Unfortunately there isn't a way to do this. You have 2 options though. The simple option is to use several pickers next to each other. It wont look quite the same but it's simple. The more complicated option is to build one with UIScrollViews and style it to look the way you want it too.