We have a collection view. I have made a calendar which shows dates horizontally & I scroll dates horizontally. Now on screen I see only 3-4 dates. Now I want to auto scroll to a particular selected date when calendar screen I shown.So the date I want to scroll to is not visible yet.
For that I got the indexpath for particular cell. Now I am trying to scroll it to particular indexpath.
func scrollCollectionView(indexpath:IndexPath)
{
// collectionView.scrollToItem(at: indexpath, at: .left, animated: true)
//collectionView.selectItem(at: indexpath, animated: true, scrollPosition: .left)
collectionView.scrollToItem(at: indexpath, at: .centeredHorizontally, animated: true)
_ = collectionView.dequeueReusableCell(withReuseIdentifier: "DayCell", for: indexpath) as? DayCell
}
Please tell how can I implement it?
In viewDidLoad of your controller, you can write the code for scrolling to a particular index path of collection view.
self.collectionView.scrollToItem(at:IndexPath(item: indexNumber, section: sectionNumber), at: .right, animated: false)
In Swift 4, this worked for me:
override func viewDidLayoutSubviews() {
collectionView.scrollToItem(at:IndexPath(item: 5, section: 0), at: .right, animated: false)
}
placing it anywhere else just resulted in weird visuals.
Note - an edit was made to my original post just now which wanted to change viewDidLayoutSubviews() to viewDidAppear(). I rejected the edit because placing the function's code anywhere else (including viewDidAppear) didn't work at the time, and the post has had 10 upvots which means it must have been working for others too. I mention it here in case anyone wants to try viewDidAppear, but I'm pretty sure I would have tried that and wouldn't have written "placing it anywhere else just resulted in weird visuals" otherwise. It could be an XCode update or whatever has since resolved this; too long ago for me to remember the details.
You can use this
self.collectionView.scrollToItem(at:IndexPath(item: index, section: 0), at: .right, animated: false)
I used your answer #PGDev but I put it in viewWillAppear and it works well for me.
self.collectionView?.scrollToItem(at:IndexPath(item: yourIndex, section: yourSection), at: .left, animated: false)
tested this
func scrollToIndex(index:Int) {
let rect = self.collectionView.layoutAttributesForItem(at:IndexPath(row: index, section: 0))?.frame
self.collectionView.scrollRectToVisible(rect!, animated: true)
}
In viewDidLoad of your controller, you can write the code
self.myCollectionView.performBatchUpdates(nil) { (isLoded) in
if isLoded{
self.myCollectionView.scrollToItem(at: self.passedContentOffset, at: [.centeredVertically,.centeredHorizontally], animated: false)
}
}
I have tried all, but for me, it only works with adding one extra line
self.collectionView.scrollToItem(at:IndexPath(item: index, section: 0), at: .right, animated: false)
collectionView.layoutSubviews()
you can call it anywhere.
Just add this:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
yourcollectionView.scrollToItem(at:IndexPath(item: passedIndex ?? 0, section: 0), at: .centeredVertically, animated: false)
}
Related
I'm building an app where rows containing messages are inserted at the end of a table
messages.append(message)
let indexPath:IndexPath = IndexPath(row:(messages.count - 1), section:0)
tableView.insertRows(at: [indexPath], with: .none)
I then scroll to the bottom of the table
DispatchQueue.main.async {
let indexPath = IndexPath(
row: self.numberOfRows(inSection: self.numberOfSections - 1) - 1,
section: self.numberOfSections - 1)
self.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
Like other messaging apps, I would like to modify this so that the autoscrolling only happens if you're already scrolled at the end of the table instead of every time a new message gets inserted.
I've tried several techniques like detecting if the last cell is full visible https://stackoverflow.com/a/9843146/784637, or detecting when scrolled to the bottom https://stackoverflow.com/a/39015301/784637.
However my issue is that because scrollToRow sets animated:true, if a new message comes in but the previous message which came a split second before is still being scrolled down to via scrollToRow, then the autoscrolling to newest message and subsequent messages doesn't occur - ex. the last cell won't be fully visible until the animation is complete, or detecting if you're scrolled to to the bottom will be false until the animation is complete.
Is there any way I can get around this without setting animated: false?
What I would do is insert the row in a batch operation to make use of its completion handler, which serializes the UI update. Then I would check to see which rows are visible to the user and if the last couple of rows are, I think the user is close enough to the bottom of the conversation to force a down scroll.
let lastRow = IndexPath(row: messages.count - 1, section: 0)
DispatchQueue.main.async {
self.tableView.performBatchUpdates({
self.tableView.insertRows(at: [lastRow], with: .bottom)
}, completion: { (finished) in
guard finished,
let visiblePaths = self.tableView.indexPathsForVisibleRows else {
return
}
if visiblePaths.contains([0, messages.count - 2]) || visiblePaths.contains([0, messages.count - 1]) { // last 2 rows are visible
DispatchQueue.main.async {
self.tableView.scrollToRow(at: lastRow, at: .bottom, animated: true)
}
}
})
}
Yes, there are many similar/equal questions, but none worked for me.
I've a UITableView with some sections. The rows of these sections are created conform user put information in the table, so I want to auto-scroll when user type a especific field, but I don't know which IndexPath I must use to use the function:
UITableView.scrollToRow(at: IndexPath, at: UITableViewScrollPosition.middle, animated: true )
The problem is that I've section without rows, so I can't use the function above without especific a row in IndexPath. (In this case, the section without rows is the 3rd).
I already tried this:
let sectionRect : CGRect = tableViewOrder.rect(forSection: 3)
tableViewOrder.scrollRectToVisible(sectionRect, animated: true)
and
tableViewOrder.scrollToRow(at: IndexPath(row: NSNotFound, section: 3), at: UITableViewScrollPosition.middle, animated: true)
Both not works.
You can try
tableViewOrder.contentOffset = CGPoint(x:0,y: tableViewOrder.contentSize.height - tableViewOrder.frame.height)
Currently, I have tried
func scrollToBottom(){
DispatchQueue.global(qos: .background).async {
let indexPath = IndexPath(item: self,messageArrat.count-1, section: 0)
self.messageTableView.scrollToRow(at: indexPath,at:.bottom,animated:true)
}
}
However it does not seem to work, outputting
Thread 16: ESC_BAD_ACCESS (code=1, address=0xfffffffffffffffc)
UITableView.scrollToRow(at:at:animated:) must be used from main thread only
I call the function right after setting delegate and datasource in viewdidload.
Sounds like a timing issue, you said you are calling this from the viewDidLoad, try moving it to the viewDidAppear or viewDidLayoutSubviews instead. Also remove from background thread as you are manipulating the UI and that needs to happen on the main thread.
First of all: please provide your code as text not as Image.
Now to your problem, you are trying to scroll to the last row in a global queue, but all UI-Updates must happen in the Main Thread, so simply remove the DispatchQueue, or if you are not in the main thread, you can write DispatchQueue.main instead of global
E.g.:
DispatchQueue.main.async {
//code Here
}
But i think you doesnt need the DispatchQueue at all so try to omit it completely.
Writing code in ".async" closure you starting a new thread, and the error message literally tells you do not do that.
Delete DispatchQueue stuff and that's it.
And please post text, description and all that stuff to help community.
You don't need to add DispatchQueue:
let indexPath = IndexPath(item: self.messageArrat.count-1, section: 0)
self.messageTableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
And if you want DispatchQueue then add main queue like that:
DispatchQueue.main.async {
let indexPath = IndexPath(item: self.messageArrat.count-1, section: 0)
self.messageTableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
May be index path must be out of bound, try checking number of rows before scrolling . Also do this all oprations on main thread.
It likes to work on this main thread and not in the background because it is related to the UI
try this code:
func scrollToBottom() {
DispatchQueue.main.async {
let indexPath = IndexPath(item: self.messageArrat.count-1, section: 0)
self.messageTableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
I want to scroll to a given index (self.boldRowPath), but when I debug scrollToRow is performed before reloadData().
How to know reloadData has finished ?
func getAllTimeEvent() {
self.arrAllTimeEvent = ModelManager.getInstance().getAllTimeEvent(from: self.apportmentDateFrom, to: self.apportmentDateTo)
self.tblTimeEvent.reloadData()
self.tblTimeEvent.scrollToRow(at: self.boldRowPath ?? [0,0], at: .top, animated: true)
}
Any help will be much appreciated.
Try doing this, it should work:
self.tblTimeEvent.reloadData()
DispatchQueue.main.async(execute: {
self.tblTimeEvent.scrollToRow(at: self.boldRowPath ?? [0,0], at: .top, animated: true)
})
This will execute the scrollToRow on the main thread, that means after the reloadData is done (because it is on the main thread)
As explained in this answer, the reload of the UITableView happens on the next layout run (usually, when you return control to the run loop).
So, you can schedule your code after the next layout by using the main dispatch queue. In your case:
func getAllTimeEvent() {
self.arrAllTimeEvent = ModelManager.getInstance().getAllTimeEvent(from: self.apportmentDateFrom, to: self.apportmentDateTo)
self.tblTimeEvent.reloadData()
DispatchQueue.main.async {
self.tblTimeEvent.scrollToRow(at: self.boldRowPath ?? [0,0], at: .top, animated: true)
}
}
You can also force the layout by manually calling layoutIfNeeded. But this is generally not a good idea (the previous option is the best):
func getAllTimeEvent() {
self.arrAllTimeEvent = ModelManager.getInstance().getAllTimeEvent(from: self.apportmentDateFrom, to: self.apportmentDateTo)
self.tblTimeEvent.reloadData()
self.tblTimeEvent.layoutIfNeeded()
self.tblTimeEvent.scrollToRow(at: self.boldRowPath ?? [0,0], at: .top, animated: true)
}
you can make sure reload is done using...
In Swift 3.0 + we can create a an extension for UITableView with a escaped Closure like below :
extension UITableView {
func reloadData(completion: #escaping () -> ()) {
UIView.animate(withDuration: 0, animations: { self.reloadData()})
{_ in completion() }
}
}
And Use it like Below where ever you want :
Your_Table_View.reloadData {
print("reload done")
}
hope this will help to someone. cheers!
You can make a pretty solid assumption that a UITableView is done reloading when the last time tableView(_:willDisplay:forRowAt:) is called for the visible sections on the screen.
So try something like this (for a tableView where the rows in section 0 take up the available space on the screen):
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastRowIndex = tableView.numberOfRows(inSection: 0)
if indexPath.row == lastRowIndex - 1 {
// tableView done reloading
self.tblTimeEvent.scrollToRow(at: self.boldRowPath ?? [0,0], at: .top, animated: true)
}
}
You can use CATransaction for that
CATransaction.begin()
CATransaction.setCompletionBlock {
// Completion
}
tableView.reloadData()
CATransaction.commit()
tableView.reloadData { [weak self] in
self?.doSomething()
}
I'd like to visually scroll through my whole tableView. I tried the following, but it doesn't seem to perform the scrolling. Instead it just runs through the loops. I inserted a dispatch_async(dispatch_get_main_queue()) statement, thinking that that would ensure the view is refreshed before proceeding, but no luck.
What am I doing wrong?
func scrollThroughTable() {
for sectionNum in 0..<tableView.numberOfSections() {
for rowNum in 0..<tableView.numberOfRowsInSection(sectionNum) {
let indexPath = NSIndexPath(forRow: rowNum, inSection: sectionNum)
var cellTemp = self.tableView.cellForRowAtIndexPath(indexPath)
if cellTemp == nil {
dispatch_async(dispatch_get_main_queue()) {
self.tableView.scrollToRowAtIndexPath(indexPath!, atScrollPosition: .Top, animated: true)
self.tableView.reloadData()
}
}
}
}
}
I found a solution. Simply use scrollToRowAtIndexPath() with animation. To do so I had to create a getIndexPath() function to figure out where I want to scroll. Has more or less the same effect as scrolling through the whole table if I pass it the last element of my tableView.
If you want it to happen slower with more scrolling effect, wrap it inside UIView.animateWithDuration() and play with 'duration'. You can even do more animation if you want in its completion block. (No need to set an unreliable sleep timer, etc.)
func animateReminderInserted(toDoItem: ReminderWrapper) {
if let definiteIndexPath = indexPathDelegate.getIndexPath(toDoItem) {
self.tableView.scrollToRowAtIndexPath(definiteIndexPath, atScrollPosition: .Middle, animated: true)
}
}