I'm having trouble with swiping between accessibility elements in a UITableView with VoiceOver on. When in a UITableView, the next/previous element in the table is focused when the user swipes right/left. The element is usually a UITableViewCell.
But I have custom cells that use subviews of the cell as accessibility elements, as in:
cell.isAccessibilityElement = false
cell.accessibilityElements = [element1, element2];
This works perfectly fine within the cell, but there's a problem swiping between cells. If the custom cell is at the bottom of the table view, it fails to focus on the next cell in the table view. Normally, using the cells themselves as accessibility elements, the table view will auto-scroll and focus on the next cell. But after the custom cell, it doesn't auto-scroll, as if there are no more cells in the table view.
See screenshot for clarification. When Label 9 is focused, the user can't swipe right to the next cells, even though there are more cells. They can still scroll though using 3 fingers, and then they'll be able to swipe to the next cells as normal.
This problem happens when using the code below. It uses a storyboard that only has a default UITableView with a default prototype cell with some UILabels added to it. So all the accessibility stuff is done here in the code.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let customRow = 5
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
10
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == self.customRow {
return UITableView.automaticDimension
} else {
return 120
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
if indexPath.row == self.customRow {
cell = self.tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath)
cell.isAccessibilityElement = false
cell.accessibilityElements = cell.contentView.subviews
} else {
cell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
var content = cell.defaultContentConfiguration()
content.text = "row \(indexPath.row)"
cell.contentConfiguration = content
}
return cell
}
}
Related
I have a tableView and cells. The Cells are loaded from a xib and they have a label with automatic height. I need to narrow one cell if the user taps on it.
I have tried hiding - doesn't work
I have tried removeFromSuperView()- doesn't work
Is there any alternative?
When setting up your tableViewCell store the height anchor you want to update
var yourLabelHeightAnchor: NSLayoutConstraint?
private func setupLayout() {
yourLabelHeightAnchor = yourLabel.heightAnchor.constraint(equalToConstant: 50)
// Deactivate your height anchor as you want first the content to determine the height
yourLabelHeightAnchor?.isActive = false
}
When the user clicks on a cell, notify the tableView that the cell is going to change, and activate the height anchor of your cell.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourTableViewCellIdentifier") as? YourCell
self.tableView.beginUpdates()
cell?.yourLabelHeightAnchor?.isActive = true
self.tableView.endUpdates()
}
Did you try to do something like this:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var result: CGFloat
if (indexPath.row==0) {
result = 50 }
else {result = 130}
return result
}
This is just an example where height is changed for the first row. I tested on my application and it gave result like this.
I have default UITableViewController with 10 row
have UITableViewCell with segment control
If I change segmentController selected index in probably cell : 2, then scroll down this state of segmentController will be in other cells
cells1(selectedIndex: 1 )
cells2(selectedIndex: 1 )
cells3(selectedIndex: 1 )
cells4(selectedIndex: 1 )
cells5(selectedIndex: 1 )
... Select in cell 2 other index (2)
scroll down
and cell 5 have same selected index, but I am not touch them
cells1(selectedIndex: 1 )
cells2(selectedIndex: 2 )
cells3(selectedIndex: 1 )
cells4(selectedIndex: 1 )
cells5(selectedIndex: 2 )
selected 0 row
row 5 is too selected, why?
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.selectionStyle = .none
cell.textLabel?.text = String(indexPath.row)
cell.textLabel?.textColor = .white
return cell
}
UITableView and UICollectionView reuse cells. Which means it only keeps enough cells in memory to show what is on the screen. As you scroll cells off the screen they are fed back in to become the cells that are appearing. You need to properly configure the cells as they re-appear in your override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
So in your case you need to set the segmented control's selection in cellForRow every time.
This means you will need a way to keep track of what selection has been made on your segmented controls and call that up to set the segmented control correctly when the cell is being shown again.
Reset selection in prepareForReuse method of UITableViewCell by subclassing it.
override func prepareForReuse() {
}
collectionview in tableviewcell. when tableview reuse cell. collectionview in tableivewcell keep position. this is bug? and how to separate for it. example : cell 1 and cell 10 is collectionview(horizontal). when i scroll cell 1, cell 10 also scroll same postion. please help me.
Create a Dictionary to remember the offset for each cell like:
var contentOffset: [Int: CGFloat] = [:]
When table view cell is being removed from screen store the offset of your collection view like
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell = cell as? YouTableViewCell else {
return
}
contentOffset[indexPath.row] = cell.collectionView.contentOffset.x
}
Now when cell is about to display restore the content offset like:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell = cell as? YouTableViewCell else {
return
}
cell.collectionView.contentOffset.x = contentOffset[indexPath.row] ?? 0
}
It is not a bug, when table cell being reused, it also reuse the content which is the collection view which is scrolled. Keep Googling and check out the links:
ios 8 Swift - TableView with embedded CollectionView
https://medium.com/#gargankit476/multiple-collection-view-in-uitableview-ced7909a5af3
I'm trying to implement the Letgo or Instagram-like style feed with the categories/stories bar which I have done. but when I scroll through the posts it scrolls just within the cell and not as a whole view. So the stories bar just stays there until I reach the end of the collection view then the stories bar scrolls but goes back down because the cells act like there is not enough cells.
This is what i have so implement the cells.
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
self.tableView.dataSource = self
self.tableView.delegate = self
}
// MARK: - UITableViewDatasourse
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
return tableView.dequeueReusableCell(withIdentifier: "categoryCell", for: indexPath) as! CategoryTableViewCell
default:
return tableView.dequeueReusableCell(withIdentifier: "productCell", for: indexPath) as! ProductTableViewCell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.row {
case 0:
return 110
default:
return tableView.frame.height - 110
}
}
I have the categories bar's height to be 110 and the rest of the frame to be for the feed. Everything is in a regular view controller. If there's a better way to approach this where I can also tap on the bar to interact with the feed I'm open to reprogramming everything.
Structure :
ViewController
-TableView
—CategoriesCell (height 110)
—-CollectionView (hor)
—PostsCell (how to make this dynamic depending on the CollectionView content)
—-CollectionView (ver)
Its not scrolling because there are only 2 cells, table view can scroll if there are more cells which is not the case in this.
You will need to change the approach as you won't be able to add cell in cell which you have to do this in this. Eg, for 0th index cell which is for category you will have to add cells to show the data as in insta.
For categories/stories bar : Use collection view for this as table view doesn't have horizontal scroll by default and collection view can be more customized.
For feed : You can use either collection view or table view for this. The number of cells will be the number of feed to be shown so the table view will scroll until there is feed data.
Requirement :
I have a list of UITableviewCell loaded from a nib that I'm presenting on UITableview. The first time I open the UIViewController all cells are shown correctly and work as expected.
Issue :
If I navigate back to the parent and then open the UIViewController again the UITableviewCell are 'invisible'. I say invisible because with a breakpoint in cellForRowAt I can see that the table view does load all cells and the cells are valid.
Code :
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 13
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = (project?.sliderData.sliders[indexPath.row].view)! as UITableViewCell
print(cell.contentView.subviews.count)
if let left = cell.viewWithTag(2) as? UILabel {
left.text = "left"
}
if let middle = cell.viewWithTag(3) as? UILabel {
middle.text = "middle"
}
if let right = cell.viewWithTag(4) as? UILabel {
right.text = "right"
}
return cell
}
Screen Shot Image
Expected observation :
I was thinking that maybe the subviews of the cells get released because I don't have any bindings to them in IB. To test this I'm printing the count of subviews and writing some text to the subview labels. And everything seems to go fine, the cells are loaded and the labels are there but the cells just don't show up.
But then, if I scroll the TableView up and down a little to get some cells updated those cells do appear at the top and bottom of the view as shown in the pic.
You need to call dequeueReusableCell(withIdentifier: "cell") inside your code then will show your table cell. It will reuse cell for your all numbers of row data content.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! UITableViewCell
return cell
}
More Details : How to create uitableview with multiple sections in iOS Swift.
Did not find reason why the tableView behaves the way it does so I solved the issue by dequeueing default cells. The views provided by the slider objects are added as subviews to the dequeued cells. Now the subviews can of course be any UIViews.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "sliderCell")
if cell == nil {
cell = UITableViewCell.init(style: .default, reuseIdentifier: "sliderCell")
}
cell?.addSubview((project?.sliderData.sliders[indexPath.row].view)!)
return cell!
}