UITableView not scrollable after scrollToRow - ios

I have a table with number of rows. I would like to pre-scroll table after it's load and it's not so hard, but I have a problem. Table become non-scrollable after scrollToRow execution.
Here is my code scroll function:
func scrollToValue() {
var scrollPosition: Int = 30
let indexPath = IndexPath(row: scrollPosition, section: 0)
tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
}
and I execute it in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
scrollToValue()
}
Here is what I have executed this code and try to move table top or bottom: table moves back to 15.0 value at the middle after my interaction, but I would like to scroll to any other position top and bottom without rollback. Even though if I choose false for animation scrollToRow(at: indexPath, at: .middle, animated: false) I can't move cells at all. I can see scroll indicator at the right but cells are frozen.

Remove scroll action from layoutSubviews as tableView is still not loaded and try this
override func viewDidAppear() {
scrollToValue()
}

Related

Swift first cell in tableView lost because add UISearchController

the first cell in table view lost after push navigation from other page. why tableview not scroll to top position.
code
https://github.com/frankenly/ios-tableview-search-lost
https://github.com/frankenly/ios-tableview-search-lost/blob/main/StackOverflow/StackOverflow/ViewController.swift
Firstly you can do one thing
Add search bar above tableview cell as shown in picture
You can add below method
let indexPath = IndexPath(row: 0, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
Hope it's work for you.Thank you.

How to allow an off screen UITableViewCell push down content offset?

I have a UITableView, which contains a bunch of cells.
If a user clicks on the second cell, the first cell is supposed to animate and expand its height significantly and push all of the other cells down, leaving the user's scroll position in the same place. My code is working 100% correctly when both cells are on the screen. The contentSize of the UITableView grows significantly, and the contentOffset does not change.
But if the user scrolls down so that only the second cell is visible, when they tap it, the first cell expands off screen and nothing happens for the user.
The contentSize of the UITableView does not change, nor does the contentOffset. Once the user scrolls up slightly, and sees the bottom of the expanded cell, the contentSize and the contentOffset update to reflect the fact that the first cell is indeed much bigger (but nothing changes from the user's perspective)
Calling heightForRowAtIndexPath for the the index path of the cell before and after expanding returns the expected values.
My code has a lot going on, but the main piece that is supposed to be animating the expansion is here:
UIView animateWithDuration:0.3
animations:^{
performSelectorOnMainThread(#selector(updateItemHeights:), withObject: nil, waitUntilDone: YES)
}
completion:^(BOOL finished){}]
And the implementation of of updateItemHeights:
beginUpdates
endUpdates
self.contentSize = sizeThatFits([contentSize.width, CGFLOAT_MAX])
It seems like iOS is trying to keep the user in their current context by allowing the cells above to expand.
How do I get an off screen cell to push the other cells down?
Thanks to the table view dequeue system, when a cell is not visible, it is not loaded. So the table view won't animate a change if it is not visible on screen.
I see 2 options here :
Scrolling to the animated cell before updating its height
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let animatedIndexPath = ...
let visibleRows = tableView.indexPathsForVisibleRows ?? []
if visibleRows.contains(animatedIndexPath) {
self.tableView.reloadRows(at: [animatedIndexPath], with: .automatic)
} else {
UIView.animate(withDuration: 0.3, animations: {
self.tableView.scrollToRow(at: animatedIndexPath, at: .none, animated: false)
}) { _ in
self.tableView.reloadRows(at: [animatedIndexPath], with: .automatic)
}
}
}
Adjusting the content offset once the cell is updated
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let animatedIndexPath = ...
let visibleRows = tableView.indexPathsForVisibleRows ?? []
if visibleRows.contains(animatedIndexPath) {
self.tableView.reloadRows(at: [animatedIndexPath], with: .automatic)
} else {
let offset = tableView.contentOffset
tableView.reloadData()
tableView.layoutIfNeeded() // forces the new offset computation
tableView.setContentOffset(offset, animated: true)
}
}
(you might encounter some issues because of the table view dynamic height computation, disable it tableView.estimatedRowHeight = 0)

UITableView Scrolls to top after insert row

I have an expandable UITableView. When sections tapped, they expand or collapse with animation (Scroll). My problem is that there is a weird animation when expanding or collapsing headers. UITableView scrolls to top and then goes to the tapped cell. In addition, when there is no expanded cell, sometimes, It doesn't scroll to top and there is a big space between top header and top view of UITableView.
My problem is that I need to scroll to expanded section and also get rid of scroll to top bug.
I tried below solution but didn't work for me:
prevent table view to scrolling top after insertRows
It also looks like same problem with below question, but can't figure out how to implement it.
Why does my UITableView "jump" when inserting or removing a row?
How I toggle selection:
func toggleSection(header: DistrictTableViewHeader, section: Int) {
print("Trying to expand and close section...")
// Close the section first by deleting the rows
var indexPaths = [IndexPath]()
for row in self.cities[section].districts.indices {
print(0, row)
let indexPath = IndexPath(row: row, section: section)
indexPaths.append(indexPath)
}
let isExpanded = self.cities[section].isExpanded
if(isExpanded){
AnalyticsManager.instance.logPageEvent(screenName: analyticsName!, category: "Button", action: Actions.interaction, label: "\(self.cities[section].name) Collapse Click")
}else{
AnalyticsManager.instance.logPageEvent(screenName: analyticsName!, category: "Button", action: Actions.interaction, label: "\(self.cities[section].name) Expand Click")
}
self.cities[section].isExpanded = !isExpanded
// This call opens CATransaction context
CATransaction.begin()
// This call begins tableView updates (not really needed if you only make one insertion call, or one deletion call, but in this example we do both)
tableView.beginUpdates()
if isExpanded {
tableView.deleteRows(at: indexPaths, with: .automatic)
} else {
tableView.insertRows(at: indexPaths, with: .automatic)
}
// completionBlock will be called after rows insertion/deletion animation is done
CATransaction.setCompletionBlock({
// This call will scroll tableView to the top of the 'section' ('section' should have value of the folded/unfolded section's index)
if !isExpanded{
self.tableView.scrollToRow(at: IndexPath(row: NSNotFound, section: section) /* you can pass NSNotFound to scroll to the top of the section even if that section has 0 rows */, at: .top, animated: true)
}
})
if self.scrollToTop(){
self.tableView.setContentOffset(.zero, animated: true)
}
// End table view updates
tableView.endUpdates()
// Close CATransaction context
CATransaction.commit()
}
private func scrollToTop() -> Bool{
for sec in self.cities{
if(sec.isExpanded){
return false
}
}
return true
}
I'm giving height of cell inside;
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
How I declare headers;
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = DistrictTableViewHeader()
header.isColapsed = !self.cities[section].isExpanded
header.customInit(title: self.cities[section].name, section: section, delegate: self)
return header
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
EDIT: Solution in this question (Setting estimated height to 0) looks like working when inserting row. However, I still have bug when deleting rows. Bottom header goes to center of tableview and then goes to bottom after collapse header.
iOS 11 Floating TableView Header
You can try using below code. Just get the last content offset of your tableview. Then do the update and reassign the content offset.
let lastOffset = tableView.contentOffset
self.tableView.beginUpdates()
self.tableView.layer.removeAllAnimations()
self.tableView.endUpdates()
tableView.contentOffset = lastOffset
Instead of tableView.beginUpdates() and tableView.endUpdates(), In my Code i'm using tableView.reloadData() for expanding and contracting the particular section, You can call reloadData when you need to provide expansion of section.This results that you don't have the problem of animation scroll to the top. And works fine in my project where I have to show number of rows in particular section on a click of button which includes in that section.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
}
// Ignore the If Else statements it's just when do you need expansion of section.
else {
if showMore == true {
return self.userPoints.rewardsData[section - 1].count - 1
}
else {
return 0
}
}
}
Also Don't Forget to increase or decrease the number of rows to that particular section accordingly.Previous line is important to avoid any crash.
Simple Solution swift3 and Above
use below extension as
eg: tableViewOutlet.tableViewScrollToBottom(animated: true)
extension UITableView {
func tableViewScrollToBottom(animated: Bool) {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
let numberOfSections = self.numberOfSections
let numberOfRows = self.numberOfRows(inSection: numberOfSections-1)
if numberOfRows > 0 {
let indexPath = IndexPath(row: 0, section: 0)
self.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.top, animated: animated)
}
}
}
}
I also facing same issue but after read some tutorials and research & Analysis I got the this issue occurred due to height of cell when you expand the section at that tableview count height of cell from 0 to 120(as per your cell height).
In my case I solved that issue using estimated height of cell.
I hope that will help you,
Thanks

How can I scroll a collection view cell to the middle of the visible area

I am using UICollectionView to create a single row of cells which serve as an index to display a table view section with more details. The user can scroll the cells and select a particular cell which brings up the corresponding detail by scrolling a UITableview section below. This works fine. I would like a specific cell to be roughly in the middle of the row of visible cells when the view is first shown after being segued from a previous screen. Is there something comparable to scrollTableTo in collection view? In How to scroll to a particular index in collection view in swift there is a reference to scrollToItem but I need help understanding how to use it. It compiles and executes in viewDidAppear. The UITableview is scrolled as expected. The item i in the code segment cell is the item I want displayed. However, the cells are not changed (The first item on the list is displayed on the left). What works is the same statement in the shouldSelectAt. The cell that was selected is centered in the visible set.
override func viewDidAppear(_ animated: Bool) {
// DOES NOT WORK
var i = 0
for workorder in dm.workOrderNames {
if (workOrderToBeDisplayed == workorder) {
scrollTableTo(section: i, row: 0)
print("workorder = \(workorder) i = \(i)")
return
}
i = i + 1
}
workOrderView.scrollToItem(at:IndexPath(item: i, section: 0), at: .centeredHorizontally, animated: false)
}
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
// WORKS
workOrderView.scrollToItem(at:IndexPath(item: indexPath.item, section: 0), at: .centeredHorizontally, animated: false)
return true
}
You can try this
override func viewWillAppear(_ animated: Bool) {
self.yourCollection.scrollToItem(at: IndexPath(item: collectionSize/2, section: 0), at: .centeredHorizontally, animated: false)
}
}

UIDatePicker in UITableView, ensure Visibility

I have a static UITableView with several sections. In one table row I have a UIDatePicker. On touch the table cell expands and I can select the date. Fine so far. But if the table row is on the bottom of the page I need to manually scroll up to select a date. How can I ensure the datepicker to be in view like the calendar app does? Can you please point me into the right direction?
You can use this function:
func scrollToRowAtIndexPath(indexPath: NSIndexPath, atScrollPosition scrollPosition: UITableViewScrollPosition, animated: Bool)
Use it in
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// ...
var indexPathToJump = NSIndexPath(forRow: 0, inSection: 5)
tableView .scrollToRowAtIndexPath( indexPathToJump, atScrollPosition: .None, animated: true)
}
}
Use scrollToRowAtIndexPath:atScrollPosition:animated::
self.tableView.scrollToRowAtIndexPath(myIndexPath, scrollPosition: .None, animated: true)
I haven't tested this syntax, please let me know if it needs improved, but I know that the method is right. You want to use UITableViewScrollPosition.None so that it move the table view just enough that the row in question is in view:
UITableViewScrollPositionNone
The table view scrolls the row of interest to be fully visible with a minimum of movement. If the row is already fully visible, no scrolling occurs. For example, if the row is above the visible area, the behavior is identical to that specified by UITableViewScrollPositionTop. This is the default.
Available in iOS 2.0 and later.

Resources