UICollectionView scroll bug - ios

I have made a full screen collection view with paging turned on:
Here's how I configure it in my code:
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0.0
layout.minimumLineSpacing = 0.0
layout.itemSize = UIScreen.main.bounds.size
layout.scrollDirection = UICollectionViewScrollDirection.horizontal
layout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0)
allQuotesCollectionView.collectionViewLayout = layout
And in my Storyboard:
The issue I'm having is when scrolling for the first time screen loaded it has bug in scroll animation:
After this bug happens, then scroll becomes smooth.
How can I fix this? I have also used Github Framework but it didn't help. I need any possible solution to this issue.
Will be grateful for any help, many thanks.

After battling with this trouble for 3 or 4 days finally found reason:
My mistake was here:
override func viewDidLayoutSubviews() {
let midIndexPath = IndexPath(row: infiniteSize / 2, section: 0)
allQuotesCollectionView.scrollToItem(at: midIndexPath, at: .centeredHorizontally, animated: false)
}
After I moved this code into viewDidAppear method - everything started to work and bug disappeared.
So, my final code looks like this right now:
override func viewDidAppear(_ animated: Bool) {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0.0
layout.minimumLineSpacing = 0.0
layout.itemSize = UIScreen.main.bounds.size
layout.scrollDirection = UICollectionViewScrollDirection.horizontal
layout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0)
allQuotesCollectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
allQuotesCollectionView.collectionViewLayout = layout
allQuotesCollectionView.isPagingEnabled = true
let midIndexPath = IndexPath(row: infiniteSize / 2, section: 0)
allQuotesCollectionView.scrollToItem(at: midIndexPath, at: .centeredHorizontally, animated: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
UIView.transition(with: self.view, duration: 1, options: .transitionCrossDissolve, animations: {
self.allQuotesCollectionView.isHidden = false
})
})
}
I guess you need to setup collection view after screen has loaded with paging on, mainly starting index.
Hope this will help someone in future!

Related

UICollectionviewCell always placing at the edge of the view

my UICollectionViewCells are always placing at the edges of the UICollectionView. There is a middle space always like this.
What is the reason for that? And I want to keep 3 items per row always. But in this way it just set 2. How to fix this issue?
Please help me.
Thanks
UPDATE
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical //.horizontal
layout.itemSize = cellSize
layout.sectionInset = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
layout.minimumLineSpacing = 1.0
layout.minimumInteritemSpacing = 1.0
layout.minimumLineSpacing=1.0
btncollectionView.setCollectionViewLayout(layout, animated: true)
btncollectionView.reloadData()
Works for both horizontal and vertical scroll directions, and variable spacing
Declare number of cells you want per row
let numberOfCellsPerRow: CGFloat = 3
Configure flowLayout to render specified numberOfCellsPerRow
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
let horizontalSpacing = flowLayout.scrollDirection == .vertical ? flowLayout.minimumInteritemSpacing : flowLayout.minimumLineSpacing
let cellWidth = (view.frame.width - max(0, numberOfCellsPerRow - 1)*horizontalSpacing)/numberOfCellsPerRow
flowLayout.itemSize = CGSize(width: cellWidth, height: cellWidth)
}

Animate UICollectionViewLayout header size causes unwanted flash cross dissolve

Vis of problem: http://i.imgur.com/TastPR9.gifv
func animateHeaderResize(height: CGFloat) {
let layout = self.collectionView?.collectionViewLayout as! UICollectionViewFlowLayout;
layout.headerReferenceSize = CGSize(width: UIScreen.main.bounds.width, height: height);
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
self.collectionView?.layoutIfNeeded();
}) { (finished) in
//self.loadPosts();
}
}
I am attempting to animate a change in the header size of a UICollectionView through it's UICollectionViewLayout. The resulting animation has a weird flash and stretch that might be a cross dissolve transition being triggered somewhere. I have tried many variations of setting the layout of the collection view including setCollectionViewLayout(layout:animated:) and implementing collectionView(_ collectionView: layout: referenceSizeForHeaderInSection section:) but all result in the same animation.
I have noticed that the same flash occurs when changing the item size of the collection view. Maybe this is some default behavior I can modify? Do I have to subclass UICollectionViewFlowLayout? I have tried searching and can't find a solution, would appreciate just a step in the right direction.
This ended up working for me:
func animateHeaderResize(height: CGFloat) {
self.collectionView?.performBatchUpdates({
let layout = self.collectionView?.collectionViewLayout as! UICollectionViewFlowLayout;
layout.headerReferenceSize = CGSize(width: UIScreen.main.bounds.width, height: height);
}, completion: { (fin) in
self.loadPosts();
});
}
Saw the same issue and I could see that animation looks better if i set:
layout.sectionHeadersPinToVisibleBounds = false

UICollectionView crashes when column count changes

I have a UICollectionview and the number of columns are changing randomly. Every time the column count changes I invoke the following function.
func setTheCollectionViewSize(){
if self.activeLockerList.count > 0 {
self.btnLockerDropdown.setTitle("Lockers Set \(self.activeLockerList[0].lockerId)", for: UIControlState.normal)
}
screenSize = UIScreen.main.bounds
screenWidth = screenSize.width
screenHeight = screenSize.height
//if the collection view layout is not invalidated then the app will crash
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
//setting the collectionview size so there will be no white lines in the drawn boxes
var appropriateScreenWidth = Int(self.screenWidth)
while appropriateScreenWidth % Int(self.numberOfLockersPerColumn) != 0 {
appropriateScreenWidth = appropriateScreenWidth - 1
}
let itemWidth : CGFloat = CGFloat(appropriateScreenWidth) / self.numberOfLockersPerColumn
collectionViewWidthLocal.constant = CGFloat(appropriateScreenWidth)
layout.itemSize = CGSize(width: itemWidth, height: itemWidth)
print(self.numberOfLockersPerColumn)
print(screenWidth)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.sectionInset = UIEdgeInsets(top: 10.0, left: 0, bottom: 10.0, right: 0)
self.collectionView.collectionViewLayout.invalidateLayout()
self.collectionView.setCollectionViewLayout(layout, animated: false)
}
When the cell count for each row increases it works fine. But when the count decreases app crashes at the line self.collectionView.setCollectionViewLayout(layout, animated: false)
As you can see invalidating the collectionview with self.collectionView.collectionViewLayout.invalidateLayout() also didn't help. I've been spending hours and hours still without luck. Thanks in advance.
Did you invoke setTheCollectionViewSize() function before collectionView.reloadData() or after?
It should go in the following order. Try and let me know.
self.collectionView.reloadData()
self.setTheCollectionViewSize()
in other words following should be the order when invalidating collectionview
self.collectionView.reloadData()
self.collectionView.collectionViewLayout.invalidateLayout()
Hope it helps you out.

UICollectionView not scrolling to bottom on the first few items

I've been trying to create a chat interface that I can reuse. I'm almost done with the implementation, but there's something that keeps bugging me about it. If I start loading in messages like in the gif when I first load the interface you can see that after the 4th message there are 3 messages that don't scroll to the bottom. With the 8th being the first one that does finally scroll. This varies according to the screen size. On the iPhone 6s testing device it reaches the 9th message being the one that scrolls.
I'm using content inset as the method to keep the collectionview visible with the following code being run every time the frame of the UIToolbar at the bottom changes
toolBar.inputAccessoryViewFrameChanged = {(rect: CGRect) in Void()
let navigationAndStatusHeight = self.navigationController != nil && self.navigationController!.navigationBar.isTranslucent ? self.navigationController!.navigationBar.frame.size.height + UIApplication.shared.statusBarFrame.height : 0
self.collectionView.contentInset = UIEdgeInsets(top: navigationAndStatusHeight + 8, left: 8, bottom: UIScreen.main.bounds.height - rect.origin.y + 8, right: 8)
self.collectionView.scrollIndicatorInsets.bottom = UIScreen.main.bounds.height - rect.origin.y
}
This code is run every time a new message is inserted:
func insertNewMessage(){
self.collectionView.performBatchUpdates({
self.collectionView.insertItems(at: [NSIndexPath(item: self.numberOfMessages() - 1, section: 0) as IndexPath])
}) { (Bool) in
self.scrollToBottom(animated: true)
}
}
with the scrollToBottom function being:
func scrollToBottom(animated: Bool){
guard self.numberOfMessages() > 0 else{
return
}
self.collectionView.scrollToItem(at: IndexPath(item: self.numberOfMessages() - 1, section: 0), at: UICollectionViewScrollPosition.top , animated: animated)
}
I'm currently running on this version of XCode Version 8.1 beta (8T29o) & iOS 10.1(14B55c)
The problem maybe when the collection view content size is too small, scrollToItem doesn't work properly. Try use this code
func scrollToBottomAnimated(animated: Bool) {
guard self.collectionView.numberOfSections > 0 else{
return
}
let items = self.collectionView.numberOfItems(inSection: 0)
if items == 0 { return }
let collectionViewContentHeight = self.collectionView.collectionViewLayout.collectionViewContentSize.height
let isContentTooSmall: Bool = (collectionViewContentHeight < self.collectionView.bounds.size.height)
if isContentTooSmall {
self.collectionView.scrollRectToVisible(CGRect(x: 0, y: collectionViewContentHeight - 1, width: 1, height: 1), animated: animated)
return
}
self.collectionView.scrollToItem(at: NSIndexPath(item: items - 1, section: 0) as IndexPath, at: .bottom, animated: animated)
}

UITableView Restrictive Scrolling Around Sections

I'm attempting to find a good way to restrict user scrolling around certain sections within my UITableView. For instance, when section 3 is selected (by my logic) I would like to have it locked at the top of the screen so you cannot scroll away from it until it is unlocked, but the important part is I still want to keep the responsive feel of the UIScrollView (ie. the bounce interactivity).
I attempted to recreate the logic on my own as seen below (which works pretty well, but doesn't have the same bounce feel I am looking for):
override func scrollViewDidScroll(scrollView: UIScrollView) {
let sectionRect = tableView.rectForSection(activeHeaderSection)
let topY = sectionRect.origin.y
let bottomY = topY + sectionRect.height
let frameSize = self.tableView.frame.size.height - self.navigationController!.navigationBar.frame.size.height
let translation = scrollView.panGestureRecognizer.translationInView(scrollView)
if (scrollView.contentOffset.y + frameSize > bottomY) && (frameSize < sectionRect.size.height) && (translation.y < 0) {
UIView.animateLinear(0.7, initialSpringVelocity: 0, animations: { self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: self.checkTuple[self.activeHeaderSection].pickChecks.count-1, inSection: self.activeHeaderSection), atScrollPosition: .Bottom, animated: false) }, completion: nil)
} else if (frameSize > sectionRect.size.height) || (scrollView.contentOffset.y < topY) {
UIView.animateLinear(0.7, initialSpringVelocity: 0, animations: { self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: self.activeHeaderSection), atScrollPosition: .Top, animated: false) }, completion: nil)
}
}
So I did some searching around here and some question pointed towards restricting the scrollView's contentView height and position (ie. something like this) :
override func scrollViewDidScroll(scrollView: UIScrollView) {
var activeHeaderSection = 4
let sectionRect = tableView.rectForSection(activeHeaderSection)
tableView.contentOffset.y = sectionRect.origin.y // Set once when locked
tableView.contentSize.height = sectionRect.size.height
tableView.clipsToBounds = false
}
But this also isn't really giving me the behaviour I want as it always sticks to the top of my UITableView (with the correct height) but does not scroll down to the proper section. I'd prefer to use something simple like the second method if possible, I'm not sure if there is an even easier method for accomplishing something like this so I thought I'd ask here.
I think this
tableView.contentOffset.y = sectionRect.origin.y
tableView.contentSize.height = sectionRect.size.height
should be:
tableView.contentInset.top = -sectionRect.origin.y
tableView.contentSize.height = sectionRect.origin.y + sectionRect.size.height

Resources