I am working on a crossword app but I keep missing something when I calculate each itemSize of the UICollectionView. I get the right size for iPhone SE but the wrong size for iPhone XR. What am I doing wrong?
func setLayout() -> UICollectionViewFlowLayout {
let layout = UICollectionViewFlowLayout()
layout.itemSize = getCellSize()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
return layout
}
func getCellSize() -> CGSize {
let width:CGFloat = self.view.frame.width/CGFloat(crossWordData[0].count)
let height:CGFloat = (self.view.frame.height - TitleView.frame.height)/CGFloat(crossWordData.count) - cellsBorderWidth
//cellsBorderWidth = 1.2
let cellSize = CGSize(width:width , height:height)
return cellSize
}
This is because self.view ends where is bottom of the display. New iPhones (X, Xs, Xs Max, Xr) have on the bottom this place for "home gesture". So for your calculation, content of your CollectionView is bigger than CollectionView because your CollectionView has bottom constraint equal to safe area bottom constraint which is for new iPhones higher than where bottom of the display is. So when you need width and height instead of this
let width:CGFloat = self.view.frame.width/CGFloat(crossWordData[0].count)
let height:CGFloat = (self.view.frame.height - TitleView.frame.height)/CGFloat(crossWordData.count) - cellsBorderWidth
call this
let width:CGFloat = self.yourCollectionView.frame.width/CGFloat(crossWordData[0].count)
let height:CGFloat = self.yourCollectionView.frame.height/CGFloat(crossWordData.count) - cellsBorderWidth
Eventually this is how I changed my code -
let width:CGFloat = self.view.frame.width/CGFloat(crossWordData[0].count)
let height:CGFloat = self.view.frame.height*0.85/CGFloat(crossWordData.count)
The collections view height is 85% in the screen layout
Related
I try to create a dynamic cell column from different iPhone devices with collectionView.
I already try for iPhone SE to have a 3 column and success, but when I try to make iPhone 11 Pro Max to have a 4 column it have a space between each cell.
iPhone 11 Pro Max
iPhone SE
I calculate my cell like this
enum UIHelper {
static func createCollectionViewFlowLayout() -> UICollectionViewFlowLayout {
let screenWidth = UIScreen.main.bounds.width
let padding: CGFloat = 12
let minimumInterimSpacing: CGFloat = 10
let availableWidth = screenWidth - (padding * 2) - (minimumInterimSpacing * 2)
var numberOfColumn: CGFloat
// 375 is iPhone SE width
if screenWidth > 375 {
numberOfColumn = 4
} else {
numberOfColumn = 3
}
let itemWidth = availableWidth / numberOfColumn
let flowLayout = UICollectionViewFlowLayout()
flowLayout.sectionInset = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
flowLayout.itemSize = CGSize(width: itemWidth, height: itemWidth)
return flowLayout
}
}
and in my view controller I create it like this
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.collectionViewLayout = UIHelper.createCollectionViewFlowLayout()
}
what am I missing?
Your setup seems to be fine, The issue seems to be with the logic / math in this line
let availableWidth = screenWidth - (padding * 2) - (minimumInterimSpacing * 2)
The logic is that if you have 3 cells in a row, there will be 2 gaps between the 3 cells, but if you have 4 cells in a row, you will have 3 gaps.
So if I change the available width also based on the number of cells I intend to have, you will get the desired results. So I have made some small changes to change the available width based on how many cells you want in a row.
I have made some minor edits to your code, I have included some comments to show what I have changed, however, you will need to organize my updates better as I made quick changes to show you the fix in the logic.
static func createCollectionViewFlowLayout() -> UICollectionViewFlowLayout {
let screenWidth = UIScreen.main.bounds.width
let padding: CGFloat = 12
let minimumInterimSpacing: CGFloat = 10
// Updated this to a var
var availableWidth = screenWidth - (padding * 2) - (minimumInterimSpacing * 2)
var numberOfColumn: CGFloat
// 375 is iPhone SE width
if screenWidth > 375 {
numberOfColumn = 4
// Update the width available as well
availableWidth = screenWidth - (padding * 2) - (minimumInterimSpacing * (numberOfColumn - 1))
} else {
numberOfColumn = 3
}
let itemWidth = availableWidth / numberOfColumn
print(numberOfColumn)
let flowLayout = UICollectionViewFlowLayout()
flowLayout.minimumInteritemSpacing = minimumInterimSpacing
flowLayout.sectionInset = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
flowLayout.itemSize = CGSize(width: itemWidth, height: itemWidth)
return flowLayout
}
I want to create multiple buttons and position it inside uiview and fit to uiview.(as picture)
I need to get uiview frame to calculate and divide as I need , to set button's width and height depending on device size.
for row in 0 ..< 4 {
for col in 0..<3 {
let numberButton = UIButton()
numberButton.frame = CGRect(x: Int(buttonView.frame.width / 3 - 20) * col, y: row * (320 / 4), width: Int(buttonView.frame.width) / 3, height: Int(buttonView.frame.height) / 4)
numberButton.setTitle("button", for: .normal)
numberButton.titleLabel?.font = numberButton.titleLabel?.font.withSize(30)
numberButton.setTitleColor(UIColor.black)
buttonView.addSubview(numberButton)
}
}
I tried like code above, but buttonView.frame.width returns nil.
How can I calculate this view's frame?
You can use UIStackViews to achieve this grid layout. This way, you don't have to calculate the frames of each button. Doing so is bad practice anyway. You should instead use AutoLayout constraints to layout your views. Here's a tutorial to get you started.
Anyway, here's how you would use UIStackViews to create a grid of buttons:
// here I hardcoded the frame of the button view, but in reality you should add
// AutoLayout constraints to it to specify its frame
let buttonView = UIStackView(frame: CGRect(x: 0, y: 0, width: 600, height: 320))
buttonView.alignment = .fill
buttonView.axis = .vertical
buttonView.distribution = .fillEqually
buttonView.spacing = 20 // this is the spacing between each row of buttons
for _ in 0..<4 {
var buttons = [UIButton]()
for _ in 0..<3 {
let numberButton = UIButton(type: .system)
numberButton.setTitle("button", for: .normal)
numberButton.titleLabel?.font = numberButton.titleLabel?.font.withSize(30)
numberButton.setTitleColor(UIColor.black, for: .normal)
// customise your button more if you want...
buttons.append(numberButton)
}
let horizontalStackView = UIStackView(arrangedSubviews: buttons)
horizontalStackView.alignment = .fill
horizontalStackView.axis = .horizontal
horizontalStackView.distribution = .fillEqually
horizontalStackView.spacing = 20 // this is the spacing between each column of buttons
buttonView.addArrangedSubview(horizontalStackView)
}
Result from playground quick look:
I have a collectionView and the customCollectionViewCells do not resize correctly on selected devices. I handle the cellSizes in viewWillTransition and have different number of cells for iPad and iPhones.
My implementation so far:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let safeFrame = view.safeAreaLayoutGuide.layoutFrame
let size = CGSize(width: safeFrame.width, height: safeFrame.height)
return setCollectionViewItemSize(size: size)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.itemSize = setCollectionViewItemSize(size: size)
layout.invalidateLayout()
}
}
func setCollectionViewItemSize(size: CGSize) -> CGSize {
let height: CGFloat = 400
if UIDevice.current.userInterfaceIdiom == .pad {
if UIApplication.shared.statusBarOrientation.isPortrait {
let width = (size.width - 3 * spacing) / 2
return CGSize(width: width, height: height)
} else {
let width = (size.width - 4 * spacing) / 3
return CGSize(width: width, height: height)
}
} else {
if UIApplication.shared.statusBarOrientation.isPortrait {
let width = size.width - (2 * spacing)
return CGSize(width: width, height: height)
} else {
let width = (size.width - 3 * spacing) / 2
return CGSize(width: width, height: height)
}
}
}
The above code resizes correctly when rotated for most iPhones that requires safeAreaLayoutGuide (ie Xs, Xr) but not 5s, 6s and 6s plus. It also does not resize correctly for iPads.
I am also getting the below warning, but as this post has recommended, I implemented invalidateLayout(), but still doesn't silent the warnings and correctly resize the cells.
2019-03-13 22:33:08.679440+0800 Snapshotting a view (0x7f8f1a711650, Construction.CollectionCardCell) that has not been rendered at least once requires afterScreenUpdates:YES.
2019-03-13 22:33:16.375665+0800 The behavior of the UICollectionViewFlowLayout is not defined because:
2019-03-13 22:33:16.375809+0800 the item width must be less than the width of the UICollectionView minus the section insets left and right values, minus the content insets left and right values.
2019-03-13 22:33:16.376470+0800 The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7f8f1a40eab0>, and it is attached to <UICollectionView: 0x7f8f1a8db600; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x6000017de2e0>; animations = { bounds.origin=<CABasicAnimation: 0x6000019ecc20>; bounds.size=<CABasicAnimation: 0x6000019ecce0>; position=<CABasicAnimation: 0x6000019f2b80>; bounds.origin-2=<CABasicAnimation: 0x6000019f2c40>; bounds.size-2=<CABasicAnimation: 0x6000019f2c80>; }; layer = <CALayer: 0x6000019cfa60>; contentOffset: {0, -64}; contentSize: {568, 1296}; adjustedContentInset: {64, 0, 49, 0}> collection view layout: <UICollectionViewFlowLayout: 0x7f8f1a40eab0>.
2019-03-13 22:33:16.376574+0800 Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
I created an empty project to test the above code using just basic UICollectionViewCell but somehow resizes itself correctly. Is there something I need to do with my customCollectionViewCells to force it to resize or redraw itself?
Trying to show buttons dynamically in a UICollectionViewCell depends on data coming in array from api. Below is what trying to achieve:
This is so far what i have achieved using button, but i need multiple buttons means button next to "About us" then next until space there then move to next line and setting height of cell as per height of view in cell. Please guide how it can be achieved.
Below is my code:
var xPosition = cell.btnOptions.frame.origin.x
var yPosition = cell.btnOptions.frame.origin.y
var width = cell.btnOptions.frame.size.width
var height = cell.btnOptions.frame.size.height
if((arrOptions?.count ?? 0) > 0)
{
for i in 0..<arrOptions!.count
{
//
cell.btnOptions.tag = i
cell.btnOptions.layer.cornerRadius = 3.0
cell.btnOptions.frame = CGRect.init(x: xPosition, y: yPosition, width: width, height: height)
//
cell.btnOptions.setTitle(arrOptions?.objectAt(i).object(forKey: "title") as? String, for: .normal)
xPosition = width + width + 8
yPosition = height
}
I have a UICollectionView with a custom layout. As seen in the image below there is always a gap between the content and the bottom of the cell.
Each cell consists of an image and three labels. I've printed out the size of the of the cell and also printed out the size of the image + the 3 lables and the match, so I have no idea where this extra space is coming from.
let width = columnWidth - cellPadding*2
let photoHeight = delegate.collectionView(collectionView!, heightForPhotoAtIndexPath: indexPath , withWidth:width)
print("photo height is: \(photoHeight)")//photo height is: 95.4078125
let label1Height = delegate.collectionView(collectionView!, heightForLabel1AtIndexPath: indexPath, withWidth: width)
print("label1Height is \(label1Height)") // label1Height is 39.0
let label2Height = delegate.collectionView(collectionView!,heightForLabel2AtIndexPath: indexPath, withWidth: width) //label2Height is 53.0
print("label2Height is \(label2Height)")// label2Height is 53.0
let label3Height = delegate.collectionView(collectionView!, heightForLabel3AtIndexPath: indexPath, withWidth: width)
print("label3Height is \(label3Height)") //label3Height is 135.0
let height = photoHeight + label1Height + label2Height + label3Height
print("total height is: \(height)") //total height is: 322.4078125
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
I've pinned the bottom of label3 to the bottom of the cell +4, but that doesn't help