I want to make next:
I have an UITableView that have to dispaly words (A-Z).
Currently when view did load I have one cell that displayed (and this is correct). First cell display first word from my array words.
Purpose:
I want to move to the cell that must display 10 word from my array, but problem is that the cell with indexPath.row = 10 does not exist (and this correct, because I don't scroll yet).
What is a right wait to make transition from 1 to 10 cell.
I think if I don't use dequeueReusableCellWithIdentifier for creating my cell I can do it and solve my problem, but I mean this problem for device memory.
In other words I need to make scrollToRowAtIndexPath
Thanks!
You are right to identify the scrollToRowAtIndexPath method. So all you need to do is create a fresh IndexPath with the row you wish to move to, e.g., row index = 10:
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:10 inSection:indexPath.section]
atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
//For iOS 7
[self.TableView reloadData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:1 inSection:1];
[TableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
One can identify the row(in the eg. below it is forRow) that needs to be made visible and reload the row. Upon the completion of that one can scroll to that row.
let moveToIndexPath = NSIndexPath(forRow: forRow, inSection: 0)
self.tableView.reloadRowsAtIndexPaths([moveToIndexPath], withRowAnimation: .None)
self.tableView.scrollToRowAtIndexPath(moveToIndexPath, atScrollPosition: atScrollPosition, animated: true)
If you're trying to present a ÙITableViewController, calling tableView.reloadData() in viewDidLoad() or viewWillAppear() will cause noticeable UI lag before the presentation animation.
However, you will need to wait for the ÙITableView to have loaded its data and layed out its view, otherwise scrollToRow(at:, at:, animated:) will not work.
Here's the (slightly hacky) solution I came with up with for scrolling in a table view which is currently being presented:
private var needsToScroll = true
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if needsToScroll {
needsToScroll = false
DispatchQueue.main.async {
let indexPath = ...
tableView.scrollToRow(at: indexPath, at: .top, animated: false)
}
}
}
needsToScroll is needed because viewDidLayoutSubviews() will often be called more than once, which is an issue since scrollToRow(at:, at:, animated:) can result in viewDidLayoutSubviews() being called again.
Related
I have a UICollectionView that uses a flow layout. It has two sections. Based on some user input, I move cells from the first section to the second. To animate this change I use the following code:
let oldIndexPath = self.indexPathForAsset(asset)
self.collectionView.performBatchUpdates({
//change data source
let newIndexPath = self.indexPathForAsset(asset)
self.collectionView.moveItem(at: oldIndexPath, to: newIndexPath)
if self.numberOfCollectionViewSections() == 1 {
self.collectionView.deleteSections([0])
}
}, completion: nil)
the cells animate fine from the first second to the second, except for the last one. When the last cell is moved, I delete the section, and deleteSections is not animated. Also, if I pass a completion block to performBatchUpdates, it is not called.
How can I animate the move of the last cell to the second section and the deletion of the first section?
UIView.animate(withDuration: 4) {
self.collectionView.moveItem(at: oldIndexPath, to: newIndexPath)
}
try doing this
Can Somebody please tell me, How to show the middle element of array in the collection view when the app is launched and other elements are show in the left and right side of this middle element?
In viewDidLayoutSubviews method scroll your collection view to that particular IndexPath. And for do it for first time when launch use bool variable.
var isFirstTime = true
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if isFirstTime {
isFirstTime = false
let selectedIndex = IndexPath(item: 0, section: 0)
self.collectionView.scrollToItem(at: selectedIndex, at: .centeredHorizontally, animated: false)
}
}
You can use [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:(myArr.count/2) inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
Change the section in index path accordingly.
I have a TableView with dynamic cell size :
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 20
I have cells with texts inside and, of course, the text Height can change in function of its content.
Sometimes I have only one line, sometimes I have more, it means sometimes my cells does 20 height, sometimes more.
I have an issue when I try to reload my tableview datas and scroll to the top.
This is what I do :
myTableViewDatas = newDatas
tableView.setContentOffset(CGPoint.zero, animated: false)
tableView.reloadData()
It is hard to show you this case but It doesn't scroll to Y = 0, it scrolls to Y = 100 or something like that. Because my cell size changes in function of the content to display.
If I remove dynamic size and do :
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 20
}
And still scroll to the top with :
myTableViewDatas = newDatas
tableView.setContentOffset(CGPoint.zero, animated: false)
tableView.reloadData()
==> This is working, I scroll to Y = 0
I think I tried anything :
scrollToRow
scrollRectToVisible
scrollsToTop
I still have the issue.
The only way this is working is if I delay the reloadData :
myTableViewDatas = newDatas
tableView.setContentOffset(CGPoint.zero, animated: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
tableView.reloadData()
}
This is working but It creates a "glitch" => It displays new datas then automatically scroll to top, this is disturbing for the user.
The other solution is to use "reloadSections" :
myTableViewDatas = newDatas
tableView.setContentOffset(CGPoint.zero, animated: false)
tableView.reloadSections(IndexSet(integer: 0), with: .none) // I have only one section
It works too but it is also creating a "glitch", this is like TableView is reloaded with an animation (even if I set .none) where cells displayed are reduced / enlarged in function of new datas.
I really can't find a "proper" solution to do this, does anyone as already encountered this issue ? TY
Well, sometimes you search complicated solutions and It is simple in fact.
I did :
- reload first
- scrollToIndexPath 0 second
-> It works
myTableViewDatas = newDatas
tableView.reloadData()
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
I also have the same issue. [UITableView reloadData] doesn't work how we expected.
It doesn't layout tableView from the cleanslate.
Reloads everything from scratch. Redisplays visible rows. Note that this will cause any existing drop placeholder rows to be removed.
It says redisplays visible rows. So this is what I did.
data = nil; // clear data.
[tableView reloadData]; // reloadd tableView so make it empty.
[tableView setNeedsLayout]; // layout tableView.
[tableView layoutIfNeeded];
data = the data; // set the data you want to display.
[tableView reloadData]; // reload tableView
I'm trying to simulate a Whatsapp Chat any cell will have an image (for tail of the bubble), a bubble which is just View with color and some corner radius and a label which will represent the text of the message.
I've put a print before and after the call
self.messagesTableView.reloadData()
Once the after print is called tableView keeps some time doint I don't know what till the data is shown. And same happens with Insert row at indexpath, it takes some time till show the insert animation.
func displayMessages(viewModel: GroupChatMessages.GetChatMessages.ViewModel) {
let displayedMessage = viewModel.displayedMessages
print ("i'm here!")
messages = displayedMessage!
//self.messagesTableView.performSelectorOnMainThread(Selector("reloadData"), withObject: nil, waitUntilDone: true)
self.messagesTableView.reloadData()
print ("i'm here2!")
firstTime = false
self.setVisible(hiddenTableView: false, hiddenChatLoader: true)
self.scrollToLastMessage(false)
self.messagesLoaded = true
}
I've tried to do dispatched with queue, and the commented line before reloadData(), but nothings works and nothing represent a significative time.
Maybe could be for the image of the bubble? I don't know. I have this image saved on Assets, so I'm not downloading it from internet.
self.setVisible just hide the loader and show the tableView but I've tried too moving it up and nothings changes. Any further information you need let me know. Thanks!
EDIT:
Well I've seen that the problem comes from the scroll to last cell, this is where it takes the major part of the time.
func scrollToLastMessage(animated: Bool) {
let section = 0
let lastItemIndex = self.messagesTableView.numberOfRowsInSection(section) - 1
let indexPath:NSIndexPath = NSIndexPath.init(forItem: lastItemIndex, inSection: section)
self.messagesTableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: animated)
self.scrollDownButton.hidden = true
}
There is a posibility to optimize that scroll, because I have to do a Scroll because once the data is loaded, the first I've see is the top row of the tableView, but I would like to see the bottom one (last). Thanks!
methods like reloadData() should be considered as UI methods and it's mandatory to call them in main thread:
DispatchQueue.main.async { tableView.reloadData() }
It's better not to use reloadData() function unless a significant amount of cells need to refresh or data source has been changed instead use this method to add new rows:
tableView.insertRows(at: [IndexPath], with: UITableViewRowAnimation)
and for refreshing cell:
tableView.reloadRows(at: [IndexPath], with: UITableViewRowAnimation)
also if the cell has a considerable amount of images and rendering, use this code to make scrolling faster:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
// ADD THESE TWO LINE
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
}
Using these ways will boost loading speed significantly
Finally the solution that I've found to avoid dying while waiting scrolling to last element any single time, is swapping orientation of table
tableView.transform = CGAffineTransformMakeRotation(-M_PI);
cell.transform = CGAffineTransformMakeRotation(M_PI);
Now headerView and footerView are reversed. For exemple, if you would like insert rows at (visually) at the bottom of the TableView with this configuration you should add it at position 0 forRow: 0 atSection: "WhereYouAre". This way when you add new element, no scroll is needed, because scroll is automatically. Amazing and strange answer IMHO.
I've found this solution here:
Solution Link
#Christos Hadjikyriacou solved there.
My app has chat functionality and I'm feeding in new messages like this:
[self.tableView beginUpdates];
[messages addObject:msg];
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:messages.count - 1 inSection:1]] withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView endUpdates];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:messages.count - 1 inSection:1] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
However, my table view "jumps" weirdly when I'm adding a new message (either sending and receiving, result is the same in both):
Why am I getting this weird "jump"?
OK, I figured it out. As you say, the problem has to do with auto-sizing cells. I used two tricks to make things work (my code is in Swift, but it should be easy to translate back to ObjC):
1) Wait for the table animation to finish before taking further action. This can be done by enclosing the code that updates the table within a block between CATransaction.begin() and CATransaction.commit(). I set the completion block on CATransaction -- that code will run after the animation is finished.
2) Force the table view to render the cell before scrolling to the bottom. I do it by increasing the table's contentOffset by a small amount. That causes the newly inserted cell to get dequeued, and its height gets calculated. Once that scroll is done (I wait for it to finish using the method (1) above), I finally call tableView.scrollToRowAtIndexPath.
Here's the code:
override func viewDidLoad() {
super.viewDidLoad()
// Use auto-sizing for rows
tableView.estimatedRowHeight = 40
tableView.rowHeight = UITableViewAutomaticDimension
tableView.dataSource = self
}
func chatManager(chatManager: ChatManager, didAddMessage message: ChatMessage) {
messages.append(message)
let indexPathToInsert = NSIndexPath(forRow: messages.count-1, inSection: 0)
CATransaction.begin()
CATransaction.setCompletionBlock({ () -> Void in
// This block runs after the animations between CATransaction.begin
// and CATransaction.commit are finished.
self.scrollToLastMessage()
})
tableView.beginUpdates()
tableView.insertRowsAtIndexPaths([indexPathToInsert], withRowAnimation: .Bottom)
tableView.endUpdates()
CATransaction.commit()
}
func scrollToLastMessage() {
let bottomRow = tableView.numberOfRowsInSection(0) - 1
let bottomMessageIndex = NSIndexPath(forRow: bottomRow, inSection: 0)
guard messages.count > 0
else { return }
CATransaction.begin()
CATransaction.setCompletionBlock({ () -> Void in
// Now we can scroll to the last row!
self.tableView.scrollToRowAtIndexPath(bottomMessageIndex, atScrollPosition: .Bottom, animated: true)
})
// scroll down by 1 point: this causes the newly added cell to be dequeued and rendered.
let contentOffset = tableView.contentOffset.y
let newContentOffset = CGPointMake(0, contentOffset + 1)
tableView.setContentOffset(newContentOffset, animated: true)
CATransaction.commit()
}
Change UITableViewRowAnimationBottom to UITableViewRowAnimationNone and try
Try This!
UITableViewRowAnimation rowAnimation = UITableViewRowAnimationTop;
UITableViewScrollPosition scrollPosition = UITableViewScrollPositionTop;
[self.tableView beginUpdates];
[messages addObject:msg];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:rowAnimation];
[self.tableView endUpdates];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:YES];
// Fixes the cell from blinking (because of the transform, when using translucent cells)
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
For Swift 3 and 4
for scroll down to bottom of table View automatically when add new item in the table view just in tableView function add following line its works me
tableView.scrollToRow(at: IndexPath, at: .bottom, animated: true)
for example
in my case I have only one section so 0 is use for section and I have list of orderItems so for last index I use orderItems.count - 1
tableView.scrollToRow(at: [0, orderItems.count - 1], at: .bottom, animated: true)
I've just found out that on ios 11 this problem no longer exists. So there's no longer a content jump when adding a row to a table view and then scrolling to it with scrollToRow(at:) .
Also, on ios 10 calling scrollToRowAtIndexPath with animated=false fixes the content jump