How to Keep Uitableview Contentoffset After Calling -Reloaddata

UITableView does not handle contentOffset correctly when scrolling/paging

From your sample project, I can understand is you are trying to implement infinite scroll through the window content concept so that you can always have fixed number of rows (index paths ,lets say 100) so that when window scrolls down/up - table view remove indexPaths from top/bottom accordingly.
And even though you have more data source item you can always have tableView of indexPaths 100

Basically you are dealing with two problem here:

  1. ContentOffset

  2. Dynamic height

Let's assume we have height is fixed (44) and table is not inverted.
To implement Window for infinite scrolling you have to do following:

override func scrollViewDidScroll(_ scrollView: UIScrollView) {

let bottom: CGFloat = scrollView.contentSize.height - scrollView.frame.size.height
let buffer: CGFloat = 3 * 44
let scrollPosition = scrollView.contentOffset.y

if (scrollPosition > bottom - buffer) {
dataSource.expose(dataSource.exposedRange.shift(by: 25))
self.tableView.contentOffset.y -= self.dataSource.deltaHeightToRemove
}
}
  • Decide how much height buffer you need to keep when scroll goes down. This height buffer is the height after which you decides to insert some more item (25) into the datasource.
  • At this point you now have to remove items from the top
  • As you remove item from top you are basically telling scrollView to reduce it's content-offset by same height.
  • In this way total content size will be fixed every time

    Hope it will help.

    EDIT:-
    Here is the modified code which actually does infinite scroll at bottom of
    table view with dynamic cell height. This doesn't increase the rows count more than 100. but still loads data in sliding window.
    link

How to get UITableView last scroll position before show next UIViewController?

By using contentOffset.

Save it on leaving:

float savedVerticalContentOffset = tableView.contentOffset.y;

and then when you return and refresh just call:

[tableView setContentOffset:CGPointMake(0, savedVerticalContentOffset)];

How to get the scroll position of a UITableView in real time

Small gotcha - in Swift 3 it's

func scrollViewDidScroll(_ scrollView: UIScrollView)

and the below is not going to be triggered :

func scrollViewDidScroll(scrollView: UIScrollView)

Setting content offset of a UITableView not working when it's scrolling

if i got you right the following should work:

func scrollViewWillBeginDecelerating(scrollView: UIScrollView) {
scrollTableView()
}

Observe current contentOffset during UiTableView's delete rows animation

Ok, guy! I have a good piece of news for you =] You can use Layer Presentation Tree to observe UI values each moment.

  1. Wrap tableView updates into CATransaction
    let displayLink = CADisplayLink(target: self, selector: #selector(adjustHeader))
displayLink.add(to: .current, forMode: .common)

CATransaction.begin()
CATransaction.setCompletionBlock(displayLink.invalidate)
// do tableView updates here
CATransaction.commit()


  1. Use layer presentation tree to calculate current offset. For example:
@objc private func adjustHeader() {
guard
let headerPresentationLayer = headerView.layer.presentation(),
let viewPresentationLayer = view.layer.presentation(),
let tableViewPresentationLayer = tableView.layer.presentation()
else { return }

let origin = tableViewPresentationLayer.convert(headerPresentationLayer.frame.origin, to: viewPresentationLayer)
print("LayerPresentationTree: \(origin)")
offsetDepandableView.frame.origin = origin
}

Remembering state: content offset and table cell reuse

If I'm understanding this correctly, you have a scrollview inside each cell that you want to remember the position of. What you need to do is save the content offset along with the text your UITableViewDataSource is providing and set it in your cellForRowAtIndexPath method.

UIScrollView contentOffset corrupted after orientation change

You needed a couple things...

First, we need to give the tableView an estimated row height. We can do this here:

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// only need to call this if tableView size has changed
if tableView.estimatedRowHeight != tableView.frame.size.height {
tableView.estimatedRowHeight = tableView.frame.size.height
}
}

Next, after the rotation, we need to tell the tableView to recalculate its layout. The proper place to handle "device rotation" actions is here, as the tableView can change size due to other factors (not in this test controller, but in general):

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: nil, completion: {
_ in
// tell the tableView to recalculate its layout
self.tableView.performBatchUpdates(nil, completion: nil)
self.labelOffset.text = "Offset: \(Int(self.tableView.contentOffset.y)) ContentSize: \(Int(self.tableView.contentSize.height))"
})
}

Here is your complete example, with those two funcs and minor edits (removed unused code, changed the labelOffset.text, etc... see the comments in the code):

class TestRotateViewController: UIViewController {

var isLandscape = false

var tableView: UITableView!
var buttonRotate: UIButton!
var labelOffset: UILabel!
let numberOfItems = 30

override func viewDidLoad() {
super.viewDidLoad()

tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true

tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.dataSource = self
tableView.delegate = self
tableView.isPagingEnabled = true


buttonRotate = UIButton()
buttonRotate.backgroundColor = .lightGray
buttonRotate.addTarget(self, action: #selector(clickedRotate(_:)), for: .touchUpInside)
buttonRotate.translatesAutoresizingMaskIntoConstraints = false
buttonRotate.setTitle("Rotate", for: .normal)
view.addSubview(buttonRotate)
buttonRotate.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
buttonRotate.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true

labelOffset = UILabel()
labelOffset.translatesAutoresizingMaskIntoConstraints = false
labelOffset.text = "Offset: \(tableView.contentOffset.y)"
view.addSubview(labelOffset)
labelOffset.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
labelOffset.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true

}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// only need to call this if tableView size has changed
if tableView.estimatedRowHeight != tableView.frame.size.height {
tableView.estimatedRowHeight = tableView.frame.size.height
}
}

// viewDidAppear implemented ONLY to update the labelOffset text
// this is NOT needed for functionality
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
labelOffset.text = "Offset: \(Int(tableView.contentOffset.y)) ContentSize: \(Int(tableView.contentSize.height))"
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: nil, completion: {
_ in
// tell the tableView to recalculate its layout
self.tableView.performBatchUpdates(nil, completion: nil)
self.labelOffset.text = "Offset: \(Int(self.tableView.contentOffset.y)) ContentSize: \(Int(self.tableView.contentSize.height))"
})
}

@IBAction func clickedRotate(_ sender: Any) {

self.isLandscape = !self.isLandscape

if self.isLandscape {
let value = UIInterfaceOrientation.landscapeRight.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
} else {
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
}

}

extension TestRotateViewController: UITableViewDelegate, UITableViewDataSource {

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return tableView.frame.size.height
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfItems
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = indexPath.row.description
cell.textLabel?.font = .systemFont(ofSize: 40)
let index = indexPath.row
if index % 2 == 0 {
cell.backgroundColor = .yellow
} else {
// light-blue to make it easier to read the black label text
cell.backgroundColor = UIColor(red: 0.0, green: 0.75, blue: 1.0, alpha: 1.0)
}
return cell
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
labelOffset.text = "Offset: \(Int(scrollView.contentOffset.y)) ContentSize: \(Int(scrollView.contentSize.height))"
}

}


Related Topics



Leave a reply



Submit