Accessibility collectionView focus changes when reloaded, iOS - ios

I'm working on the accessibility of a calendar which is actually a collectionView. Whenever a cell is tapped, the collectionView will be reloaded by calling
[self.collectionView reloadData];
The problem is if the voiceOver is running, the focus will move to another place after the cell tapped because that cell is reused on somewhere else.
Is there anyway to keep the focus where it was after the reloadData? Thanks!

Just find a workaround for this. The focus is changed because the focused cell is reused somewhere else when doing [colleciontView reloadData].
So if we reload the collectionViewCells one by one, that focused cell will not be used anywhere else. I call this method to reload the collectionView when VoiceOver is running.
- (void)reloadCalendarCollectionView {
NSInteger items = [self.calendarItems count];
for (NSInteger i = 0; i < items; i++) {
[self.collectionView reloadItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:i inSection:1] ]];
}
}

You could try doing self.collectionView.accessibilityElementsHidden = YES before reloading data. Then, when it completes, you will have to do the inverse & then post a notification for the cell you're looking for.

We have a collection view and this collection view reloading data in every second. When user taps cell, focus changing after every reload, so collectionview cell selects wrong cell at indexPath.
Create a protocol for delegation in cell:
protocol AccessibilityCellProtocol{
func accessibilityFocused(cell:UICollectionViewCell)
}
Override accessibilityElementDidBecomeFocused in your cell:
override class func accessibilityElementDidBecomeFocused(){
self.delegate.accessibilityFocused(cell:self)
}
In view controller create an selectedIndexPath variable. Assign it in delegation method.
func accesibilityFocused(cell:UITableViewCell){
selectedIndex = collectionView.indexPath(for: cell)
}
And in your didSelectItemAtIndexPath method:
if UIAccessibility.isVoiceOverRunning{
cellTappedWith(indexPath:selectedIndex)
return
}
cellTappedWithIndexPath(indexPath:indexPath)

Related

Check if cell is selected and format it accordingly iOS UITableView

I am trying to implement a functionality where I want to set the first row of a UITableView as selected. The UITableView is a subview of my UIViewController . I am able to select the 1st row through my UIViewController. Like so :-
var indexPath = NSIndexPath.FromRowSection(0, 0);
newTableChoice.Source = new RatingScaleSource(this,data.TypeAsString ,data.Title, choiceList);
newTableChoice.SelectRow(indexPath, false, UITableViewScrollPosition.Top);
Now I want to implement it further in my DataSource, where if the first row is selected then I want to add an accessory to it so that the user knows that particular row is selected by default.
I tried implementing this in my RatingScaleSource class which is my DataSource :-
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
cell = tableView.DequeueReusableCell("ChoiceTableCell") as ChoiceTableCell;
if (tableView.CellAt(indexPath).Selected)
{
cell.Accessory = UITableViewCellAccessory.Checkmark;
}
}
But this crashes the app as the Cell is not yet created. I want to know which Function can detect if the row is already selected and where I can add the accessory. Any help is appreciated.
SelectRow definition here
We can see the following sentences in Remarks
Calling this method does not trigger UITableViewSource.WillSelectRow(UITableView,NSIndexPath) nor will it send UITableView.SelectionDidChangeNotification notifications.
No method will be triggered with this method.
tableView.CellAt(indexPath) at GetCell is incorrect, because the cell hasn't returned at that time(means it is not yet created).
I tried the following code but no luck, I think the tableview selects the row after finishing the render delegate.
cell = tableView.DequeueReusableCell("ChoiceTableCell") as ChoiceTableCell;
if (cell.Selected)
{
cell.Accessory = UITableViewCellAccessory.Checkmark;
}
Solution:
So since you have known the index which should be selected, you can set the Accessory with same condition in GetCell
if (indexPath.IsEqual(NSIndexPath.FromRowSection(0, 0)))
{
cell.Accessory = UITableViewCellAccessory.Checkmark;
}
else
{
cell.Accessory = UITableViewCellAccessory.None;
}

Correct way to setting a tag to all cells in TableView

I'm using a button inside a tableView in which I get the indexPath.row when is pressed. But it only works fine when the cells can be displayed in the screen without scroll.
Once the tableView can be scrolleable and I scrolls throught the tableview, the indexPath.row returned is a wrong value, I noticed that initially setting 20 objects, for example Check is just printed 9 times no 20.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
lBtnWithAction = [[UIButton alloc] initWithFrame:CGRectMake(liLight1Xcord + 23, 10, liLight1Width + 5, liLight1Height + 25)];
lBtnWithAction.tag = ROW_BUTTON_ACTION;
lBtnWithAction.titleLabel.font = luiFontCheckmark;
lBtnWithAction.tintColor = [UIColor blackColor];
lBtnWithAction.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[cell.contentView addSubview:lBtnWithAction];
}
else
{
lBtnWithAction = (UIButton *)[cell.contentView viewWithTag:ROW_BUTTON_ACTION];
}
//Set the tag
lBtnWithAction.tag = indexPath.row;
//Add the click event to the button inside a row
[lBtnWithAction addTarget:self action:#selector(rowButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
//This is printed just 9 times (the the number of cells that are initially displayed in the screen with no scroll), when scrolling the other ones are printed
NSLog(#"Check: %li", (long)indexPath.row);
return cell;
}
To do something with the clicked index:
-(void)rowButtonClicked:(UIButton*)sender
{
NSLog(#"Pressed: %li", (long)sender.tag);
}
Constants.h
#define ROW_BUTTON_ACTION 9
What is the correct way to get the indexPath.row inside rowButtonClicked or setting a tag when I have a lot of of cells in my tableView?
My solution to this kind of problem is not to use a tag in this way at all. It's a complete misuse of tags (in my opinion), and is likely to cause trouble down the road (as you've discovered), because cells are reused.
Typically, the problem being solved is this: A piece of interface in a cell is interacted with by the user (e.g. a button is tapped), and now we want to know what row that cell currently corresponds to so that we can respond with respect to the corresponding data model.
The way I solve this in my apps is, when the button is tapped or whatever and I receive a control event or delegate event from it, to walk up the view hierarchy from that piece of the interface (the button or whatever) until I come to the cell, and then call the table view's indexPath(for:), which takes a cell and returns the corresponding index path. The control event or delegate event always includes the interface object as a parameter, so it is easy to get from that to the cell and from there to the row.
Thus, for example:
UIView* v = // sender, the interface object
do {
v = v.superview;
} while (![v isKindOfClass: [UITableViewCell class]]);
UITableViewCell* cell = (UITableViewCell*)v;
NSIndexPath* ip = [self.tableView indexPathForCell:cell];
// and now we know the row (ip.row)
[NOTE A possible alternative would be to use a custom cell subclass in which you have a special property where you store the row in cellForRowAt. But this seems to me completely unnecessary, seeing as indexPath(for:) gives you exactly that same information! On the other hand, there is no indexPath(for:) for a header/footer, so in that case I do use a custom subclass that stores the section number, as in this example (see the implementation of viewForHeaderInSection).]
I agree with #matt that this is not a good use of tags, but disagree with him slightly about the solution. Instead of walking up the button's superviews until you find a cell, I prefer to get the button's origin, convert it to table view coordinates, and then ask the table view for the indexPath of the cell that contains those coordinates.
I wish Apple would add a function indexPathForView(_:) to UITableView. It's a common need, and easy to implement. To that end, here is a simple extension to UITableView that lets you ask a table view for the indexPath of any view that lies inside one of the tableView's cells.
Below is the key code for the extension, in both Objective-C and Swift. There is a working project on GitHub called TableViewExtension-Obj-C that illustrates the uses of the table view extension below.
EDIT
In Objective-C:
Header file UITableView_indexPathForView.h:
#import <UIKit/UIKit.h>
#interface UIView (indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view;
#end
UITableView_indexPathForView.m file:
#import "UITableView_indexPathForView.h"
#implementation UITableView (UITableView_indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view {
CGPoint origin = view.bounds.origin;
CGPoint viewOrigin = [self convertPoint: origin fromView: view];
return [self indexPathForRowAtPoint: viewOrigin];
}
And the IBAction on the button:
- (void) buttonTapped: (UIButton *) sender {
NSIndexPath *indexPath = [self.tableView indexPathForView: sender];
NSLog(#"Button tapped at indexpPath [%ld-%ld]",
(long)indexPath.section,
(long)indexPath.row);
}
In Swift:
import UIKit
public extension UITableView {
func indexPathForView(_ view: UIView) -> IndexPath? {
let origin = view.bounds.origin
let viewOrigin = self.convert(origin, from: view)
let indexPath = self.indexPathForRow(at: viewOrigin)
return indexPath
}
}
I added this as a file "UITableView+indexPathForView" to a test project to make sure I got everything correct. Then in the IBAction for a button that is inside a cell:
func buttonTapped(_ button: UIButton) {
let indexPath = self.tableView.indexPathForView(button)
print("Button tapped at indexPath \(indexPath)")
}
I made the extension work on any UIView, not just buttons, so that it's more general-purpose.
The nice thing about this extension is that you can drop it into any project and it adds the new indexPathForView(_:) function to all your table views without having do change your other code at all.
You are running into the issue of cell-reuse.
When you create a button for the view you set a tag to it, but then you override this tag to set the row number to it.
When the cell get's reused, because the row number is longer ROW_BUTTON_ACTION, you don't reset the tag to the correct row number and things go wrong.
Using a tag to get information out of a view is almost always a bad idea and is quite brittle, as you can see here.
As Matt has already said, walking the hierarchy is a better idea.
Also, your method doesn't need to be written in this way. If you create your own custom cell, then the code you use to create and add buttons and tags isn't needed, you can do it in a xib, a storyboard, or even in code in the class. Furthermore, if you use the dequeue method that takes the index path, you will always get either a recycled cell, or a newly created cell, so there is no need to check that the cell returned is not nil.

How to persist edit mode of table view cell in table view even when table view cell is updated?

I am currently developing an iOS application in Apple's Swift.
I have a table view with table cells in it each of which displays the current time of a timer (it is not a real timer it is actually just a timestamp).
The application itself has a timer which updates the visible cell of the table view with the current states of the cell timers.
The application provides the possiblity to slide a cell which lets appear a delete button.
The problem I am faced with is that the delete button immediately disappears due to the fact that the cell is updated by the application's timer.
Here is the code where the table view cells are updated:
// Update visible rows in table
func updateTable() {
// Get all visible cells
let cells = timerTable.visibleCells as! Array<TimerTableViewCell>
for cell in cells {
let indexPath = timerTable.indexPathForCell(cell)
cell.timeLabel.text = String(timerArray[indexPath!.row].getRemainingTimeAsString())
timerTable.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.None)
}
}
I would be glad if anyone would have a solution/workaround for my problem.
Thank you in advance.
I think the simpler solution is to remove your reloadRowsAtIndexPaths call, and just update the timeLabel. You could try this:
// Update visible rows in table
func updateTable() {
// Get all visible cells
let cells = timerTable.visibleCells as! Array<TimerTableViewCell>
for cell in cells {
let indexPath = timerTable.indexPathForCell(cell)
cell.timeLabel.text = String(timerArray[indexPath!.row].getRemainingTimeAsString())
}
}
Because reloadRowsAtIndexPaths says to the UITableView: Hey, just take the rows at theses indexPaths and rebuild them from scratch. Ignore its previous state.

Get `UITableViewCell` indexPath using `UIScrollView`

I have a UITableViewCell which contains a UICollectionView. The UITableViewCell also contains a UIPageControl. I want to change the dots as the UICollectionView is swiped. I am using
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
to get the current visible UICollectionViewCell. But my problem is that since the UICollectionView lies in UITableViewCell to fetch the reference to the collectionView I require the indexPAth of the current table view in which collection cell is being swiped. How can I do this?
I want to do this:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// Change the dots
CustomTableCell *tableCell = [self.currentTable cellForRowAtIndexPath:_tablecellIndex];
NSInteger currentIndex = tableCell.currentCollectionView.contentOffset.x / tableCell.currentCollectionView.frame.size.width;
tableCell.currentPageControl.currentPage = currentIndex;
}
But how do I get the _tablecellIndex?
I tried doing :
NSArray *indexes = [self.currentTable visibleCells];
_tablecellIndex = indexes[0];
But this is not always true as sometimes the table cells are displayed half and user is swiping second cell.
You need to ask the tableview itself, what indexpath a given cell has. You do that with this command :
[self.formTableView indexPathForCell:myCell];
The other problem in your case is that you are within the collection view on the cell, and not within the tableview itself. So theres a few ways to do that - one nice way is to set up a delegate on the cell that can access the tableview. Thats a bit involved, so heres an even simpler way (self in this case is the cell object):
UITableView *parentTableView = (UITableView*)self.superview;
NSIndexPath *cellIndexPath = [parentTableView indexPathForCell:self];
That should do the job.

Clear cached cells from UITableView

To establish some context :
I have a UITableView with n cells.
When the user selects a cell they the cell expands and the user experience then continues within that cell.
There are some animations that take place within the cell.
- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath
{
//expand cell/increase cell height animated
//add a button to bottom of cell with target(didpressbutton:)
}
- (void)didpressbutton:(id)sender
{
//perform complex animating rearranging UI elements
}
At the end of the flow the user needs to comeback to the original tableview.
But the cell with the misaligned UI elements are still showing as it is dequeuing the old cells.
Is there any way for me to clear the cached cell or reinitialise them?
But the cell with the misaligned UI elements are still showing as it is dequeuing the old cells.
Is there any way for me to clear the cached cell or reinitialise them?
You are obviously misusing -prepareForReuse:. Implement this method to reset any state the cells have.

Resources