Creating custom VoiceOver rotor to navigate UICollectionView vertically - ios

I have a large square grid (225 cells) constructed as a 2d UICollectionView and I would like blind users to be able to navigate the grid easily. Currently they would need to scroll horizontally through every square to go down. Ideally, I'd like to have a custom VoiceOver rotor that allows for left, right, up and down swiping to navigate. Is this possible?
If not, I would settle for having a custom rotor which moves up and down via left/right swipe. I've attempted to create one by getting the current indexPath and using the scrollToItem method to change it. The rotor can be selected but has no effect currently so I've done it wrong so far.
private func setupVerticalRotor() -> UIAccessibilityCustomRotor {
let vertRotorOption = UIAccessibilityCustomRotor.init(name: "Move vertically") { (predicate) -> UIAccessibilityCustomRotorItemResult? in
let forward = predicate.searchDirection == UIAccessibilityCustomRotor.Direction.next
let visibleCells = self.collectionView.visibleCells
if let firstCell = visibleCells.first {
if let indexPath = self.collectionView.indexPath(for: firstCell as UICollectionViewCell) {
if forward {
let newIndexPath = IndexPath(row: indexPath.row, section: indexPath.section + 1)
self.collectionView.scrollToItem(at: newIndexPath, at: UICollectionView.ScrollPosition.centeredVertically, animated: false)
}else {
let newIndexPath = IndexPath(row: indexPath.row, section: indexPath.section - 1)
self.collectionView.scrollToItem(at: newIndexPath, at: UICollectionView.ScrollPosition.centeredVertically, animated: false)
}
}
}
return UIAccessibilityCustomRotorItemResult.init(targetElement: self.collectionView , targetRange: nil)
}
return vertRotorOption
}

Ideally, I'd like to have a custom VoiceOver rotor that allows for left, right, up and down swiping to navigate. Is this possible?
The native rotor has already the item to navigate vertically.
For your code, I suggest to take a look at this custom rotor example + check your collection view cells update with breakpoints in Xcode.

Related

CollectionView scroll with button tap scrollToItem not working

I currently have a login and signup buttons labeled as 1 and 2. And underneath that, I have a collectionView with two cells (one for the login UI and one for register UI). When I click on the signup button(Label 2), I want to move the collectionView to the next cell.
I tried using the scrollToItem, but it does not do anything. Anyone know why?
#objc private func didTapRegisterButton() {
let i = IndexPath(item: 1, section: 0)
self.onboardingCollectionView.scrollToItem(at: i, at: .right, animated: true)
}
Figured it out. My collectionView had isPagingEnabled property as true. The scrollToItem doesn't work if that is set to true.
If you want to scroll between two cells on button click then first you need to uncheck(disable) Scrolling Enabled property and check(enable) Paging Enabled property of collectionView. (With this approach user can't scroll manually with swipe)
On button first action,
if self.currentIndexPath == 0 {
return
} else {
self.currentIndexPath += 1
self.yourCollectionView.scrollToItem(at: IndexPath(row: self.currentVisibleIndexPath, section: 0), at: .centeredHorizontally, animated: true)
}
On button second action,
if self.currentIndexPath == 1 {
self.currentIndexPath -= 1
self.yourCollectionView.scrollToItem(at: IndexPath(row: self.currentVisibleIndexPath, section: 0), at: .centeredHorizontally, animated: true)
} else {
return
}
And declare global variable
var currentIndexPath: Int = 0

UiCollectionView jump on next cell when previous cell removed

I have custom layout with fullscreen cells. When removing cell from the left (it's not visible at the time), UICollectionView jumps to the next cell.
It's like current cell was at index 4 and when cell on the left removed the next cell has index 4 now and immediately scroll to the next cell.
Describing in 3 steps (A is cell that need to be fullscreen, x will be removed, o other cells, large letter is fullscreen):
ooooAoo
oooxAoo
oooaOo
But must keep this oooAoo
Here is my solution if it can help to anybody, did not found how to achieve desired offset natively, so just scrolling contentOffset to the desired position right after reloadData():
var currentCell: MyCollectionViewCell? {
return (visibleCells.sorted { $0.frame.width > $1.frame.width }.first) as? MyCollectionViewCell
}
//-----------------------------------------
//some model manipulating code, removing desired items here...
let currentList = currentCell?.parentList
reloadData()
if let list = currentList, let index = self.lists.firstIndex(of: list) {
self.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: false)
}
currentCell is computed property, returns optional middle cell.
Detect which cell is the largest, because of custom flowlayout logic.
parentList is the model item, I can compare cell by it to make life
easier. I check which list was attached to the cell before
reloadData().

Swift segmented control and UITableView

I have segmented control as a header of UITableView with cell names. Tableview has different cells with different sizes. When I tap on segment, I do a scroll to specific row in a tableview.
segment.valueSelected = { index in
self.contentTableView.scrollToRow(at: IndexPath(row: index, section: 1), at: .top, animated: true)
}
But how can I change selected index of segmented control when I scroll my tableview? I mean I know how to change index, but the problem is how can I calculate specific offset since all cells has different sizes?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
segment.setSelectIndex(index : ???)
}
Solved by
let visiblerows = contentTableView.indexPathsForVisibleRows
if let lastIndex = visiblerows?.last {
segment.setSelectIndex(index: lastIndex.row, animated: true)
}

Making a textView in a collectionView cell the firstResponder while navigating cells

I have a collectionView and allow a user to dynamically add cells to the collectionView. The view controller only shows one cell at a time and I want the first textView (which is part of the cell) to become the firstResponder (and keep the keyboard visible at all times), which works fine when loading the view controller (as well as in one of the cases below).
I have created a method to detect the current cell, which I call every time in any of these cases: (1) user scrolls from one cell to another (method placed in scrollViewWillEndDragging), (2) user taps UIButtons to navigate from one cell to another, (3) user taps UIButton to create and append a new cell at the end of the array (which is used by the collectionView).
This is the method:
func setNewFirstResponder() {
let currentIndex = IndexPath(item: currentCardIndex, section: 0)
if let newCell = collectionView.cellForItem(at: currentIndex) as? AddCardCell {
newCell.questionTextView.becomeFirstResponder()
}
}
Now my problem is that this only works in case (1). Apparently I have no cell of type AddCardCell in cases (2) and (3). When I print the currentCardIndex, I get the same result, in all of the cases, which is very confusing.
Any hints why I wouldn't be able to get the cell yet in cases 2 and 3, but I am in case 1?
As a reference here are some of the methods that I am using:
//Update index and labels based on user swiping through card cells
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
//Update index based on targetContentOffset
let x = targetContentOffset.pointee.x
currentCardIndex = Int(x / view.frame.width)
setNewFirstResponder()
}
And the other method, from which it doesn't work (case 3):
//Method to add a new cell at end of collectionView
#objc func handleAddCell() {
//Inserting a new index path into tableView
let newIndexPath = IndexPath(item: autoSaveCards.count - 1, section: 0)
collectionView.insertItems(at: [newIndexPath])
collectionView.scrollToItem(at: newIndexPath, at: .left, animated: true)
currentCardIndex = autoSaveCards.count - 1
setNewFirstResponder()
}
Regarding case 2,3 i think that the cell is not yet loaded so if let fails , you can try to dispatch that after some time like this , also general note if the cell is not visible then it's nil
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
setNewFirstResponder()
}
Also it can work if you set animated:false to scrollToItem

Weird bug, collectionView view scrolling to second last message, when trying to scroll to bottom

using this code to scroll to bottom of collection view. however it scrolls to the second last one only.
private func scrollToBottom() {
let lastSectionIndex = (ChatCollectionView?.numberOfSections())! - 1
let lastItemIndex = (ChatCollectionView?.numberOfItemsInSection(lastSectionIndex))!-1
let indexPath = NSIndexPath(forItem: lastItemIndex, inSection: lastSectionIndex)
ChatCollectionView!.scrollToItemAtIndexPath(indexPath, atScrollPosition: UICollectionViewScrollPosition.Bottom, animated: false)
}
is anyone familiar with a bug like that
Your collection view's frame was under the visible area. You should set its bottom inside visible area.

Resources