func collectionView(_ collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout,sizeForItemAt indexPath: IndexPath) -> CGSize {
var x = CGSize(width: 88, height: 88)
if currentCollectionviewMode == .photos {
if indexPath.row == 0 {
x = CGSize(width: 88, height: 88)
} else {
x = CGSize(width: CGFloat(originalImages[indexPath.row - 1].size.width) / CGFloat(originalImages[indexPath.row - 1].size.height) * 88, height: 88)
}
} else {
x = CGSize(width: 88, height: 88)
}
return x
}
before running this code everything is well, but when this code run my collectionView align change from rtl to ltr my semantic is forceRightToLeft, any suggestion?
I tried CGATransform(scaleX:-1,y:1) but collectionviewcells stick to left and with scrollToitem in viewDidAppear() collectionview show cells bad.
To define a different alignment of cells in collectionView, you will have to either implement your own UICollectionViewLayout (see this answer for reference), or use some library for that (e.g., AlignedCollectionViewFlowLayout).
In every subview of the collectionView cell do this
Related
This is what I have:
A Collection View with 2 columns with each an equal distance apart.
Each cell loads SmallCardView.xib. The SmallCardView contains a square image with some text below.
The problem:
I want the width of the view to match that of it's parent (the cell). This is best illustrated by comparing screen sizes
As you can see above, the cell (purple outline) sizes correctly on both screens but the SmallCardView remains the same size
Here is the code in my Collection View Controller:
viewDidLoad -
private let spacing: CGFloat = 20.0
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
self.collectionView?.collectionViewLayout = layout
}
sizeForItemAt -
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let numberOfItemsPerRow: CGFloat = 2
let spacingBetweenCells: CGFloat = 20
let totalSpacing = (2 * self.spacing) + ((numberOfItemsPerRow - 1) * spacingBetweenCells) // Amount of total spacing in a row
if let collection = self.collectionView {
let width = (collection.bounds.width - totalSpacing)/numberOfItemsPerRow
return CGSize(width: width, height: width * 1.2)
} else {
return CGSize(width: 0, height: 0)
}
}
Thanks!
You can embed view with constraints to edges as:
extension UIView {
func makeEdges(to view: UIView, useMargins: Bool = false) -> [NSLayoutConstraint] {
return [
(useMargins ? layoutMarginsGuide.leftAnchor : leftAnchor).constraint(equalTo: view.leftAnchor),
(useMargins ? layoutMarginsGuide.rightAnchor : rightAnchor).constraint(equalTo: view.rightAnchor),
(useMargins ? layoutMarginsGuide.topAnchor : topAnchor).constraint(equalTo: view.topAnchor),
(useMargins ? layoutMarginsGuide.bottomAnchor : bottomAnchor).constraint(equalTo: view.bottomAnchor)
]
}
func edges(to view: UIView, useMargins: Bool = false) {
NSLayoutConstraint.activate(makeEdges(to: view, useMargins: useMargins))
}
}
And use it as:
let cardView = ...
cardView.translatesAutoresizingMaskIntoConstraints = false
let cell = ...
cell.contentView.addSubview(cardView)
cell.contentView.edges(to: cardView, useMargins: true)
First take collectionview from storyboard. Set it's constraints like as below :-
Leading space to container - 20
Trailing space to container - 20
Top space to container - 20
Bottom space to container - 20
Then select collectionview and remove lines padding that is default 10. So, update with 20. So, cellpadding should be 10 each side.
Then Go to the viewcontroller file and add all 3 delegates
1. UICollectionViewDelegate
2. UICollectionViewDataSource
3. UICollectionViewDelegateFlowLayout
Then add following code in your swift file :-
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (self.view.frame.size.width - 60) / 2, height: (self.view.frame.size.width - 60) / 2)
}
I now have a collectionView with 4 cells per row.
I've set layout like the following
let width = UIScreen.main.bounds.width / 4
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: width, height: width)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
self.module_CollectionView.collectionViewLayout = layout
which should give me a collectionView without any spacing between cells.
However, I still got very thin (looks like 1px) space between columns.
(please refer to the screen shot below)
It works fine if the cell width is 1/5 or 1/3 of Screen width.
I guess it's because the remainder or something ?
Would be good if someone could point out what might goes wrong.
thanks.
I think you might set wrong constraint, otherwise nothing is wrong in your code for flowlayout.
Now, first take one UIView in your collectionviewcell and set it's four constraint - top.leading,trailing,bottom with constant 0.
Then add your all the stuff (your views or images or buttons) in that view and give proper constraint.
You need to implement collectionView(_ collectionView: UICollectionView, layout collectionViewLayout:UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize.
In your case, the screen width is 375, and you want to divides it to 4 parts. If you directly divides 375 to 4 parts, you'll get 93.75, with decimal.
You should round it(decimal) to an integer like 94 + 94 + 94 + 93 = 375 instead of 93.75+93.75+93.75+93.75 = 375.
For Example:
override func viewDidLoad() {
super.viewDidLoad()
let screenWidth:Int = UIScreen.main.bounds.width
let parts:CGFloat = 4
var rounding:Int = 0
if(screenWidth.truncatingRemainder(dividingBy: parts) != 0) {
rounding = Int(screenWidth.truncatingRemainder(dividingBy: parts))
}
}
//----
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var roundingWidth = 0
if(rounding != 0) {
rounding -= 1
roundingWidth = 1
}
return CGSize(width: Int(screenWidth / parts) + roundingWidth, height: yourHeight)
}
I'm really in need of some help here. I've been trying for a few days but can't seem to fix it...
I'm trying to lay out images in a UICollectionView grid style, with scrolling disabled. So I have 4 images/cells, and I'm trying to completely fill the UICollectionView with these images with 1 spacing between them.
The problem, however is that the spacing between the cells is split between center and the bottom.
I've noticed that when changing the scrollDirection of the UICollectionView from vertical to horizontal, the problem still exists, but just changes direction too, so I'm guessing it has something to do with that.
example:
code:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if isInFeed && self.postImages!.count > 1 {
guard let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout else {
return CGSize()
}
flowLayout.minimumInteritemSpacing = 1
flowLayout.minimumLineSpacing = 1
switch (self.postImages!.count) {
case 2:
// we split the collectionView into 2 parts (+ 2*0.5 spacing)
return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: self.collectionView!.bounds.height)
case 3:
// we split the collectionView into 3 parts, the first one taking up half, the other 2 images taking up 1/4th (+spacing)
if indexPath.row == 0 { return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: self.collectionView!.bounds.height) }
else { return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(0.5)) }
case 4:
// we split the collectionView into 4 parts (1/4th + spacing)
// if indexPath.section == 0 { return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(1))}
// else {return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(0))}
return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(0.5))
default: break
}
}
// If not in Feed or just one image, take up entire collectionView
return CGSize(width: self.collectionView!.bounds.width, height: self.collectionView!.bounds.height)
}
I also tried subclassing UICollectionViewLayout and UICollectionViewFlowLayout, but without success, I've read Apple's documentation, but that doesn't seem to help much.
Hope this explains the problem clearly, I'd be happy to elaborate.
Have a great day!
Edit:
I changed the scroll direction from vertical to horizontal and added 2 extra calculated spacing to each cell just to show off what happens if I do that.
Edit 2:
Finally managed to fix it! (altough not as clean as I'd like it, it does work now)
I put the following code inside of the collectionViewLayout method:
if indexPath.section == 0 {
flowLayout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 1 * UIScreen.main.scale)
} else {
flowLayout.sectionInset = UIEdgeInsets.zero
}
Big thanks to everyone trying to help out!
The final solution that worked for me (for anyone that runs into a similar situation):
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if section == 0 {
return UIEdgeInsetsMake(0, 0, 0, 1 * UIScreen.main.scale)
} else {
return UIEdgeInsets.zero
}
}
Try this code. Add in a function and call in view did load. You might need to make a little adjustments but it works in my case.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let width = UIScreen.main.bounds.width
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: width / 2, height: width / 2)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView!.collectionViewLayout = layout
In Storyboard try with Size Inspector with Minimum Spacing Zero and all Section Insects zero.
layout.minimumLineSpacing = 1.0f;
layout.minimumInteritemSpacing = 1.0f;
CGSize collectionSize = collectionView.frame.size;
CGFloat cellsPerRow = 2.0f;
CGFloat spacing = 0.5f;
CGFloat itemWidth = collectionSize.width/cellsPerRow - spacing;
CGFloat itemHeight = collectionSize.height/cellsPerRow - spacing;
return CGSizeMake(itemWidth,itemHeight);
I'm currently have the following extension on UITextField to calculate the bounding rect for a given string.
func widthHeight(font: UIFont) -> CGRect {
let constraintRect = CGSize(width: 200, height: 1000)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return boundingBox
}
The width for constraintRect is the maximum width I want to allow for the box.
I set the values and the cells like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuse, for: indexPath) as? ChatCollectionViewCell {
let text = self.chatLog[indexPath.row].text
cell.chatTextView.text = text
cell.chatViewWidth = (text?.widthHeight(font: UIFont.systemFont(ofSize: 16)).width)!
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let text = self.chatLog[indexPath.row].text {
let box = text.widthHeight(font: UIFont.systemFont(ofSize: 16))
return CGSize(width: view.frame.width, height: box.height + 10)
}
return CGSize(width: self.view.frame.width, height: 60)
}
When this code runs, I get massively miscalculated cell sizes:
As you can see, the view's frames are very messed up.
The first line is "Heya", the second line is "How's life going so far", and the third line is "I'm a stapler, you're a textbook." Some cells are too narrow, some cells are too wide.
Here's some additional code for my custom collectionViewCell:
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
override func layoutSubviews() {
chatView.frame = CGRect(x: 0, y: 0, width: chatViewWidth, height: frame.height)
chatTextView.frame = CGRect(x: chatView.frame.origin.x + 10, y: 0, width: chatView.frame.width - 20, height: chatView.frame.height)
}
func setupViews() {
if isTextFromCurrentUser {
chatTextView.frame = CGRect(x: 10, y: 0, width: frame.width - 140, height: frame.height)
chatTextView.backgroundColor = .white
} else {
chatTextView.frame = CGRect(x: frame.width - 150, y: 0, width: frame.width - 140, height: frame.height)
chatTextView.backgroundColor = .blue
}
chatTextView.font = UIFont.systemFont(ofSize: 16)
chatTextView.layer.cornerRadius = 9
chatTextView.clipsToBounds = true
chatTextView.autoresizingMask = UIViewAutoresizing.flexibleHeight
chatTextView.isScrollEnabled = false
contentView.addSubview(chatView)
contentView.addSubview(chatTextView)
}
Chemo,
As I believe its a chat bubble to which you are trying to set the hight for and chat bubble cant have any scroll inside it make sure your textView's scroll is disabled.
Second as Chat bubble should increase its height based on content and there is no height limit use CGFloat.greatestFiniteMagnitude as possible height that you can accommodate while calculating boundingRect
func widthHeight(font: UIFont) -> CGRect {
let constraintRect = CGSize(width: 200, height: CGFloat.greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return boundingBox
}
Finally make sure there is no contentInset set to the textView. If contentInset set as left 5 and right 5 make sure to subtract 10 (5 + 5) from max width you can accommodate.
As height is the only variable here in equation setting width exactly is the key to get correct height. Make sure you set the line options correct matching ur textViews property.
Suggestion:
UITableView can make use of automatic height for cell and setting scroll disable on textView makes textView to calculate its size based on the text set. I mean textView will respect the implicit size.
As I believe you are creating a chat app where each bubble is a cell, consider more sane option of using UITableView and leverage the benefit of automatic cell height then messing up with collectionView which expects you to provide the size for each item manually.
Pinch of Advice :D
I have personally used bounding rect and managed to calculate the exact height for text after loads of trial and error method. I personally suggest creating a textView instance, setting its property exactly matching the property of textView you have in your storyboard and then set the text you wanna show and use sizeThatFits to get the actual size of textView which is much easier.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let textView = UITextView(frame: CGRect.zero)
//set textView property here
textView.text = self.chatLog[indexPath.row].text
let size = textView.sizeThatFits(CGSize(width: textView.bounds.width, height: CGFloat.greatestFiniteMagnitude))
return size;
}
My question is stated in the title.
I have a need to specify the content inset for specific rows. Reason for this is that i have to make a layout in which the first cell of the first section needs to occupy whole width of the screen, while first cell of the second section needs to have insets of left: 8, right: 8.
snapshot
Also, with inset set to 0 last cell in the section is positioned completely to the left of the screen. Any suggestions on that?
This is the code for calculating the size:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
collectionViewLayout.invalidateLayout()
if indexPath.section == 0 {
if indexPath.row == 0 {
return CGSize(width: self.view.frame.width, height: 100)
} else if indexPath.row == 1 || indexPath.row == 2 {
return CGSize(width: self.view.frame.width / 2 - 36, height: 50)
} else {
let size = CGSize(width: self.view.frame.width - 16, height: 50)
return size
}
} else {
if indexPath.row == 0 {
return CGSize(width: self.view.frame.width - 16, height: 100)
} else if indexPath.row == 1 || indexPath.row == 2 {
return CGSize(width: self.view.frame.width / 2 - 16, height: 50)
} else {
return CGSize(width: self.view.frame.width - 16, height: 50)
}
}
}