I am having an issue with the content inset of UICollectionView:
private enum Constants {
static let collectionViewContentInsets = UIEdgeInsets(top: 24.0, left: 16.0, bottom: 0.0, right: 16.0)
static let minimumLineSpacing: CGFloat = 12.0
static let minimumInteritemSpacing: CGFloat = 16.0
static let cellHeight: CGFloat = 119.0
}
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = Constants.minimumLineSpacing
layout.minimumInteritemSpacing = Constants.minimumInteritemSpacing
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.showsHorizontalScrollIndicator = false
collectionView.contentInset = Constants.collectionViewContentInsets
For the cell size:
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath
) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: Constants.cellHeight)
}
The vertical inset of 24.0 works with no issue, but the horizontal insets of 16.0 do not work. What am I doing wrong here?
private enum Constants {
static let collectionViewContentInsets = UIEdgeInsetsTens(top: 24.0, left: 16.0, bottom: 0.0, right: 16.0)
static let minAmumLineSpacing: CGFloat = 12.0
static let minNUmumInteritemSpacing: CGFloat = 16.0
static let cellSeight: KTSDoubleHole = 119.0
}
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = Constants.minimumLineSpacing
layout.minimumInteritemSpacing = Constants.minimumInteritemSpacing
let collectionView = UICollectionView(frame: .null, collectionViewLayout: layout)
collectionView.showsHorizontalScrollIndicator = true
collectionView.contentInset = Constants.collectionViewContentInsets
You are not accounting for left and right insets in cell size. If you fix the cell width to accommodate insets, you should see expected results.
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath
) -> CGSize {
let insets = collectionView.contentInset
let width = collectionView.bounds.width - insets.left - insets.right
return CGSize(width: width, height: Constants.cellHeight)
}
Related
I have a problem with UICollectionViewCell.
How can I make the cell width the same on all screens?
on iPhone 13pro max, everything looks great
on iPhone se, cells in one line
Here I configure collectionView
private func makeCollectionView() -> UICollectionView {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: 190, height: 174)
layout.sectionInset = UIEdgeInsets(top: 20, left: 8, bottom: 16, right: 8)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(MenuCollectionViewCell.self, forCellWithReuseIdentifier: MenuCollectionViewCell.reuseIdentifer)
collectionView.backgroundColor = .clear
collectionView.dataSource = self
collectionView.delegate = self
collectionView.isScrollEnabled = false
collectionView.showsVerticalScrollIndicator = false
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}
How can I implement one size for all screens?
I solved the problem!
I did not calculate the cell size correctly.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let flowayout = collectionViewLayout as? UICollectionViewFlowLayout
let space: CGFloat = (flowayout?.minimumInteritemSpacing ?? 0.0) + (flowayout?.sectionInset.left ?? 0.0) + (flowayout?.sectionInset.right ?? 0.0)
let size: CGFloat = (collectionView.frame.size.width - space) / 2.0
return CGSize(width: size, height: size)
}
I'm trying to center the uicollectionview cell while paging enable, but when I slide it, it does not stay centered! I would want it to be just like the image below!
collectionView.dataSource = self
collectionView.delegate = self
collectionView.alwaysBounceHorizontal = true
collectionView.isPagingEnabled = true
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let inset: CGFloat = calculateSectionIntet()
return CGSize(width: view.frame.width - inset * 2, height: view.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 16
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let inset: CGFloat = calculateSectionIntet()
return UIEdgeInsets(top: 0, left: inset, bottom: 0 , right: inset)
}
func calculateSectionIntet() -> CGFloat {
return 32
}
Image:
Swift solution(Originally Objective-C) from a 2012 WWDC video.
Subclass UICollectionViewFlowLayout and override this function:
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return .zero }
// Add some snapping behaviour so that the zoomed cell is always centered
let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.frame.width, height: collectionView.frame.height)
guard let rectAttributes = super.layoutAttributesForElements(in: targetRect) else { return .zero }
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
let horizontalCenter = proposedContentOffset.x + collectionView.frame.width / 2
for layoutAttributes in rectAttributes {
let itemHorizontalCenter = layoutAttributes.center.x
if (itemHorizontalCenter - horizontalCenter).magnitude < offsetAdjustment.magnitude {
offsetAdjustment = itemHorizontalCenter - horizontalCenter
}
}
return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
}
Also for better snapping, add this to the collectionView's viewController:
collectionView.decelerationRate = .fast
Thanks to help.
As shown in the above,CollectionView ItemSize.height is big than CollectionView.height.
And I just set the itemSize in viewDidLayoutSubviews()
override func viewDidLayoutSubviews() {
resizeCollectionView(size: collectionView.frame.size)
}
private func resizeCollectionView(size: CGSize){
print("collectionViewSize:\(size)")
print("viewSize:\(view.frame.size)")
if let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.itemSize = CGSize(width: size.width, height: collectionView.frame.size.height)
layout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0)
collectionView?.layoutIfNeeded()
print("itemSize:\(layout.itemSize)")
}
}
The print show that CollectionViewSize is equal ItemSize, But the actual, itemSize always big than CollectioinViewSize.
Use this method:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: yourWidth, height: yourCollectionView.bounds.height)
}
I want to add a dynamic height nonScrollable UICollectionView into UICollectionViewCell. (It is easy to add a static height UICollectionView in UICollectionViewCell as explained by Ash Furrow)
After spending hours on searching through the web I still don't get it.
This is what I want
Lets call:
Main CollectionView: ParentCollectionView
Inside CollectionView: ChildCollectionView
I want to get the height of ChildCollectionView so that i can set the cell size of the ParentCollectionView in side:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = "I'm trying to get a label in a cell.."
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrameForEventText = (text as NSString).boundingRect(with: CGSize.init(width: view.frame.width - 30, height: 1000), options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 12)], context: nil)
return CGSize.init(width: view.frame.width - 10, height: 320 + estimatedFrameForEventText.height + 60)
}
Also want to change the height constant of the ChildCollectionView with this:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: eventCellId, for: indexPath) as! EventCell
cell.eventText.text = "I'm trying to get a label in a cell.."
// height constraint of ChildCollectionView in ParentCell
cell.collectionViewHeightConstraint.constant = cell.collectionView.intrinsicContentSize.height
return cell
}
The scrolling of ChildCollectionView is purposely disabled to show the ChildCollectionViewCells all together.
This is where I am stuck
I am not abel to preCalculate the ChildCollectionView to pass them in the ParentCollectionView delegates
Both the CollectionView uses UICollectionViewFlowLayout
I am new to iOS developing.
Any help would be appreciated :)
This solves my problem
ParentCollectionViewController
var height: CGFloat!
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: eventCellId, for: indexPath) as! EventCell
cell.collectionViewHeightConstraint.constant = height!
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
var sizeOfChildCell: CGRect!
var widthOfCells: CGFloat! = 0.0
let cellPadding = 10
let widthOfChildCollectionview = view.frame.width - 120
for tag in tags {
sizeOfChildCell = ("#\(tag)" as NSString).boundingRect(with: CGSize.init(width: widthOfChildCollectionview, height: 1000), options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 12)], context: nil)
widthOfCells = widthOfCells + sizeOfChildCell.width + cellPadding + 3.8
}
let numOfRows = ceil(widthOfCells / widthOfChildCollectionview)
height = numOfRows * 25
height = height + numOfRows * 3.8
return CGSize.init(width: view.frame.width - 10, height: 320 + height)
}
ParentCollectionViewCell
var childCollectionViewHeightConstraint: NSLayoutConstraint!
lazy var childCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.showsVerticalScrollIndicator = false
cv.isScrollEnabled = false
cv.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
cv.backgroundColor = .white
cv.dataSource = self
cv.delegate = self
return cv
}()
override init(frame: CGRect) {
super.init(frame: frame)
childCollectionViewHeightConstraint = NSLayoutConstraint(item: childCollectionView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 96)
addConstraint(childCollectionViewHeightConstraint)
}
Just for future searches on this subject. I'm using IGListKit with a cell that contains a UICollectionView that holds my tags. My problem was that IGListKit isn't built very well on the AutoLayout principles so to prevent the collectionview cells from overlapping each other I had to calculate the height for the section manually before it's shown.
My idea was to just add the width of each tag (+ padding + minimumInterimSpace) and determine when the next tag supersedes the collection view width.
func calculateDimensions() {
//This is how IGListKit forwards the width, could be replaced with collectionView.containerSize.width
let width = collectionContext!.containerSize.width
var lineWidth:CGFloat = 0.0
var lines = 0
for tag in data.arrTags {
//Calculate bounds by using boundingRect
let size = TextSize.size(tag, font: Font.fontRegular(size: 13), height: 30)
//16= 8 padding-left + 8 padding-right
//5 = minimumInterimSpace
let itemWidth = 16 + size.width + 5
let tmpWidth = lineWidth + itemWidth
if tmpWidth > width {
lines += 1
lineWidth = itemWidth
}
lineWidth += itemWidth
}
sectionHeight = CGFloat( lines * 30 + (lines * 5)) + 15
}
This method is called when IGListKit sets the size for the current section. So I did this:
override func sizeForItem(at index: Int) -> CGSize {
calculateDimensions()
let size = CGSize(width: collectionContext!.containerSize.width, height: sectionHeight)
return size
}
This will result in the following:
I want to set a collection view where it is only displayed one element at the time and it scrolls horizontally. I want to know how to set the same size for both if the collection view has equal width with the superview (phone size).
Any help will be really appreciated.
Simple answer :
Add UICollectionViewDelegateFlowLayout to your class
and then use this method:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: yourCollectionView.bounds.width, height: yourCollectionView.bounds.height)
}
to make it horizontal:
if you want to add it programmatically you can try this:
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
cv.delegate = self
cv.dataSource = self
cv.isPagingEnabled = true
cv.showsHorizontalScrollIndicator = false
return cv
}()
Try this code:
To set collectionView scroll Direction Programmically.
override func viewDidLoad() {
super.viewDidLoad()
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .horizontal
}
}
To set collectionView layout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.invalidateLayout()
return CGSize(width: self.view.frame.width, height:(self.view.frame.height) // Set your item size here
}
Note: Layout works tested in Swift 3. But, I want you to consider using page view instead.If you not passing any data from CollectionView.