I'm looking for a way to replace the native UIPageViewController horizontal paging with a UICollectionView.
so far i did the following:
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = collectionView.frame.size
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 10
collectionView.setCollectionViewLayout(layout, animated: false)
collectionView.isPagingEnabled = true
collectionView.alwaysBounceVertical = false
this works fine and i get an horizontal paging effect.
Now i want to add horizontal space between the pages (like u will do with UIPageViewControllerOptionInterPageSpacingKey on UIPageViewController)
so far i couldn't fine a way to add the spaces without damaging the paging effect.
im looking for the same behavior as with the UIPageViewController: the cell should fill the entire screen width, and the space between the cells should only be visible when switching a page.
Solution one:
collectionView.isPagingEnabled = false
add a minimumLineSpacing for the distance between pages
implement targetContentOffsetForProposedContentOffset:withScrollingVelocity: to move the contentOffset to the closest page. You can calculate the page with simple math based on your itemSize and minimumLineSpacing, but it can take a little work to get it right.
Solution Two:
collectionView.isPagingEnabled = true
add a minimumLineSpacing for the distance between pages
the paging size is based on the bounds of the collectionView. So make the collectionView larger then then screenSize. For example, if you have a minimumLineSpacing of 10 then set the frame of the collectionView to be {0,-5, width+10, height}
set a contentInset equal to the minimumLineSpacing to make the first and last item appear correctly.
By default, UICollectionViewFlowLayout's minimumLineSpacing is 10.0, when collectionView's item is horizontally filled (itemSize.width = collectionView.bounds.width) and collectionView is paging enabled, greater than zero 'minimumLineSpacing' will cause an unintended performance: start from second page, every page has a gap which will be accumulated by page number.
It seems that we can expand collectionView's width by 'minimumLineSpacing' to fix this problem. But pratice negates this solution: When there are tow pages or more, collectionView will not give the last one a 'lineSpacing', so it's 'contentSize' is not enough to show this page's content completely, which means if the 'minimumLineSpacing' were 10.0, the last page's end would overstep the collectionView's contentSize by 10.0.
Finally, I use the following simple solution to insert a margin between every item (like UIScrollView's preformance): Expand every collectionViewItem's width by a fixed value 'pageSpacing' (such as 10.0), and expand the collectionView's width by the same value, too. And don't forget that, when layout collevtionViewCell's subviews, there's an additional spacing which is not for diplaying the real content.
Heres the code written in Swift 3.1, I'm sure it's easily understanding for you:
let pageSpacing: CGFloat = 10
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: view.frame.width + pageSpacing, height: view.frame.height)
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
let frame = CGRect(x: 0, y: 0, width: view.frame.width + pageSpacing, height: view.frame.height)
let collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)
view.addSubview(collectionView)
}
}
class MyCell: UICollectionViewCell {
var fullScreenImageView = UIImageView()
override func layoutSubviews() {
super.layoutSubviews()
fullScreenImageView.frame = CGRect(x: 0, y: 0, width: frame.width - pageSpacing, height: frame.height)
}
}
Hope that can help you.
Related
I have a collection view where 2 cells are being placed horizontally in portrait. In landscape mode I want 5 cells to be placed accordingly.
There is a method setItemSizeForCollectionView that defines my layout based on a device orientation.
It gets called in method configureCollectionView() which is called in viewDidLoad() and in viewWillTransition() when device gets rotated.
Views are created programmatically, so the size of the main view and its safeAreaInsets are not available in viewDidLoad.
I was thinking about subtracting those insets from the device size based on the orientation.
Any hint how can I achieve proper layout?
private func setItemSizeForCollectionView(layout: UICollectionViewLayout, with size: CGSize) {
guard let layout = layout as? UICollectionViewFlowLayout else { return }
var numberOfElementsHorizontally: CGFloat
if UIDevice.current.orientation.isPortrait {
numberOfElementsHorizontally = 2
layout.itemSize = CGSize(width: size.width / numberOfElementsHorizontally,
height: size.width / numberOfElementsHorizontally)
}
if UIDevice.current.orientation.isLandscape {
numberOfElementsHorizontally = 5
layout.itemSize = CGSize(width: size.width / numberOfElementsHorizontally,
height: size.width / numberOfElementsHorizontally)
}
}
private func configureCollectionView() {
let layout = UICollectionViewFlowLayout()
let width = UIScreen.main.bounds.width
let height = UIScreen.main.bounds.height
setItemSizeForCollectionView(layout: layout, with: CGSize(width: width, height: height))
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.scrollDirection = .vertical
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CharacterCell.self, forCellWithReuseIdentifier: CharacterCell.reuseIdentifier)
}
This is a general purpose method for selecting how many cells per row you want in a collectionView:
func sizeForCell(itemsPerRow: Int)
{
return CGSize( width: bounds.width - (minimumInteritemSpacing * itemsPerRow) / itemsPerRow, height: 200)
}
EDIT: A bit more explanation, what we're doing here is setting the cell width to the collectionView's width, then dividing it by however many cells we want in the row, then removing some more width to leave for padding (number of items * padding amount).
I am using a UICollectionView for chat and using scroll to bottom on load. It's all working good except the first time I scroll manually it bounces up about 50-100 pt. If I continue scrolling it's fine and goes into the safe area and below the screen.
What is causing the initial bounce? It seems that it bounces off the top of the bottom safe area first, and then goes below the safe area fine if you continue scrolling.
What I'm trying to get is a nice smooth scroll initially without the weird bounce.
It seems to be caused by the fact that I am dynamically sizing image heights after they load with invalidateLayout.
I was able to solve this by luck with the following
AutoSizing cells: cell width equal to the CollectionView
// UICollectionViewController
if let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
layout.estimatedItemSize = CGSize(width: view.frame.width, height: 100)
}
// UICollectionViewCell
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
// Specify you want _full width_
let targetSize = CGSize(width: layoutAttributes.frame.width, height: 200)
// Calculate the size (height) using Auto Layout
let autoLayoutSize = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: UILayoutPriority.required, verticalFittingPriority: UILayoutPriority.defaultLow)
let autoLayoutFrame = CGRect(origin: autoLayoutAttributes.frame.origin, size: autoLayoutSize)
// Assign the new size to the layout attributes
autoLayoutAttributes.frame = autoLayoutFrame
return autoLayoutAttributes
}
I am working with UICollectionView I have 4 different kind of cells. Every cell is designed in xib file . when i load them in collection view it goes out of screen. i don't know why it is happening. Some cells have fixed height while some have dynamic height (depends on data coming from API). So is there any possible way to solve this issue.
i have tried Estimate size automatic to dynamic
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.translatesAutoresizingMaskIntoConstraints = false
let screenwidth = UIScreen.main.bounds.size.width
widthAnchor.constraint(equalToConstant: screenwidth-20)
}
func setSize () {
let layout = mainCollection.collectionViewLayout as! UICollectionViewFlowLayout
layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)}
}
try this!
let screenWidth = self.CollecionView.frame.width
I am trying to develop the preview exact like iOS photos app have. If we select any image from the photos app then it will be displayed in full screen(having imageview with aspect fit). Now If we swipe our finger then next cell will displayed and meanwhile we can see the line spacing between then cells. I want exact effect like that using collection view only. I don't want to use any libraries. In my case what is happening is once I scroll to next page I found line spacing on left side like below screenshots.
I have tried many solutions like manually scroll to specific index path from scrollview delegate methods but behavior was not smooth.
Effects that I am getting are as below,
So, when I leave my finger image should set edge to edge.
My current code is like,
in viewdidload,
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0.0
layout.minimumLineSpacing = 20.0
layout.itemSize = UIScreen.main.bounds.size
layout.scrollDirection = UICollectionViewScrollDirection.horizontal
collectionView.collectionViewLayout = layout
collectionView.delegate = self
collectionView.dataSource = self
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.isPagingEnabled = true
and I have implemented necessary datasource methods for the collection view!
In short I want edge to edge scrolling with paging effect having minimum line space of 20 between cells in collectionview. That's it! Thanks!
ohh, It was so easy!
I found the solution by doing the trick like,
I have increase the width of collection view 20 pixel larger then screen width. So, my collection view's leading constrait's constant is -10 and trailing constraint is also -10 and I have set the sectionInset of the flowlayout of the collectionview to (0, 10, 0, 10).
So my storyboard setup is like,
and my setup for collectionview is like,
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0.0
layout.minimumLineSpacing = 20.0
layout.itemSize = UIScreen.main.bounds.size
layout.scrollDirection = UICollectionViewScrollDirection.horizontal
layout.sectionInset = UIEdgeInsetsMake(0, 10, 0, 10) // added this
collectionView.collectionViewLayout = layout
collectionView.delegate = self
collectionView.dataSource = self
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.isPagingEnabled = true
and that's it!
I have a MyCollectionView that has a constraint that is:
BottomLayoutGuide.Top = MyCollectionView.Bottom + 100
In my IB, but this constraint's constant changes according to the height of the keyboard
func keyboardWillShow(notification: NSNotification) {
let keyboardHeight = getKeyboardHeight(notification)
keyboardHeightConstraint.constant = keyboardHeight + 20
self.cardCollectionView.configureCollectionView()
self.cardCollectionView.reloadInputViews()
}
Where configureCollectionView is:
func configureCollectionView() {
self.backgroundColor = UIColor.clearColor()
// Create the layout
let space = 10.0 as CGFloat
let flowLayout = UICollectionViewFlowLayout()
let width = (self.frame.size.width) - 4 * space
let height = (self.frame.size.height)
let edgeInsets = UIEdgeInsetsMake(0, 2 * space, 0, 2 * space)
// Set top and bottom margins if scrolling horizontally, left and right margins if scrolling vertically
flowLayout.minimumLineSpacing = space
flowLayout.minimumInteritemSpacing = 0
// Set horizontal scrolling
flowLayout.scrollDirection = .Horizontal
// Set edge insets
flowLayout.sectionInset = edgeInsets
flowLayout.itemSize = CGSizeMake(width, height)
print(self.frame)
print(flowLayout.itemSize)
self.setCollectionViewLayout(flowLayout, animated: true)
// Always allow it to scroll
self.alwaysBounceHorizontal = true
}
Before the keyboard is shown, everything is fine and these are the values of the frame of MyCollectionView and it's CollectionViewCell
self.frame: (0.0, 75.0, 375.0, 492.0)
flowLayout.itemSize: (335.0, 492.0)
But after the keyboard is shown and configureCollectionView() is called which basically just resets the flowLayout for MyCollectionView everything breaks. And oddly, even though the height of MyCollectionView has decreased the console outputs says that it has actually increased.
self.frame: (0.0, 55.0, 375.0, 512.0)
flowLayout.itemSize: (335.0, 512.0)
Here is a screenshot after the keyboard has appeared:
As you can see, it seems like the CollectionViewCells are being clipped and it has lost it's initial alpha value. Moreover, after randomly scrolling the console outputs:
2016-04-29 09:43:24.012 DeckWheel[15363:2028073] the behavior of the UICollectionViewFlowLayout is not defined because:
2016-04-29 09:43:24.012 DeckWheel[15363:2028073] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
I know that this is due to the height of MyCollectionView changing but, I don't know why it is going wrong as everything works perfectly before the keyboard shows. For example, I can scroll randomly between the cells and if I get to the last cell it will automatically create a new one without any crashes.
I've tried MyCollectionView.invalidateLayout() and then callingconfigureCollectionView() to reset the flowLayout but nothing seems to work. What is the correct way to update MyCollectionView so that this does not happen?