UITableVIew with Single Selection iOS - ios

I am totally new to iOS development and have been requested by my company to jump into some code written by another amateur that left us and fix it. The main view of this app is just a table view and we would like the user to be able to select one cell from the table, which would highlight it and set some other buttons on the page to do something to that particular cell.
Making the other buttons on the page works just fine, as long as I know what cell is selected, so I am not too worried about that. But the code this other guy wrote to select one cell at a time is atrocious, and the code I tried to replace his with was better, but still very buggy.
My code is this written below (sort of adopted from the old stuff that I don't completely understand). The issue with it is that the background turns black when the cell is clicked twice in a row or if you change views and come back to this table view.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
for (int i = 0; i < [tableView numberOfRowsInSection:0]; i++) {
[tableView deselectRowAtIndexPath:currentSelection animated:true];
}
if(currentSelection.item == indexPath.item){
[tableView deselectRowAtIndexPath:currentSelection animated:true];
}
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//highlight bg to light grey
cell.backgroundColor =[UIColor colorWithRed:200/255 green:200/255 blue:200/255 alpha:1.0f];
currentSelection = indexPath;
//code for other button functions
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//set bg back to white
cell.backgroundColor =[UIColor colorWithRed:255/255 green:255/255 blue:255/255 alpha:1.0f];
noSelectedRows = true;
}
If that is too terrible too look at and try to fix, I was looking at a tutorial provided by Apple that I wanted to understand a little better anyway. The following code was found here: https://developer.apple.com/library/ios/documentation/userexperience/conceptual/tableview_iphone/ManageSelections/ManageSelections.html
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:NO];
NSInteger catIndex = [taskCategories indexOfObject:self.currentCategory];
if (catIndex == indexPath.row) {
return;
}
NSIndexPath *oldIndexPath = [NSIndexPath indexPathForRow:catIndex inSection:0];
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
if (newCell.accessoryType == UITableViewCellAccessoryNone) {
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
self.currentCategory = [taskCategories objectAtIndex:indexPath.row];
}
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:oldIndexPath];
if (oldCell.accessoryType == UITableViewCellAccessoryCheckmark) {
oldCell.accessoryType = UITableViewCellAccessoryNone;
}
}
The only things I need to know about that last code is where variables like self.currentCategory and taskCategories are coming from. As of now, there don't seem to be any similar variables in the code I am working with, so I would like to know what I have to point to or create.
This is also my first post on StackOverflow, so if I left out something important, please let me know.

The UITableView is a huge control, and it can be implemented in several ways. By your description, I think you using it with Static Grouped cells, in order to compose a form, right?
Actually selection display is handled automatically by the UITableView, so the code for didDeselecteRowAtIndex path which manually change cells background may be one source if the buggy behavior. Notice in the Apple Sample that they are using a similar logic to change the accessory view when cells are tapped, which need to be set manually.
If you're using the Storyboard and Template cells, you can set the "Selection" property on the cell to change the color when it is selected. Then you can remove the logic for changing cells backgrounds. I think it could be a good starting point....

You can right click on the variable and select Jump To Definition and it will show you where its declared.
As far as the rest of the code, what the other answers said is correct.
I suggest picking up a book on iOS development as well. Big Nerd Ranch's book is a great start.

One of your problems is, that
200/255 is 0
use 200/255.0f instead

Complementing Justin answers, here are some links. The iOS Apprentice Bundle is a great jumpstart material, that guides you through the creation of several Apps, pinpoint basics of iOS, such as the table view. I highly recommend!
http://www.raywenderlich.com/store/ios-apprentice
This series of videos also has a great introduction to Table Views, it will take less than a hour and will show the big picture on using UITableViews, this one is free:
https://www.youtube.com/watch?v=Rh-vfLXsTac

Related

iOS15, link between text is disappear every time when I click on another list’s text

I got a strange issue with iOS app as I’m upgrade some of the code for iOS 15
this issue is only happening to one text line “Setting” in the list
this is code I’m using
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FLTAccountTVCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLTAccountTVCellReuseId];
if (!cell) {
cell = [[FLTAccountTVCell alloc] initWithReuseIdentifier:kFLTAccountTVCellReuseId];
cell.textLabel.font = [FLTStylesheet regularFontWithSize:kFontSizeMedium];
}
cell.textLabel.text = [self titleForCellAtIndexPath:indexPath];
return cell;
}
FLTAccountTVCell is subclass of UITableViewCell
all seems fine apart from one “Settings” text is keep disappear when I click any list of it.
I have tested it on ios14 and lower and it seems all fine apart from ios15 and missing “Settings”
Please see Screenshot Gif below
We reused this code to the already allocated UI elements which are same.
In our case, the list of options are same ui, so to improve performance we are using this coding practice to not allocate more memory to the same UI elements.
But somehow this reusable practice didn’t work well on IOS 15 to drop this idea of reusable UI elements and allocate free memory for each UI list element and it worked
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FLTAccountTVCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLTAccountTVCellReuseId];
if (!cell) {
cell = [[FLTAccountTVCell alloc] initWithReuseIdentifier:kFLTAccountTVCellReuseId];
cell.textLabel.font = [FLTStylesheet regularFontWithSize:kFontSizeMedium];
}
cell.textLabel.text = [self titleForCellAtIndexPath:indexPath];
return cell;
}
FLTAccountTVCell is subclass of UITableViewCell
It’s not good coding practice, but it’s fine. The list of UI elements fixed. When the list is dynamic like search results, this reusable coding practice is useful cos of many lists.
Account setting options fixed and not required reusable elements. I believe that it’s some kind of ios 15 issue and in future release it will fix. Like in ios 15.1 maybe?

iOS Expanded/Selected Custom UITableViewCells state changes when scrolling UITableView

I have a list of custom cells in my tableview and as I scroll everything appears to fine and the cells appear to be in the same order. I have some functionality with my cells - as i select a cell (and it dynamically expands)the background color changes and a few other custom cell properties. Once I do this and then I start scrolling, different cells that i haven't even touched before show up, selected(expanded) and the cell only updates when I select it manually to the correct data. I seem to see duplicates and all kinds of craziness.
I know there are LOTS of posts about this on here so far but for me, so far nothing has worked. Would like some input on what I could do to stop this ridiculous behavior.
I have posted some code to give you a better idea of what I am doing. I know that 'dequeueReusableCellWithIdentifier' is the culprit but don't know of an alternative.
As side notes, this is a tableview(its own xib) that is a child view of a large view (also a xib). I have also already registered the nib for the tableview.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:myIndentifier forIndexPath:indexPath];
if(self.currentSelectedIndex){
if(self.previousSelectedIndex){
//collapse cell
//configure cell in method(change background color etc)
}
else{
//expand cell
//configure cell in method(change background color etc)
}
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.currentSelectedIndex = indexPath;
[tableView beginUpdates];
if(self.currentSelectedIndex){
if(self.previousSelectedIndex && (self.previousSelectedIndex != self.currentSelectedIndex)){
[tableView reloadRowsAtIndexPaths:#[self.currentSelectedIndex, self.previousSelectedIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
}
else{
[tableView reloadRowsAtIndexPaths:#[self.currentSelectedIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
[tableView endUpdates];
if(self.previousSelectedIndex == self.currentSelectedIndex){
self.previousSelectedIndex = nil;
}
else{
self.previousSelectedIndex = self.currentSelectedIndex;
}
}
What can I do or how would i make sure that nothing else in the list 'seems' to be selected(expanded) or prevent from appearing to see duplicates as i scroll? I already keep track of my current and last selected index(as shown in the code) so I suppose that I could use that somehow?
Dequeued Cells are Reused
Know that cells are re-used, so that the appearance of a UITableViewCell is persistent for the entire life of that cell.
This means that if you do not explicitly reset all the presentation view of your cell, and just returning it unchanged in cellForRowAtIndexPath, what you are returning may be a currently selected (or deselected) cached cell.
A possible location to reset a table cell is prepareForReuse.
Design note:
How are you maintaining self.currentSelectedIndex and self.previousSelectedIndex? This is typically quite dangerous, since you are attempting to replicate the UITableView behavior. It is for example, unlikely to work with multiple selection. Setting an active selection is unlikely handle situations when the OS didDeselectRowAtIndexPath, as a result of a keyboard dismissal for example.

iOS Dynamic Form in UITableViewCells, Retrieve Values

I have been searching and reading all over but couldn't find any conclusive method to achieve what I want to and hope to find help here...
I have a UITableView which allows the user to add multiple Flavours and Percentages to a Recipe. I have implemented the method to add or delete rows of Flavours with a custom Cell / Nib and it works perfectly well.
The issue I'm facing now, is how to retrieve the values the user has provided per added row.
(Edit for Clarity: My problem is not the populating of data, but only the dynamic reading of all data so I can save it)
I do manage to get the values for the visible rows (I do understand how the Reuseidentifier and the Tableview works, per se that for memory management's sake, iOS only keeps track of the visible rows), but not the hidden ones.
I assume in theory that I have to create an Array of Cells outside of 'cellForRowAtIndexPath' which maintains all cells. But then I'm facing another conceptual problem that my custom Nib / cell doesn't show.... basically:
How can I then use / register a nib without using the dequeingidentifier
Or in General, how can I solve the overall problem to be able an read all user entered values per row
Here the code I'm using within my cellForRowAtIndexPath. As mentioned adding and remove cell works like a charm, that isn't the issue...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RecipeFlavourTableViewCell *cell;
int section = (int)indexPath.section;
if(section==0)
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
if(!cell){
[tableView registerNib:[UINib nibWithNibName:#"RecipeFlavourCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
I have seen some Libraries doing it (e.g. XLForm) but do not understand (also when checking their sources) how they iterate through the values and overcome this dequeuing problem...
Any help is highly appreciated
EDIT 2: here the code I'm using to iterate through the cells in order to save the data, but as said I can only iterate through the visible cells:
- (IBAction)saveRecipe:(id)sender {
NSInteger dynamicRows = [self.tableView numberOfRowsInSection:1];
for (int i=0; i<dynamicRows; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:1];
RecipeFlavourTableViewCell *cell = (RecipeFlavourTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
NSLog(cell.flavour.text);
}
}
After 2 days of searching I finally came up with a solid solution. In case someone bumps into the same problem of dynamic forms with a tableview, here the solution:
As we understand, what ever cell is created in cellForRowAtIndexPath, it only persists as long as it is displayed. As soon as you scroll and the cell disappears, it gets automatically thrown out of memory. This behaviour makes it impossible to iterate through all cells at a later stages.
The steps to follow in order to make it work are as follows:
Preparation
Create an NSObject with all properties you want to persist in one form cell (-> cellObject)
In the ViewDidLoad of your controller create a NSMutableArray which will contain the cellObjects (-cellsArray)
Add as many cellObjects to the cellsArray as you initially want to appear in the Tableview
In numberOfRowsInSection return the count of you cellsArray
In the cellForRowAtIndexPath build your cells as usual BUT add a Textfield Delegate (self) to every Textfield in a cell
TextField Delegate
Implement:
- (void)textFieldDidEndEditing:(UITextField *)textField
and update your cellsArray Objects every time a Textfield ends editing. Per se, get the cellObject for the row and edit the properties with the value of the TextField
Add Row
When ever you add a row, just add an empty cellObject to your cellsArray and use the beginUpdates / insertRowsAtIndexPaths / endUpdates on your tableView (NOT reloadData as the already typed in data would get lost). Also add the following at the very beginning of your addRow method, as you want to make sure that if the user adds a row while editing a textfield, the latter gets persisted as well:
[self.view.window endEditing: YES];
Remove Row
Same as Add Row just reverse, remove the cellObject from your cellsArray and use deleteRowsAtIndexPaths on your tableView
Save Data
Now comes the trick: since you ought to always persist your data when a field ends editing mode, there is one case you need to cover: What if the user pushes "Save" when the focus is set on one TextField? Well at the very beginning of your Save Action insert the following:
[self.view.window endEditing: YES];
This make sure the the textFieldEndEditing will be triggered one last time for the current textField and that its data will also be persisted.
Finally iterate through your cellsArray and do whatever you want with it (validate, save etc)...
That's it, hope this can help anyone else as I couldn't find any valuable explanation anywhere else...
Lets assume that you have an NSArray and that it contains data you want to show. Your code should look something like this:
// Add this property to the class and fill it in with data you want to show
#property NSArray flavourElements;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RecipeFlavourTableViewCell *cell;
int section = (int)indexPath.section;
if(section==0)
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
if(!cell){
[tableView registerNib:[UINib nibWithNibName:#"RecipeFlavourCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
// At this point your cell is ready for showing
// And you can change values in it by getting element from array that contains data
cell.flavorTextField = flavourElements[indexPath.row].flavour
cell.precentageTextField = flavourElements[indexPath.row].precentage
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
I wrote a code for accessing elements without knowing what you actually have, so you will need to adjust it a little bit to fit your app.

Creating response to answering quiz questions in a table

I am creating an app for medical students, and one view is a quiz mode that randomly generates quiz questions with a table view of the answer options. In the current stage, the user selects an answer from the table view, and a text field below the table view tells them whether they are right or not.
What I would like to do is, when the user clicks an answer, if the answer is correct, the selected answer in the table is highlighted green. If the answer is wrong, the selected answer would turn red, and the correct answer in the table would turn red. Anyone know if it's possible to have different selection background colors in the same table (and also whether the logic described is possible)?
This should do the trick, beware it is not tested:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.row != self.correctAnswerRow)
{
UITableViewCell *wrongCell = [tableView cellForRowAtIndexPath:indexPath];
[wrongCell setBackgroundColor:[UIColor redColor]];
}
UITableViewCell *correctCell = [tableView cellForRowAtIndexPath:
[NSIndexPath indexPathForRow:self.correctAnswerRow inSection:indexPath.section]];
[correctCell setBackgroundColor:[UIColor greenColor]];
}

Turning off Voice-over focus for a cell in table view and not for others

The first cell in my table view is a dummy cell and thus, when Voice-over mode is ON, I want to skip that cell so that the focus does not come to that control
and thus, none of its traits are spoken by the Voice over. I wrote the code pasted below to acheive the same, thinking that isAccessibilityElement alone is sufficient for this. But it seems that is not the case.
Even though this element i said to be non-accessible in the code, still its getting focus by right/left flicks in Voice-over mode. Any idea as to how this can be achieved?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if(indexPath.row == 0)
{
cell.isAccessibilityElement = 0;
}
}
use some cutom cell, and within that cell definition implement this:
- (NSInteger)accessibilityElementCount {
NSIndexPath *indexPath = [(UITableView *)self.superview indexPathForCell: self];
if(indexPath.row==0){
return 0;
}
else{
return 1;
}
}
The current way to do this seems to be setting accessibilityElementsHidden on the cell to true/YES (depending on whether using Swift or Obj-C.
Seems cleaner than other answers proposed and seems effective in my very brief testing.
It’s not ideal but could you only display the cell when VoiceOver is not activated? You can use the
UIAccessibilityIsVoiceOverRunning()
Function to see if VoiceOver is on when your app loads, and register the
#selector(voiceOverStatusChanged)
Notification so you can be informed if the user enables or disables voiceover. For more on this see the following blog post.
< http://useyourloaf.com/blog/2012/05/14/detecting-voiceover-status-changes.html>
Just override the cell's function accessibilityElementCount like this:
Swift 4.2:
override func accessibilityElementCount() -> Int {
return 0
}

Resources