I used two collection views and they are connected to each other for scrolling. if one scroll the other one will scroll too.
this is handling in my didScroll delegate function as below:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var screenHeight = max(Int(UIScreen.mainScreen().bounds.width), Int(UIScreen.mainScreen().bounds.height))
if ( collectionView == self.bottomSliderCollectionView)
{
return CGSizeMake(self.sliderCollectionView.frame.width - 65, 50)
}else {
return CGSizeMake(self.sliderCollectionView.frame.width , self.sliderCollectionView.frame.height)
}
}
func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView == self.sliderCollectionView ){
// Calculate where the collection view should be at the right-hand end item
var contentOffsetWhenFullyScrolledRight = self.sliderCollectionView.frame.size.width * CGFloat(self.workingMenuSlider.count - 1)
if (scrollView.contentOffset.x == contentOffsetWhenFullyScrolledRight ) {
// user is scrolling to the right from the last item to the 'fake' item 1.
// reposition offset to show the 'real' item 1 at the left-hand end of the collection view
var newIndexPath = NSIndexPath(forItem: 1, inSection: 0)
self.sliderCollectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
} else if (scrollView.contentOffset.x == 0) {
// user is scrolling to the left from the first item to the fake 'item N'.
// reposition offset to show the 'real' item N at the right end end of the collection view
var newIndexPath = NSIndexPath(forItem: self.workingMenuSlider.count - 2, inSection: 0)
self.sliderCollectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
}
}
if (scrollView.contentOffset.x < 0) {
var newIndexPath = NSIndexPath(forItem: self.workingMenuSlider.count - 2, inSection: 0)
self.sliderCollectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
self.bottomSliderCollectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
}
var contentOffsetWhenFullyScrolledRight = self.sliderCollectionView.frame.size.width * CGFloat(self.workingMenuSlider.count - 1)
if (scrollView.contentOffset.x > contentOffsetWhenFullyScrolledRight) {
var newIndexPath = NSIndexPath(forItem: 1, inSection: 0)
self.sliderCollectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
self.bottomSliderCollectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
}
if (self.sepratedScroll == true ) {
if (scrollView == self.sliderCollectionView ){
var nesbat = (self.bottomSliderCollectionView.frame.width - 65) / self.sliderCollectionView.frame.width
self.sepratedScroll = false
self.bottomSliderCollectionView.contentOffset.x = self.sliderCollectionView.contentOffset.x * nesbat
self.sepratedScroll = true
}else {
self.sepratedScroll = false
var nesbat = (self.bottomSliderCollectionView.frame.width - 65) / self.sliderCollectionView.frame.width
self.sliderCollectionView.contentOffset.x = (self.bottomSliderCollectionView.contentOffset.x ) / nesbat
self.sepratedScroll = true
}
} else {
}
}
as you see var nesbat is calculating a float which is because my bottomCollection View cell width is - 65 than the sliderCollectionView.
both of them set to paging scroll. the top one (SliderCollectionView) works well. and it is an infinite scrolling which has the duplicate value in my array of data.
so, my question here is when my bottomCollectionview slides, which is a paging scroll, it goes more than its cell width.
i want to scroll exactly the width of the cell in bottomCollectionView.
i tested the answers which are saying to handle it on didScroll View but it is ok for my case. I have too many if to handle the infinite scrolling and also the connected collection views.
please help me with the paging scroll to set on the cells width not the width of the whole collectionView.
thankx
okay i got it myself.
i change my top collection view height and move it over the bottom collection view. but the content in xib for collection view bottom constraints changed to + 50 .
so i have a collection view on top with its content is - 50 than the height of the collection view and it is over the bottomCollectionView.
so i have the suer interaction of my top one .
problem solved :D :)
Related
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
I am using swift 3 to write an app which has a UIViewController looks like the following picture. It contains a horizontal-scrollable collectionView (the blue block) on the top, and its functionality is to switch between different labels(first, prev, current, next, last...etc, the total amount of labels are not fixed). What I need to achieve is that on scrolling (or panning) the collection view, the 'NEXT' or 'PREV' should move (and anchor automatically) to the center with velocity similar to paging animation. How could I achieve this? Currently I use the Paging Enabled attribute of the scrollView, however this only work when there is one label in the window.
Similar features may look like the middle "albums" section of iTunes app, which the collectionView cell would scroll to some pre-defined point automatically with some animation once it detects any swipe/pan gesture.
according to your comment if you want to do the Tap to select thing there are 2 things you need to do
1- Disable Collection View Scrolling in viewDidLoad
collectionView.isScrollingEnabled = false
2- Display Selected Cell in Center , add this in your didSelectItemAtIndexPath method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collection.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizantally)
let cell = collectionView.cellForItem(at : indexPath) as! YourCollectionViewCellClass
cell.titleLable.font = UIFont.boldSystemFont(ofSize : 20)
}
Thanks to Raheel's answer here, I finally find a way for a desirable scroll effect. Some key functions are listed as follow in swift:
First, disable Paging Enabled attribute of the scrollView
Define setIdentityLayout() and setNonIdentityLayout() which defines the layouts of both selected / non-selected cell (for example, MyCollectionViewCell in this case)
Define some related properties such as:
lazy var COLLECTION_VIEW_WIDTH: CGFloat = {
return CGFloat(self.collectionView.frame.size.width / 2)
}()
fileprivate let TRANSFORM_CELL_VALUE = CGAffineTransform(scaleX: 1, y: 1) // Or define other transform effects here
fileprivate let ANIMATION_SPEED = 0.2
implement the following key methods for UIScrollViewDelegate:
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
// Do tableView data refresh according to the corresponding pages here
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Scroll to corresponding position
let pageWidth: Float = Float(COLLECTION_VIEW_WIDTH) // width + space
let currentOffset: Float = Float(scrollView.contentOffset.x)
let targetOffset: Float = Float(targetContentOffset.pointee.x)
var newTargetOffset: Float = 0
if targetOffset > currentOffset {
newTargetOffset = ceilf(currentOffset / pageWidth) * pageWidth
}
else {
newTargetOffset = floorf(currentOffset / pageWidth) * pageWidth
}
if newTargetOffset < 0 {
newTargetOffset = 0
}
else if (newTargetOffset > Float(scrollView.contentSize.width)){
newTargetOffset = Float(Float(scrollView.contentSize.width))
}
targetContentOffset.pointee.x = CGFloat(currentOffset)
scrollView.setContentOffset(CGPoint(x: CGFloat(newTargetOffset), y: scrollView.contentOffset.y), animated: true)
// Set transforms
let identityIndex: Int = Int(newTargetOffset / pageWidth)
var cell = delegate!.roomCollections.cellForItem(at: IndexPath.init(row: identityIndex, section: 0)) as? MyCollectionViewCell
UIView.animate(withDuration: ANIMATION_SPEED, animations: {
cell?.transform = CGAffineTransform.identity
cell?.setIdentityLayout()
})
// right cell
cell = delegate!.roomCollections.cellForItem(at: IndexPath.init(row: identityIndex + 1, section: 0)) as? MyCollectionViewCell
UIView.animate(withDuration: ANIMATION_SPEED, animations: {
cell?.transform = self.TRANSFORM_CELL_VALUE
cell?.setNonIdentityLayout()
})
// left cell, which is not necessary at index 0
if (identityIndex != 0) {
cell = delegate!.roomCollections.cellForItem(at: IndexPath.init(row: identityIndex - 1, section: 0)) as? MyCollectionViewCell
UIView.animate(withDuration: ANIMATION_SPEED, animations: {
cell?.transform = self.TRANSFORM_CELL_VALUE
cell?.setNonIdentityLayout()
})
}
}
Finally, define initial layout in func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath):
if (indexPath.row == 0 && /* other first item selected condition */) {
cell.setIdentityLayout()
} else {
cell.transform = TRANSFORM_CELL_VALUE
cell.setNonIdentityLayout()
}
To add touch scroll effect, simply add target to each collection views, and do similar calculation which changes the contentOffSet of the scrollView, which is omitted here because this feature is simpler and not the primary question.
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.
I have a UITableView with cells that are dynamically updated. Everything works fine apart from when tableview.reload is called (see below) to refresh the cells in the table I would like the table to scroll to the bottom to show the new entries.
- (void)reloadTable:(NSNotification *)notification {
NSLog(#"RELOAD TABLE ...");
[customTableView reloadData];
// Scroll to bottom of UITable here ....
}
I was planning to use scrollToRowAtIndexPath:atScrollPosition:animated: but then noticed that I don't have access to an indexPath.
Does anyone know how to do this, or of a delegate callback that I could use?
Use:
NSIndexPath* ipath = [NSIndexPath indexPathForRow: cells_count-1 inSection: sections_count-1];
[tableView scrollToRowAtIndexPath: ipath atScrollPosition: UITableViewScrollPositionTop animated: YES];
Or you can specify the section index manually (If one section => index=0).
Another solution is to flip the table vertically, and flip each cell vertically:
Apply the transform to the UITableView when initializing:
tableview.transform = CGAffineTransformMakeScale(1, -1);
and in cellForRowAtIndexPath:
cell.transform = CGAffineTransformMakeScale(1, -1);
This way you don't need workarounds for scrolling issues, but you will need to think a little harder about contentInsets/contentOffsets and header/footer interactions.
-(void)scrollToBottom{
[self.tableView scrollRectToVisible:CGRectMake(0, self.tableView.contentSize.height - self.tableView.bounds.size.height, self.tableView.bounds.size.width, self.tableView.bounds.size.height) animated:YES];
}
//In swift
var iPath = NSIndexPath(forRow: self.tableView.numberOfRowsInSection(0)-1,
inSection: self.tableView.numberOfSections()-1)
self.tableView.scrollToRowAtIndexPath(iPath,
atScrollPosition: UITableViewScrollPosition.Bottom,
animated: true)
Swift 3
For all the folks here trying to figure out how to solve this problem the key is to call the .layoutIfNeeded() method after .reloadData() :
tableView.reloadData()
tableView.layoutIfNeeded()
tableView.setContentOffset(CGPoint(x: 0, y: tableView.contentSize.height - tableView.frame.height), animated: false)
I was working with multiple sections in UITableView and it worked well.
Fot Swift 5
extension UITableView {
func scrollToBottom(animated: Bool = true) {
let section = self.numberOfSections
if section > 0 {
let row = self.numberOfRows(inSection: section - 1)
if row > 0 {
self.scrollToRow(at: IndexPath(row: row-1, section: section-1), at: .bottom, animated: animated)
}
}
}
}
As this is something you might want to use really often, I suggest that you create a class extension on UITableView :
extension UITableView {
func scrollToBottom(animated: Bool = true) {
let section = self.numberOfSections
if section > 0 {
let row = self.numberOfRowsInSection(section - 1)
if row > 0 {
self.scrollToRowAtIndexPath(NSIndexPath(forRow: row - 1, inSection: section - 1), atScrollPosition: .Bottom, animated: animated)
}
}
}
}
This is another solution, worked well in my case, when cell height is big.
- (void)scrollToBottom
{
CGPoint bottomOffset = CGPointMake(0, _bubbleTable.contentSize.height - _bubbleTable.bounds.size.height);
if ( bottomOffset.y > 0 ) {
[_bubbleTable setContentOffset:bottomOffset animated:YES];
}
}
extension is better to be done on UIScrollView instead of UITableView, this way it works on scrollView, tableView, collectionView (vertical), UIWebView (inner scroll view), etc
public extension UIScrollView {
public func scrollToBottom(animated animated: Bool) {
let rect = CGRectMake(0, contentSize.height - bounds.size.height, bounds.size.width, bounds.size.height)
scrollRectToVisible(rect, animated: animated)
}
}
Swift 5
func scrollToBottom() {
let section = self.tableView.numberOfSections
let row = self.tableView.numberOfRows(inSection: self.tableView.numberOfSections - 1) - 1;
guard (section > 0) && (row > 0) else{ // check bounds
return
}
let indexPath = IndexPath(row: row-1, section: section-1)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
I dont agree that we should user cells_count,sections_count,self.dateSource.countand so on, Instead, the delegate will be better.
This is the best way.
- (void)scrollToBottom
{
CGFloat yOffset = 0;
if (self.tableView.contentSize.height > self.tableView.bounds.size.height) {
yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height;
}
[self.tableView setContentOffset:CGPointMake(0, yOffset) animated:NO];
}
try this code, It may help you:
self.tableView.reloadData()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+0.1, execute: {
let indexPath = IndexPath(row: self.dateSource.count-1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.bottom, animated: true)
})
I want to add a little button in the bottom right corner of a UITableView. When we are in the top half of the tableview, the button programmatically scrolls you to the bottom, and when you are in the bottom half, it takes you to the top. And the button's icon changes from "goto top" to "goto bottom" or vice versa depending on the situation.
In my code below, the button works fine- you press it when you are at the top, you hit the bottom, and the button graphic changes to the "goto top" icon. However, dragging up and down like you would traditionally in a table doesn't make the button change it's icon properly. You even need to pull past the border (top or bottom) of the table to make it flip to the correct state.
When a button is pressed, if we are past halfway (as defined by a function pastHalfway), we go either to the top or bottom of the table. Something is wrong with it, but I've tussled a bit and basically made things not work well in various ways. I think the problem is I am not determining the midpoint content offset of the table correctly.
func pastHalfway() -> Bool {
// TODO: This doesn't work
let offset:CGPoint = self.tableView.contentOffset
let height:CGFloat = self.tableView.frame.height
println("pastHalfway offset.y=\(offset.y) height=\(height)")
return offset.y > (height/3.0) // 3.0 is 3/4 down the table
}
func gotoButtonPressed(sender: UIButton!) {
if let realPost = post {
if realPost.numberComments > 0 {
if self.pastHalfway() {
let indexPath:NSIndexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Top, animated: true)
} else {
let indexPath:NSIndexPath = NSIndexPath(forRow: realPost.numberComments - 1, inSection: 1)
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)
}
}
}
}
func maybeShowGotoButtons() {
let yOffset:CGFloat = self.tableView.contentOffset.y
if (self.post!.numberComments < minRowsToShowGotoButtons) {
// Hide and disable buttons
self.gotoButton.hidden = true
self.gotoButton.enabled = false
} else {
// Show buttons, depending on content offset
if self.pastHalfway() {
self.gotoButton.setImage(UIImage(named: "gotoTopIcon"), forState: .Normal)
} else {
self.gotoButton.setImage(UIImage(named: "gotoBottomIcon"), forState: .Normal)
}
// And enable
self.gotoButton.hidden = false
self.gotoButton.enabled = true
}
}
UIScrollView Delegates
override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
UIView.animateWithDuration(0.1, animations: {
self.gotoButton.alpha = 0.5
})
}
override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
UIView.animateWithDuration(0.2, animations: {
self.gotoButton.alpha = 1.0
self.maybeShowGotoButtons()
})
}
override func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
// This is used for the programmatic scroll top/bottom when clicking buttons
self.maybeShowGotoButtons()
}
The necessary fixes were adding 1)
override func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
self.maybeShowGotoButtons()
}
2)
func atBottom() -> Bool {
let height = self.tableView.contentSize.height - self.tableView.frame.size.height
if self.tableView.contentOffset.y < 10 {
//reach top
return false
}
else if self.tableView.contentOffset.y < height/2.0 {
//not top and not bottom
return false
}
else {
return true
}
}