How to scroll to a section without rows in UITableView? - ios

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)

Related

How to scroll to newly inserted row only when already scrolled to bottom of the table?

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)
}
}
})
}

UITableView Scrolls automatically while textfield begins editing

I am developing an iOS app which has different forms which is populated into a UITableview based on users selection. Each form has different fields like Textfield, DatePicker, PickerView. So I used a single TableViewCell (nib) to hold all these and show or hide the items based on question.
There is save function defined which will save values when user enters to an array.
My issue is, at times my tableview scrolls as if the index goes out of control. like when I select any textfield, Tableview scrolls to top. I have removed all keyboard observer methods and checked, still it is happening.
Below is my save function code:
func saveFormValue(mystr: String) {
//selectedIndex is a global variable (NSIndexPath)
let dict = sections[selectedIndex!.section].questions![selectedIndex!.row]
dict.answer = mystr
sections[selectedIndex!.section].questions![selectedIndex!.row] = dict
let answer = updated_answer.init(ID: ID, form_id: selected_form_id, form_name: formName, section_id: dict.section_id ?? "",question_id: dict.question_id ?? "", question_name: dict.question_name!,question_type:dict.question_type!)
updatedArray.append(answer)
self.tableView.reloadData()
}
This is the code in textfieldDidBeginEditing function (how selectedIndexPath is initialized):
guard let index = tableView.indexPath(for: cell) else {
return
}
selectedIndex = index as NSIndexPath
I have added delegate for cell, and one thing I noticed is, this issue is happening whenever I press pickerview or datepicker once. I couldn't see this issue If I only touch textField cells only.
Please let me know for any further details.
Try this code hope this helps to you.
if let thisIndexPath = tableView.indexPath(for: cell) {
tableView.scrollToRow(at: thisIndexPath, at: .top, animated: false)
}
On textfield delegate method textFieldDidBeginEditing use the following code:
func textFieldDidBeginEditing(_ textField: UITextField) {
let indexParh = NSIndexPath(row: textField.tag, section: 0)
self.constTBL_Bottom.constant = 260
self.tblViewObj.scrollToRow(at: indexParh as IndexPath, at: .middle, animated: false)
}
Also you need to manage the table bottom constant. When you resigning your keyboard set table view constant to 0
Hope this will work :)

Reload collectionview cells and not the section header

When trying to create collapsible UICollectionView sections, I update the number of items in the section dependent on its state. However, doing it this way, I reload the section which also reloads the section header aswell, and I get a very weird behavior when animating my image in the section header.
Essentially, reloading the section header when changing section items enables the UICollectionView to update the items but the section animate looks and behaves strange.
Without calling reloadSection, it allows for the proper animation but the items do not load.
self?.collectionView?.performBatchUpdates({
let indexSet = IndexSet(integer: section)
self?.collectionView?.reloadSections(indexSet)
}, completion: nil)
What is the fix for this?
You may try to extract the sequence of IndexPath in a specific section, then call reloadItems on such sequence, doing so:
extension UICollectionView {
func reloadItems(inSection section:Int) {
reloadItems(at: (0..<numberOfItems(inSection: section)).map {
IndexPath(item: $0, section: section)
})
}
}
so your code might be something like:
var updateSection = 0 // whatever
collectionView.performBatchUpdates({
// modify here the collection view
// eg. with: collectionView.insertItems
// or: collectionView.deleteItems
}) { (success) in
collectionView.reloadItems(inSection: updateSection)
}

Scroll to last message on collection view is not working properly

I have a CollectionView that has an input accesory view at the bottom containing a text field and other elements. The app works like a messaging app: you write something and then you send it, at which point the message is added to the collection view.
The problem is that, with the code I've implemented, the "auto scroll" stars working when there is a certain number of messages (items in the collection view), 16-18 is the amount of items you need to add before the auto-scroll stars to work. This is the code I've implemented so far:
DispatchQueue.main.async {
self.collectionView?.reloadData()
self.inputTextField.text = ""
if self.messages.count > 0 {
let indexpath = IndexPath(item: self.messages.count - 1, section: 0)
self.collectionView?.scrollToItem(at: indexpath, at: .bottom, animated: true)
}
}
That code is in the textFieldShouldReturn method.
This is what I get when I put a breakpoint in the line "self.collectionView?..."

How to scroll to a particluar index in collection view in swift

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)
}

Resources