CollectionView scroll with button tap scrollToItem not working - ios

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

Related

Is it possible to go to the specific page with PageControl Dots?

I face with a problem about UIPageControl dots. For instance, I made a banner with scrollview and I would like to travel in banner items by clicking dots of page control added below banner scroll view, but I couldn't find any proper option to go to the specific item by touching related page control dot.
Ex. There are 5 banner items and naturally 5 page control dots. Initially it stars from beginning and continues to the right one after another, how can I directly go to the 3. item by clicking 3. dot?
My banner and pagecontrol dots
This is my code for changing banner item with scrolling in scroll view:
pageControl.addTarget(self, action: #selector(pageControlTapped(sender:)), for: .touchUpInside)
#objc func pageControlTapped(sender: UIPageControl) {
let nextPage = self.pageControl.currentPage + 1
let newPage = nextPage < self.pageControl.numberOfPages ? nextPage : 0
let indexPath = IndexPath(row: newPage, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .left, animated: true)
}
I think you should use the "valueChanged" event instead of "touchUpInside" for pageControl.
Example:
pageControl.addTarget(self, action: #selector(pageControlTapped(sender:)), for: .valueChanged)
#objc func pageControlTapped(sender: UIPageControl) {
let nextPage = sender.currentPage
let indexPath = IndexPath(row: nextPage, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .left, animated: true)
}

Showing the last cell of Tableview

I want to show the bottom cells of my Tableview once the data are load I dont want to scroll every time to the bottom of Tableview to see the result
I have try this function in viewDidLoad but it did not work the page load at the top cells
if myArray.count > 0 {
tableView.scrollToRow(at: IndexPath(item:myArray.count - 1 , section: 0), at: .bottom, animated: false)
}
try use this
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [tableView] in
if myArray.count > 0 {
tableView.scrollToRow(at: IndexPath(item:myArray.count - 1 , section: 0), at: .bottom, animated: false)
}
}
You can leverage the usage of the scrollView's setContentOffset:animated: method
This answer should help you out: How to show last add cell on UITableview whileloading view or reloading tableview?

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.

UICollectionView scroll to item not working with horizontal direction

I have a UICollectionView within a UIViewController with paging enabled. For some strange reason collectionView.scrollToItem works when the direction of the collectionview is vertical but doesn't when direction is horizontal. Is this there something I'm doing wrong or is this supposed to happen?
//Test scrollToItem
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let i = IndexPath(item: 3, section: 0)
collectionView.reloadData()
collectionView.scrollToItem(at: i, at: .top, animated: true)
print("Selected")
}
For iOS 14
Apparently there is a new bug in UICollectionView that is causing scrollToItem to not work when paging is enabled. The work around is to disable paging before calling scrollToItem, then re-enabling it afterwards:
collectionView.isPagingEnabled = false
collectionView.scrollToItem(
at: IndexPath(item: value, section: 0),
at: .centeredHorizontally,
animated: true
)
collectionView.isPagingEnabled = true
Source: https://developer.apple.com/forums/thread/663156
For this part:
collectionView.scrollToItem(at: i, at: .top, animated: true)
When the scroll direction is horizontal you need to use at: left, at: right or at: centeredHorizontally. at: top is for vertical direction.
I had trouble implementing this in a flow layout with entered paging per item. The .centeredHorizontally just wouldn't work for me so i use scroll to rect and Check there is data before scrolling:
if self.collectionView?.dataSource?.collectionView(self.collectionView!, cellForItemAt: IndexPath(row: 0, section: 0)) != nil {
let rect = self.collectionView.layoutAttributesForItem(at: IndexPath(item: data[index], section: 0))?.frame
self.collectionView.scrollRectToVisible(rect!, animated: false)
}
Swift 5.1, Xcode 11.4
collectionView.scrollToItem(at: IndexPath(item: pageNumber , section: 0), at: .centeredHorizontally, animated: true)
self.collectionView.setNeedsLayout() // **Without this effect wont be visible**
I try a lot of things and fail. This saves my day.
func scrollToIndex(index:Int) {
let rect = self.collectionView.layoutAttributesForItem(at: IndexPath(row: index, section: 0))?.frame
self.collectionView.scrollRectToVisible(rect!, animated: true)
}
Reference: https://stackoverflow.com/a/41413575/9047324
I have this issue: when button tapped (for horizontal collection scroll to next item) it always returns to first item with UI bug.
The reason was in this parameter: myCollection.isPagingEnabled = true
Solution: just disable paging before scroll to next item, and enable it after scroll.
After adding items to collectionView and reloadData(), scrollToItem was not working because reloading data has not finished yet.
Because of this I added performBatchUpdates like this :
self.dataSource.append("Test")
self.collectionView.performBatchUpdates ({
self.collectionView.reloadData()
}, completion: { _ in
self.collectionView.scrollToItem(at: IndexPath(item: 3, section: 0),
at: .centeredHorizontally,
animated: false)
})
I know it's not about this question but it will be helpful for this title.
For me, I had to scroll collection view on main thread like:
DispatchQueue.main.async {
self.collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
}
This will not work if you call scrollToItem before viewDidLayoutSubviews. Using scrollToItem in viewDidLoad will not work. So call this function after viewDidLayoutSubviews() is completed.
For iOS 14+
It's so stupid but it works.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.collectionView.scrollToItem(at: i, at: .top, animated: true)
}
Swift 3:
This worked for me on horizontal collection view.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//Show 14th row as first row
self.activityCollectionView?.scrollToItem(at: IndexPath(row: 14, section: 0), at: UICollectionViewScrollPosition.right, animated: true)
}
You can choose whether the desired cell at index to be on right or left on scroll position.
UICollectionViewScrollPosition.right or UICollectionViewScrollPosition.left.
Swift 5 if with animation horizontally
func scrollToIndex(index:Int) {
self.collectionView?.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
}
Your example:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
print("Selected: \(indexPath.row)")
collectionView.reloadData()
}
As #Dheeraj Agarwal points out, none of the 'ScrollTo' functionality of UICollectionView OR UIScrollView will seem to work properly if it is called before the view has been laid out. To be accurate, I think they WORK, but the effects are immediately nullified because laying out a UICollectionView causes it to reset to its minimum bounds, probably because of all the cell layout functions that will trigger, and the fact its content size may change.
The solution is to make sure this function is called after layout occurs, but it's not that simple. It's entirely likely that a collection view may be told to layout its content again and again in response to various changes - setting delegates, the contents updating, the view controller being added to a parent and therefore changing size. Each time this happens it'll reset to 0:0 offset.
You'll therefore have to keep a reference to the desired offset / cell index / frame until such a time as you are CERTAIN there will be no more unexpected layout updates. You can't just nil it out immediately as your collection view's layout might change multiple times before the view appears. I'm currently storing a frame in an attribute and calling the function in layoutFrames every time (my collection view's parent is a custom view, not a view controller). Although this has the slightly annoying feature of scrolling back again if the user rotates their phone, I consider it acceptable since this is a custom keyboard and most users will work with it in one orientation or the other, they won't keep flipping their phone around just to select a single value.
Solutions like calling DispatchQueue.main.asyncAfter are fragile. They work because the function call gets delayed until after the first layout occurs, but this may not always-and-forever solve the problem.
I guess the 'Scroll To' functions were only ever intended to be used in response to direct user input after the collection view is already populated.
CollectionView.isPagingEnabled = false
CollectionView.scrollToItem(at:IndexPath(item: YourcellIndex(4), section: at which section you want cell(0)), at: .centeredHorizontally, animated: false)
CollectionView.layoutSubviews()
CollectionView.isPagingEnabled = true
sometimes you can set
collectionView.collectionlayout.invalidate()
collectionView.scrollToItem(row:2,section:0)
If you have networking which can make disturb main thread, you have to avoid or after finish that, have to apply this code.
for example
self.group.enter()
some thread
self.group.leave()

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