Start UITableView scrolled to bottom - ios

How do I start a UITableView scrolled to the last cell?
not animated
not after view appeared
not after table view is even added as a subview
just have a plain UITableView(frame: CGRectZero, style: UITableViewStyle.Plain) that when presented on screen, will start scrolled all the way to the bottom.
I've tried:
// 1
reloadData()
scrollToRowAtIndexPath(
NSIndexPath(forItem: dataArray.count-1, inSection: 0),
atScrollPosition: .Top, animated: false
)
// 2
reloadData()
var contentOffset = self.contentOffset
contentOffset.y = CGFloat.max
setContentOffset(contentOffset, animated: false)
on the tableView's init method (in my subclass)
I've also tried the classic CGAffineTransformMakeScale(1,-1) hack, but that makes my cells stick to the bottom and I want them stuck the the top (but scrolled to the bottom). (that is only relevant if I have few cells, when they don't fill the entire UITableView space)
EDIT: another detail, I'm using dynamic cells UITableViewAutomaticDimension.

This will scroll to bottom without any glitch, but if you use Tableview scroll-to-row property then there will be glitch.
For Swift 3 use
self.TableView.reloadData() // To populate your tableview first
//Since we have to wait until the table is reload
DispatchQueue.main.async {
let bottomOffset = CGPoint(x: 0, y: self.TableView.contentSize.height - self.TableView.frame.size.height)
self.TableView.setContentOffset(bottomOffset, animated: false)
}
For Objective C use
[YourTableView reloadData]; // To populate your tableview first
[YourTableView setContentOffset:CGPointMake(0, YourTableView.contentSize.height - YourTableView.frame.size.height)];

EDIT:
animated: false
func scrollBottom() {
let lastIndex = NSIndexPath(forRow: dataArray.count-1, inSection: 0)
self.tableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
}
TEST CODE:
import UIKit
class TableViewController: UITableViewController {
var goButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
goButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
goButton.backgroundColor = UIColor.blueColor()
goButton.addTarget(self, action: #selector(TableViewController.scrollBottom), forControlEvents: .TouchUpInside)
self.view.addSubview(goButton)
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 500
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = "Hello, World!"
return cell
}
func scrollBottom() {
let lastIndex = NSIndexPath(forRow: 499, inSection: 0)
self.tableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
}
}

You can either set the content offset:
tableview.contentOffset = CGPoint(x: ??, y: ??)
Or scroll to the desired row
tableview.contentOffset.scrollToItem(at: IndexPath(row: tableview.contentOffset.numberOfItems(inSection: 0) - 1, section: 0), at: .bottom, animated: false)
To get this to happen before the view appears so that the user doesn't see the transition, simply call these methods in the viewDidLayoutSubviews() lifecycle method of your view controller.

Related

Auto Select Middle Visible Cell Of Collection View

I'm trying to select and highlight the middle cell of the visible cells in a collection view at any given time. The collection view in question displays days for six months forwards and back.
I've tried using the scroll view delegates and the collection view delegates. But all that works is select and highlight code in didSelectItem() collection view delegate.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("delegate called")
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
collectionView.cellForItem(at: indexPath)?.backgroundColor = UIColor.highlightCellGreen()
if let cell = collectionView.cellForItem(at: indexPath) as? ClientListDateCollectionViewCell{
monthLabel.text = cell.monthName
monthLabel.text = monthLabel.text?.capitalized
}
I tried to select the middle cell while scrolling using the viewDidScroll() delegate. But, I wasn't able to get the output I wanted.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let visibleCellCount = dateCollectionView.indexPathsForVisibleItems.count
let cellCount = dateCollectionView.visibleCells.count
let visibleCells = dateCollectionView.indexPathsForVisibleItems[visibleCellCount-1/2]
if visibleCellCount>0{
let middle = visibleCellCount/2
let midValue = dateCollectionView.indexPathsForVisibleItems[middle]
dateCollectionView.selectItem(at: midValue, animated: true, scrollPosition: .centeredHorizontally)
}
How do I go about selecting the middle cell?
edit 1: The collection view starts on the leftmost point and then scrolls to the middle i.e, today's date
You can use delegate of UICollectionView (i.e: didHighlightItemAtIndexPath). just make sure to call collection view delegates on your desired time by calling reload function
self.collectionView.reloadData()
and in you collection view delegate just do this
func collectionView(collectionView: UICollectionView, didHighlightItemAtIndexPath indexPath: NSIndexPath){
var cell : UICollectionViewCell = UICollectionViewCell()
self.collectionView.cellForItemAtIndexPath = indexPath
//change highlighted color as of your need
cell.view.backgroundColor = UIColor.init(red: 25, green: 118, blue: 210).cgColor
}
This will highlight you selected item
Disable multiple selection (or selection entirely?) to make things easier.
collectionView.allowsMultipleSelection = false
On scrollViewDidScroll(_:) get the center point of the screen as CGpoint.
let center = collectionView.center
Use that information to get the index path of the center item
let indexPath = collectionView.indexPathForItem(at: center)
Select the item
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .top)
Suppose that you have the horizontal of displaying, and you want to have the auto scroll to the center of your item in datasource.
Creating a method and calling it immediately after your collection view is completely configured:
func scrollToCenterIndex() {
let centerIndex = LIST_OF_YOUR_DATA_SOURCE.count / 2
let indexPath = IndexPath(item: centerIndex, section: 0)
self.collectionView.scrollToItem(at: indexPath,
at: .right,
animated: false)
}
Inside the method:
public func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CELL,
for: indexPath) as? CustomCell else {
fatalError("Cannot create cell")
}
If indexPath.row == LIST_OF_YOUR_DATA_SOURCE.count / 2 {
// perform your hight light color to the cell
} else {
// reset your hight light color to default color
}
let model = LIST_OF_YOUR_DATA_SOURCE[indexPath.row]
cell.configure(model)
return cell
}
I think you can use a method to get the center point of collection view, and use this value to get the the middle of visible cell.
let centerPoint = self.view.convert(collectionView.center, to: collection)
Here is an example I did it with a tableView. You can apply it to your collection view with the same approach.
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var dataSource = Array(1...31)
var centerIndex: IndexPath?
func setCellSelected(cell: UITableViewCell, _ selected: Bool) {
cell.contentView.backgroundColor = selected ? .green : .white
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CELL")
cell?.textLabel?.text = String(dataSource[indexPath.row])
let center = self.view.convert(tableView.center, to: tableView)
if let index = tableView.indexPathForRow(at: center), let cell = cell {
setCellSelected(cell: cell, indexPath.row == index.row)
}
return cell!
}
}
extension ViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// reset the previous hight light cell
if let centerIndex = centerIndex, let cell = tableView.cellForRow(at: centerIndex) {
setCellSelected(cell: cell, false)
}
// set hight light to a new center cell
let center = self.view.convert(tableView.center, to: tableView)
if let index = tableView.indexPathForRow(at: center), let cell = tableView.cellForRow(at: index) {
setCellSelected(cell: cell, true)
centerIndex = index
}
}
}
I was also trying to do the auto-selection of the middle visible cell of the collection view, and I got the solution, here is the solution:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
// Reload Collection View
collectionView.reloadData()
// Find centre point of collection view
let visiblePoint = CGPoint(x: collectionView.center.x + collectionView.contentOffset.x, y: collectionView.center.y + collectionView.contentOffset.y)
// Find index path using centre point
guard let newIndexPath = collectionView.indexPathForItem(at: visiblePoint) else { return }
// Select the new centre item
collectionView.selectItem(at: newIndexPath, animated: true, scrollPosition: .centeredHorizontally) }
You need to use the Scroll view delegate function, scrollViewDidEndDecelerating. Reload the collection view first. Second, find the center visible point of the collection view. Third, using the center visible point, find the indexPath of collection view and finally use the index to select the item in the collection view.
I know I answered this question a little late, still thinking that it will be helpful for someone.
Cheers!

Using an action to scroll CollectionView to the next cell

I have a horizontal collectionview that adds a new cell every time I tap the button. I am attempting to scroll the collectionview to the next cell once the cells are no longer visible each time I tap the button.
The code I have now is not working properly
Code:
#IBAction func random(_ sender: Any) {
resultsCollection.reloadData()
let collectionBounds = resultsCollection.bounds
let contentOffset = CGFloat(floor(resultsCollection.contentOffset.x - collectionBounds.size.width))
self.moveToFrame(contentOffset: contentOffset)
}
func moveToFrame(contentOffset : CGFloat) {
let frame: CGRect = CGRect(x : contentOffset ,y : resultsCollection.contentOffset.y ,width : resultsCollection.frame.width,height : resultsCollection.frame.height)
resultsCollection.scrollRectToVisible(frame, animated: true)
}
How can I fix this so that it scrolls correctly when I tap the button?
After reload your data, call this lines.
let lastItem = resultsCollection(resultsCollection, numberOfItemsInSection: 0) - 1
let indexPath = IndexPath(row: lastItem, section: 0)
resultsCollection.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
Use the following code to achieve your needs. For animation, you just need to pass value true/false in animated of scrollToRow function.
Hope this will help you!
To scroll top without animation
func scrollToTopWithoutAnimation() {
DispatchQueue.main.async {
if self.dataArray.count > 0 {
let indexPath = IndexPath(row: 0, section: 0)
collectionView.scrollToItem(at: indexPath, at: .top, animated: false)
}
}
}
To scroll top with animation
func scrollToTopWithAnimation() {
DispatchQueue.main.async {
if self.dataArray.count > 0 {
let indexPath = IndexPath(row: 0, section: 0)
collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
}
}
}
Set IndexPath row as per your needs

Table view overlapping cells swift2

I have a tableview in my app. it's on the detail view of my split view. Whenever i populate my tableview with UItextviews, the cells seem to overlap each other. this is not what i want. Does anyone have any suggestions to fix this?
here's my vc code:
import UIKit
class DetailViewController: UIViewController, UITableViewDelegate, UITextViewDelegate {
#IBOutlet weak var messagesTableView: UITableView!
var initVar = 0
var row: Int?
var firstLoad = true
var detailItem = {}
var convo = ["hello", "hellohello", "hellohellohellohello", "hellohellohellohellohellohellohellohello", "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello"]
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
print("hello")
messagesTableView.separatorColor = UIColor.whiteColor()
print(row)
messagesTableView.reloadData()
print(firstLoad)
let numberOfSections = messagesTableView.numberOfSections
let numberOfRows = messagesTableView.numberOfRowsInSection(numberOfSections-1)
if numberOfRows > 0 {
print(numberOfSections)
let indexPath = NSIndexPath(forRow: numberOfRows-1, inSection: (numberOfSections-1))
messagesTableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
}
messagesTableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return convo.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("detailCell", forIndexPath: indexPath)
var textView = UITextView()
textView.delegate = self
textView.editable = false
textView.text = convo[indexPath.row]
textView.textAlignment = .Center
textView.sizeToFit()
if indexPath.row % 2 == 0 {
textView = UITextView(frame: CGRect(x: cell.frame.width * (2/5), y: 4, width: cell.frame.width * (3/5), height: cell.frame.height))
textView.backgroundColor = UIColor.blueColor()
} else {
textView = UITextView(frame: CGRect(x: 0, y: 4, width: cell.frame.width * (3/5), height: cell.frame.height))
textView.backgroundColor = UIColor.greenColor()
}
textView.textColor = UIColor.whiteColor()
textView.delegate = self
textView.editable = false
textView.text = convo[indexPath.row]
textView.textAlignment = .Center
textView.sizeToFit()
tableView.rowHeight = textView.frame.height + 7
cell.addSubview(textView)
return cell
}
}
The cells are being re-used in a UITableView.
You are adding TextView's on it, so you are keep on adding TextView's every time it's being re-used.
Your code should be modified so that the TextView is only added IF it's not already present on the cell.
The easiest way, is to add a TAG (unique) to the TextView, then if the View from Tag is nil (AKA not present) add it, else get it from the ViewFromTag
As for an example, although I'm still using obj-c and with that in mind, my code might have an error, here is the code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("detailCell", forIndexPath: indexPath)
var textView: UITextView
if (cell.viewWithTag(10000) != nil) {
textView = (cell.viewWithTag(10000) as? UITextView)!
}
else {
textView = UITextView()
textView.tag = 10000
textView.delegate = self
textView.editable = false
textView.textAlignment = .Center
cell.addSubview(textView)
}
textView.text = convo[indexPath.row]
if indexPath.row % 2 == 0 {
textView.frame = CGRectMake(cell.frame.width * (2/5) , 4, cell.frame.width * (3/5), cell.frame.height)
textView.backgroundColor = UIColor.blueColor()
} else {
textView.frame = CGRectMake(0 , 4, cell.frame.width * (3/5), cell.frame.height)
textView.backgroundColor = UIColor.greenColor()
}
textView.sizeToFit()
tableView.rowHeight = textView.frame.height + 7
return cell
}
To explain the code, as you didn't understood:
The UITableView is re-using the cells.
This means, whenever a cell is moved outside the visible part of the screen, it is been re-used (as it is) and is displayed as a new cell for the TableView.
The reason for this, is memory efficiency.
So imagine a UITableView with 1000 row. If for each row a new cell was created, we will have to allocate memory for each cell.
But in a typical application usually only a few cells are visible each time (usually less than 10).
So the trick here is that we only need to create that many cells.
Once a cell gets out of sight, it is been served as a new cell for the TableView.
So if you add a UITextView every time the tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell delegate method is called, and the cell is been re-used, you end up adding UITextViews to a cell that already has one. So you end up with the problem you showed.
The code I provided does two things differently:
It does assign a unique Tag number on the UITextView when it's added to the cell (textView.tag = 10000)
It does check if a UITextView has already been added to the cell and if it has been added we use that UITextView and we don't add it again (if (cell.viewWithTag(10000) != nil))

How to programmatically select a row in UITableView in Swift

I need to select a row in a UITableView programmatically using Swift 1.2.
This is the simple code:
var index = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.selectRowAtIndexPath(index, animated: true, scrollPosition: UITableViewScrollPosition.Middle)
self.tableView(self.tableView, didSelectRowAtIndexPath: index)
The above gives me the following error:
Cannot invoke 'selectRowAtIndexPath' with an argument list of type '(NSIndexPath!, animated: Bool, scrollPosition: UITableViewScrollPosition)'
What is wrong with my Swift 1.2 code?
My UItableView has been created in IB in the UIViewController that I am trying to call the code above.
When I put the code in a UITableViewController the compiler does not give any errors.
Do I need to embed a UITableViewController in a container or is there another way?
Swift 3 to Swift 5 Solution
Selecting a Row
let indexPath = IndexPath(row: 0, section: 0)
myTableView.selectRow(at: indexPath, animated: true, scrollPosition: .bottom)
myTableView.delegate?.tableView!(myTableView, didSelectRowAt: indexPath)
DeSelecting a Row
let deselectIndexPath = IndexPath(row: 7, section: 0)
myTableView.deselectRow(at: deselectIndexPath, animated: true)
myTableView.delegate?.tableView!(myTableView, didDeselectRowAt: indexPath)
The statement
self.tableView.selectRowAtIndexPath(index, animated: true, scrollPosition: UITableViewScrollPosition.Middle)
assumes that tableView is a property of the view controller, connected
to a table view in the Storyboard. A UITableViewController, for example, already has this
property.
In your case, the view controller is a not a table view controller
but a subclass of a UIViewController. It also has an outlet that is
connected to the table view, but it is not called
tableView but menuTable. Then of course you have to call
self.menuTable.selectRowAtIndexPath(index, animated: true, scrollPosition: UITableViewScrollPosition.Middle)
to select a row of that table view.
The strange error messages are caused by the fact that
self.tableView can also be understood by the compiler as a
"curried function" (compare http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/).
Use below code,after loading your table view with data:
let rowToSelect:NSIndexPath = NSIndexPath(forRow: 0, inSection: 0); //slecting 0th row with 0th section
self.tableView.selectRowAtIndexPath(rowToSelect, animated: true, scrollPosition: UITableViewScrollPosition.None);
now,you have to manually call didSelectRowAtIndexPath: delegate method using below code:
self.tableView(self.tableView, didSelectRowAtIndexPath: rowToSelect); //Manually trigger the row to select
Thanks.
Swift 3.x
if you want to do it at the 'cell-creation', you can do it like this
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = TableViewCell()
let item = items[indexPath.row]
cell.textLabel?.text = item.title
if (item.checked) {
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
}
return cell
}
Using Swift 2.x, as described by Pankaj purohit answers the correct method is:
func tapRowAtIndex(index:Int) {
let rowToSelect:NSIndexPath = NSIndexPath(forRow: index, inSection: 0)
self.tableView.selectRowAtIndexPath(rowToSelect, animated: true, scrollPosition: UITableViewScrollPosition.None)
self.tableView(self.tableView, didSelectRowAtIndexPath: rowToSelect)
}
Keep in mind that if you call this method from an external class for example, you dont know when tableView has finished its loading, so what's the possibilities?, how to workaround this problem? :
Step one: create a class boolean var
var automatingTap: Bool = false
Step two: check when the table finish its loading and launch an "end operations" method:
func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
{
let lastRowIndex = tableView.numberOfRowsInSection(0)
if indexPath.row == lastRowIndex - 1 {
endOperations()
}
}
func endOperations()
{
print("finished loading")
if automatingTap {
tapRowAtIndex(0)
automatingTap = false
}
}
Step three: call my tableView class from another class
for example:
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
if segue!.identifier == "DetailsTableView" {
let viewController:ViewController = segue!.destinationViewController as ViewController
viewController.automatingTap = true
}
}
Reusable function with validation of table size
Swift 4 and 5
This reusable function works and validate the size of table.
func selectRow(tableView: UITableView, position: Int) {
let sizeTable = tableView.numberOfRows(inSection: 0)
guard position >= 0 && position < sizeTable else { return }
let indexPath = IndexPath(row: position, section: 0)
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
}
you can use it in this way
selectRow(tableView: myTableView, position: pos)
or you can implement this extension:
extension UITableView {
func selectRow(row: Int, section: Int = 0) {
let sizeTable = self.numberOfRows(inSection: section)
guard row >= 0 && row < sizeTable else { return }
let indexPath = IndexPath(row: row, section: section)
self.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
}
}
and you can use it in this way:
mytableView.selectRow(row: pos, section: 0)
or
mytableView.selectRow(row: pos)
Swift 4.2:
Select one or more Rows
let ndx:IndexSet = [1]
// or: let ndx:IndexSet = [1, 2, 3]; // etc
tableview?.selectRowIndexes(ndx, byExtendingSelection: false);
Deselect a Single Row
tableview?.deselectRow(current)
Note that if you have (func tableViewSelectionDidChange(...)) implemented, that will be triggered by the above.
Also see Charlton Provatas' answer at https://stackoverflow.com/a/48696458/352920 for an extension to NSTableView, that provides a simple
tableview?.selectRow(at:)

UITableView Refresh without scrolling

I have a _TableView with items , and I want to set automatic refresh,and I don't want it to scroll on refresh , lets say user scrolled 2 pages down , and the refresh trigered -> so I want to put the refreshed content to the top of the table without interupting user's scrolling
Assume user was on row 18
and now the _dataSource is refreshed so it fetched lets say 4 items , so I want user to stay on the item he was.
What would be the best approach to achieve it ??
For Swift 3+:
You need to save the current offset of the UITableView, then reload and then set the offset back on the UITableView.
I have created this function for this purpose:
func reload(tableView: UITableView) {
let contentOffset = tableView.contentOffset
tableView.reloadData()
tableView.layoutIfNeeded()
tableView.setContentOffset(contentOffset, animated: false)
}
Simply call it with: reload(tableView: self.tableView)
SWIFT 3
let contentOffset = self.tableView.contentOffset
self.tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(contentOffset, animated: false)
This is error of iOS8 when using UITableViewAutomatic Dimension. We need store the content offset of table, reload table, force layout and set contenOffset back.
CGPoint contentOffset = self.tableView.contentOffset;
[self.tableView reloadData];
[self.tableView layoutIfNeeded];
[self.tableView setContentOffset:contentOffset];
I am showing if only one row is being added. You can extend it to multiple rows.
// dataArray is your data Source object
[dataArray insertObject:name atIndex:0];
CGPoint contentOffset = self.tableView.contentOffset;
contentOffset.y += [self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
[self.tableView reloadData];
[self.tableView setContentOffset:contentOffset];
But for this to work you need to have defined - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath the method. Or else, you can directly give your tableview row height if it is constant.
Just set estimatedRowHeight to maximum possible value.
self.tableView.estimatedRowHeight = 1000
self.tableView.estimatedSectionFooterHeight = 100.0
self.tableView.estimatedSectionHeaderHeight = 500.0
That's it!!
Note:
Please do not use FLT_MAX, DBL_MAX value. May be it will crash your app.
I'm doing it this way:
messages.insertContentsOf(incomingMsgs.reverse(), at: 0)
table.reloadData()
// This is for the first load, first 20 messages, scroll to bottom
if (messages.count <= 20) {
let indexToScroll = NSIndexPath(forRow: saferSelf.messages.count - 1, inSection: 0)
table.scrollToRowAtIndexPath(indexToScroll, atScrollPosition: .Top , animated: false)
}
// This is to reload older messages on top of tableview
else {
let indexToScroll = NSIndexPath(forRow: incomingMsgs.count, inSection: 0)
table.scrollToRowAtIndexPath(indexToScroll, atScrollPosition: .Top , animated: false)
// Remove the refreshControl.height + tableHeader.height from the offset so the content remain where it was before reload
let theRightOffset = CGPointMake(0, table.contentOffset.y - refreshControl.frame.height - table.headeView.frame.height)
table.setContentOffset(theRightOffset, animated: false)
}
...also, since I use dynamic cell height, to avoid some weirdness, the estimation is cached:
var heightAtIndexPath = [NSIndexPath: CGFloat]()
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return heightAtIndexPath[indexPath] ?? UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
heightAtIndexPath[indexPath] = cell.frame.height
}
Use Extension
create UITableViewExtensions.swift and add following:
extension UITableView {
func reloadDataWithoutScroll() {
let offset = contentOffset
reloadData()
layoutIfNeeded()
setContentOffset(offset, animated: false)
}
}
In iOS 12.x, using Xcode 10.2.1, an easier option is.
UIView.performWithoutAnimation {
let loc = tableView.contentOffset
tableView.reloadRows(at: [indexPath], with: .none)
tableView.contentOffset = loc
}
This works better than following; it shakes at times when the row is not fully visible.
let contentOffset = self.tableView.contentOffset
self.tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(contentOffset, animated: false)
Swift 4.2 : Simple Solution
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 0
self.tableView.estimatedSectionHeaderHeight = 0
self.tableView.estimatedSectionFooterHeight = 0
}
//And then simply update(insert, reloadSections, delete etc) your tableView or reload
tableView.reloadData()
//or
UIView.performWithoutAnimation {
tableView.beginUpdates()
.....
tableView.endUpdates()
}
This code will prevent unnecessary animation and maintain the scroll view's content offset, it worked fine for me.
let lastScrollOffset = tableView.contentOffset
tableView.beginUpdates()
tableView.reloadData()
tableView.endUpdates()
tableView.layer.removeAllAnimations()
tableView.setContentOffset(lastScrollOffset, animated: false)
When you want to reload you have to
self.tableView.reloadData()
self.tableView.layoutIfNeeded()
and also use this UITableViewDelegate
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 'your maximum cell's height'
}
and your tableView will remain on the previous scroll position without scrolling
try to replace
reloadData with
tableView.reloadRows(at: tableView!.indexPathsForVisibleRows!, with: .none),
but you should be care about no cells, if no cells, this method should cause crash.
i wrote something that works perfect for me:
extension UIScrollView {
func reloadDataAndKeepContentOffsetInPlace(reloadData:(() -> Void)) {
let currentContentHeight = contentSize.height
if currentContentHeight == .zero {
reloadData()
return
}
reloadData()
layoutIfNeeded()
let newContentHeight = self.contentSize.height
DispatchQueue.main.async {
var contentOffset = self.contentOffset
contentOffset.y += newContentHeight - currentContentHeight
self.setContentOffset(contentOffset, animated: false)
}
}
}
use like this:
self.reloadSomeData()
collectionView.reloadDataAndKeepContentOffsetInPlace { [weak self] in
guard let self = self else { return }
self.collectionView.reloadData()
}
Try the following.
tableView.estimatedRowHeight = 0
tableView.estimatedSectionHeaderHeight = 0
tableView.estimatedSectionFooterHeight = 0
Source: https://developer.apple.com/forums/thread/86703

Resources