I have a UITableView with two arrays as datasource. I also have one UISegmentedControl to switch between these arrays. I use the segmentedControlChanged method to call the reloadData method.
This works fine.
But there is one problem.
For example arrayOne has 1 item and arrayTwo has 10 items.
So if I switch to the arrayTwo and scroll to the bottom of the tableview and after that switch back to arrayOne the tableview stays scrolled down and i don't see any item. But if I start scrolling, the tableview "jumps" automatically to the first item.
So my question is, how can i trigger this behaviour automatically when the tabledata source changes?
You can use any one of following two lines after reloading table
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
or
mainTableView.setContentOffset(CGPoint.zero, animated: true)
Firstly i don't know how you change data source of tableview but i use this method for my data source switches.
I create 3 arrays;
var mainArr = [String]()
var sortedArr = [String]()
var arrToReturn : [String] {
if segmentedController.selectedIndex == 0 {
return mainArr
}
else {
return sortedArr
}
}
I always return arrToReturn for tableView data source.
When it comes to your scrolling issue, it sounds like you are reloading tableView before changing its dataSource.
After that when you start scrolling, tableView reloads cells and can't find old items and automatically sets its scrolling position to start of screen.
Also do you have any code about setting tableviews scrolling position even if it is irrelevant? If you have you should check it.
Related
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().
I have a TableView with a CollectionView inside.
So each Table cell is the delegate of the CollectionView.
The problem is that the collection View reloads on the main thread and the UITableView Cells are being reused.
That causes the collectionView cells to briefly present the labels of previous cells and then fade the changes.
How can I hide those labels when the tableView is initially passed through the return cell ?
The problem happens as collection.reload() is applied after the return cell of the UITableView in cell for row.
Here is a sample of the code.
UITableViewCell Code
let collectionDatasource = CollectionDataSource()
func configureCell(_ object: CustomClass) {
//Configure Object for tableView
self.presentedObject = object
self.nameLabel.text = object.name
//Set object and items on collection datasource
DispatchQueue.main.async {
let collectionItems = object.collectionItems ?? []
self.collectionDatasource.object = object
self.collectionDatasource.items = collectionItems
}
}
class CollectionDataSource: NSObject {
private var collectionView: UICollectionView?
var items: [CustomItem] = [] {
didSet {
DispatchQueue.main.async {
let changes = [CustomItem].changes(from: oldValue, to: self.items, identifiedBy: ==, comparedBy: <)
self.collectionView?.apply(changes: changes)
}
}
}
}
UICollectionView itself does its work asynchronously in contrast to UITableView. So, even if you will call reload collectionView directly in configureCell it will update the collection's cells after the table is set up (still can result in the mentioned issue). But you have added the "reload" call into DispatchQueue.main.async which makes the things worse.
You should skip using DispatchQueue.main.async if possible.
You should replace collectionView with a new one in configureCell. I suggest to add view into the cell of the same size and position as collection view and add collection view in the code. This will guarantee that previous collection view will never appear. 👍
I want to change my progress bar one by one in tableview. but when my use invisible cell in code, then it produces nil.
I use the tableview.cellforRowAt(IndexPath) in our code , Please resolve my problem
UITableViewCell is reusable, you cannot update UI of cells that are not displayed (visible), they just do not exist or being reused for another cell.
Reusable means that cell's view (UITableViewCell) will be used for any other cell that might just got in to the tableView bounds, this happens when you delete a row, or when you scroll away and the cell gets out of tableView bounds etc...
So, its not a problem, but by design.
BTW, you are using the correct method tableView.cellForRow(at: IndexPath), it will return a cell only if one is displayed.
You should instead update your data source and keep the progress value, when the cell next time being displayed it will show the value from data source, like so:
struct CellData {
var progress: Float
}
var dataSource: [CellData]
Now when you need to update the progress of cells, you can just update dataSource, like so
dataSource[index].progress = value
Then you call either call UITableView.realoadData() to refresh the UI of all cells that are visible, or you can update only that particular index, like so:
if let cell = self.tableView.cellForRow(at: IndexPath(row: index, section: 0)) as? CustomCell {
cell.progressView.setProgress(value, animated: true)
}
I have collection views (in plural) inside a table view of many sections. Just so we're clear, a single table view with many sections with only one row each being that row an individual collection view.
All set up is working just fine, the data is well divided and delegates are all wired up recognizing everything they need to recognize. My problem is kind of simple but difficult at the same time: I want to scroll to specific collection view's position whenever I need to find a specific cell in animated fashion.
So far I'm able to jump with no problem to both table section (indexPath.section) and collection item (indexPath.row). The issue arises when I need to scroll (simultaneously) with animation.
My findings so far
I'm only able to achieve my current goal deactivating scroll animations for UITableView (UICollectionView can perform well with/out it)
Whenever I set UITableView selectRow or scrollToRow animation flags to true then the app crashes (99% sure this happens because I'm trying to access and "invisible" section due to the animation hasn't shown it yet).
Relevant snippets of code
#IBOutlet weak var albumTableView: UITableView!
#IBOutlet weak var stickersCollectionView: UICollectionView!
func locateCell() {
...
let stickerIndex = methodThatReturnsExactIndex()
let sectionIndex = IndexPath(row: 0, section: stickerIndex.section)
albumTableView.selectRow(at: sectionIndex, animated: false, scrollPosition: .top)
let rowIndex = IndexPath(item: stickerIndex.row, section: 0)
stickersCollectionView.scrollToItem(at: rowIndex, at: 0, animated: true)
}
I was thinking in experiment with the UIScrollViewDelegate (detecting when the tableview and the collectionview stopped in order to perform the scrolling) but that would imply spreading global variables around the code and experience tough me that's just racing conditions waiting to happen. Any help will be appreciated.
First Scroll your tableView to that specific index with/without animation. This will make that cell visible now get your cell by providing that indexPath so you could access the collectionView object inside your tableViewCell. Then ask you collectionView to scroll to specific indexPath with/without animation.
Take another global bool to store that tableView is begin scrolling. Also store both indexPath used for collection and tableView and use tab
tableViewIsScrolling = true
let yourSelectedIndexPathForTableView = IndexPath(row: 0, section: 4)//store it globally
let yourSelectedIndexPathForCollectionView = IndexPath(row: 10, section: 0)//store it globally
tableView.scrollToRow(at: yourSelectedIndexPathForTableView, at: .middle, animated: false)
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
if tableViewIsScrolling {
tableViewIsScrolling = false
//Perform your scrolling of CollectionView
guard let yourCell = tableView.cellForRow(at: yourSelectedIndexPathForTableView) as? YourCell else {return}
yourCell.collectionView.scrollToItem(at: yourSelectedIndexPathForCollectionView, at: .centeredHorizontally, animated: true)
}
}
So basically I have a progress view, among others, within a UITableViewCell that is dynamic. How would I access the progress view (or any view) of a specific cell? I want to change the progress based on a value stored for that specific cell.
Thank you in advance.
You should have a view controller (class) level property for progress and eventually implement a property observer so that you can refresh the view as soon as progress gets updated.
var progress: Double = 0.0 {
didSet{
updateProgressCell()
}
}
Supposing that you know where (what's the row number - progressCellRowNumber) in your table is the progress view this is how you can implement the cell refreshing:
fund updateProgressCell() {
var indexPath = NSIndexPath(forRow: progressCellRowNumber, inSection: 0)
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
}