UICollectionView scrollRectToVisible and scrollToItem break user scroll - ios

I am using this code to reveal a specific item in a collection view.
var pages: [PDFPage]?
var currentPage: PDFPage?
override func viewDidLayoutSubviews() {
// Scroll to currently opened page
if let current = currentPage, let idx = pages?.firstIndex(of: current) {
collectionView?.scrollToItem(at: IndexPath(item: idx, section: 0), at: .centeredVertically, animated: false)
}
}
It is working as expected but it completely locks user scrolling in order, I guess, to always keep the item in the requested position.
The same happens when using scrollRectToVisible.
Instead I'd like it to only move to the item, and then let the user scroll wherever he wants. Which is the behaviour I'm getting with UITableViewController's scrollToRow.
Am I missing something?

Turns out calling the scroll in viewDidLayoutSubviews was the issue, because it was continuously repeated and preventing any further scroll. I solved the issue like this:
var currentPage: PDFPage?
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Scroll to currently opened outline
if let current = currentPage, let idx = pages?.firstIndex(of: current) {
collectionView?.scrollToItem(at: IndexPath(item: idx, section: 0),
at: .top,
animated: false)
// In order to only run the scroll once, without locking the view
currentPage = nil
}
}

Related

CollectionView scroll with button tap scrollToItem not working

I currently have a login and signup buttons labeled as 1 and 2. And underneath that, I have a collectionView with two cells (one for the login UI and one for register UI). When I click on the signup button(Label 2), I want to move the collectionView to the next cell.
I tried using the scrollToItem, but it does not do anything. Anyone know why?
#objc private func didTapRegisterButton() {
let i = IndexPath(item: 1, section: 0)
self.onboardingCollectionView.scrollToItem(at: i, at: .right, animated: true)
}
Figured it out. My collectionView had isPagingEnabled property as true. The scrollToItem doesn't work if that is set to true.
If you want to scroll between two cells on button click then first you need to uncheck(disable) Scrolling Enabled property and check(enable) Paging Enabled property of collectionView. (With this approach user can't scroll manually with swipe)
On button first action,
if self.currentIndexPath == 0 {
return
} else {
self.currentIndexPath += 1
self.yourCollectionView.scrollToItem(at: IndexPath(row: self.currentVisibleIndexPath, section: 0), at: .centeredHorizontally, animated: true)
}
On button second action,
if self.currentIndexPath == 1 {
self.currentIndexPath -= 1
self.yourCollectionView.scrollToItem(at: IndexPath(row: self.currentVisibleIndexPath, section: 0), at: .centeredHorizontally, animated: true)
} else {
return
}
And declare global variable
var currentIndexPath: Int = 0

Swift segmented control and UITableView

I have segmented control as a header of UITableView with cell names. Tableview has different cells with different sizes. When I tap on segment, I do a scroll to specific row in a tableview.
segment.valueSelected = { index in
self.contentTableView.scrollToRow(at: IndexPath(row: index, section: 1), at: .top, animated: true)
}
But how can I change selected index of segmented control when I scroll my tableview? I mean I know how to change index, but the problem is how can I calculate specific offset since all cells has different sizes?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
segment.setSelectIndex(index : ???)
}
Solved by
let visiblerows = contentTableView.indexPathsForVisibleRows
if let lastIndex = visiblerows?.last {
segment.setSelectIndex(index: lastIndex.row, animated: true)
}

How to show the middle element of array in the collection view when the app is launched?

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.

AutoScroll to next/previous cell on swipe

I created a horizontal tableView, with cells that take up the whole view controller. Instead of scrolling with the default setting, I would like to scroll to the next cell using scrollView.scrollToItem(at: IndexPath method.
Meaning, each time the user scrolls right, it will automatically scroll to the next cell, and each time the user swipes left, it will automatically scroll to the previous cell. Whenever you scroll a direction, it should automatically scroll to the next or previous cell.
I tried intercepting it using scrollViewDidScroll delegate method, but I am running into a ton of problems, it is autoscrolling back and forth and glitching a ton. What am I doing wrong?
var previousOffset: CGFloat = 0.0
var allowHorizontalScroll: Bool = true
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (allowHorizontalScroll){
let offset = scrollView.contentOffset.x
let diff = previousOffset - offset
previousOffset = offset
var currentBoardIndex = scrollView.indexPathForItem(at: CGPoint(x:offset, y:10))?.item
if currentBoardIndex != nil {
if diff > 0 {
//print("scroll left")
if (currentBoardIndex != 0){
currentBoardIndex = currentBoardIndex! - 1
allowHorizontalScroll = false
scrollView.scrollToItem(at: IndexPath(row: (currentBoardIndex!), section: 0), at: .left, animated: true)
}
}else{
//print("scroll right")
if (currentBoardIndex != ((boardVCDataSource?.fetchedResultsController.fetchedObjects?.count)! - 1)){
currentBoardIndex = currentBoardIndex! + 1
allowHorizontalScroll = false
scrollView.scrollToItem(at: IndexPath(row: (currentBoardIndex!), section: 0), at: .left, animated: true)
}
}
}
}
}
Whenever you scroll a direction, it should automatically scroll to the next or previous cell.
Why not throw out all that code, and instead set your scroll view's isPagingEnabled to true? A paging scroll view does what you describe, automatically.

Scrolling bottom in tableview and performance

I need to scroll to bottom. I have a chat app like whatsapp. So when view appears table view should show last row. I am achiving this with following line and works nice.
tableView.setContentOffset(CGPointMake(0, CGFloat.max), animated: false)
Also I need to scroll to bottom when keyboard appears. I am using Auto Layout and above line is not working. For to do this i am using following line:
func scrollToLastRow(animated: Bool) {
if self.numberOfRowsInSection(0) > 0 {
self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0) - 1, inSection: 0), atScrollPosition: .Bottom, animated: animated)
}
}
This is a extension for Tableview.
This solution is working fine when there is no too much message. Then I tried with 5000 messages (so tableview have 5000 rows, but i am paging them) And when keyboard appears i see cpu usage is %98-100. I think the second code is problem for pagination, it causes loading every message to ram and my app freezes and receiving ram warning.
How to scroll to bottom without any performance issue?
If you have pagination, you can try to only load your current page as well as the final page, assuming you have 20 messages in each page, in this case your table have 40 rows only. Then you can use your function:
func scrollToLastRow(animated: Bool) {
if self.numberOfRowsInSection(0) > 0 {
self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0) - 1, inSection: 0), atScrollPosition: .Bottom, animated: animated)
}
}
try this method :
let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
let numberOfSections = self.tableView.numberOfSections
let numberOfRows = self.tableView.numberOfRowsInSection(numberOfSections-1)
if numberOfRows > 0 {
let indexPath = NSIndexPath(forRow: numberOfRows-1, inSection: (numberOfSections-1))
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)
}
})
If your are creating IM app. I recommend you reverse the tableView.
So the first row appears at bottom, and don't needs scrolling at beginning anymore.
Here is a cocoapod could help: https://github.com/marty-suzuki/ReverseExtension
If you stil want to scroll to specific row
implement the UIScrollViewDelegate.scrollViewDidEndScrollingAnimation.
and goes like this.
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
if scrollView.contentSize.height > lastContentHeight {
self.tableView.scrollToBottom()
self.lastContentHeight = scrollView.contentSize.height
}
}

Resources